WP Manifestindependent plugin directory
manifest / forms / wp-power-contract

Power Contract WP Registry grade F. Critical findings · 4 findingsWP RegistryCritical findings · 4 findingsOpen the reportF #5 in Forms

WordPress 線上簽合約 & 審批 外掛,您可以創建多個合約模板,帶入多種欄位,把網址丟給您的客人,讓他們線上簽合約。

★ 4stars
416release downloads
≈50active sites
v0.0.13latest · 6mo ago

Install

Download power-contract.zip From the v0.0.13 release on GitHub, Apr 24, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/zenbuapps/wp-power-contract/releases/download/v0.0.13/power-contract.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

Power Contract | 我的 WordPress 外掛

一句話講完 Power Contract :

WordPress 線上簽合約 & 審批 外掛,您可以創建多個合約模板,帶入多種欄位,把網址丟給您的客人,讓他們線上簽合約。

使用流程




1. 創建合約模板

image

  • 下方有可以使用的 shortcode ,點擊複製貼上
  • 右方可以上傳公司合約章,不同合約模板可以搭配不同印章,您有多間公司也OK

❓ 怎麼調整版面 & 公司章位置

  • Power Contract 可以接入各編輯器,像是 傳統編輯器、區塊編輯器、Elementor 等,你可以用你熟悉的工具調整版面
  • Shortcode 都可以設置 class、id,所以如果你更習慣寫 css, javascript,也可以自行調整
  • 支持任意 input name 名稱,在未衝突的情況都會存入 post_meta
  • 如果 input 的 shortcode 手動填入 value ,則會變成 readonly,且不能編輯

❓ 怎麼調整合約模板版面

你可以複製 wp-content/plugins/power-contract/inc/templates/single-contract_template.php 到 wp-content/themes/{您的佈景主題或子主題}/single-contract_template.php ,並且修改

wp-content/plugins/power-contract/inc/templates/single-contract_template.php 是合約模板的最小可運作模板,建議與專業人士或作者討論如何修改

2. 將合約模板網址傳給您的客戶查閱

image

此時欄位是不能編輯的

3. 客戶查閱後,開始簽約

點擊繼續,欄位都會變成黃底高亮,且可以編輯

image

image

如果有未填欄位或者未簽名,會有提示 image

4. 客戶送出

image

預設是沒有按鈕的,客戶簽約完只能關閉視窗

❓ 怎麼調整彈窗訊息以及按鈕

後台設定可以調整訊息以及按鈕文字連結

image

5. 管理員審閱合約以及核准合約

image

顯示

  1. 合約欄位資料
  2. 用戶簽名
  3. 用戶簽約當下的 IP 地址
  4. 用戶簽約當下的 合約截圖

可以批量核准或批量待審核

image




Hooks

簽約後 action hook

透過此 hook 可以自由控制簽約後的行為

do_action( 'power_contract_contract_created', $new_contract_id, $args )

調整 input shortcode 欄位參數

如果你想要動態調整 input shortcode 欄位參數,可以使用此 filter hook 例如要自動帶入參數的話,此 hook 很有用

apply_filters('power_contract_input_args', $args)


⛏️ 多語系工作仍在進行中

image

Releases

10 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v0.0.13 latest
Apr 24, 2026 6mo ago
power-contract.zip
28
Apr 9, 2026 6mo ago
power-contract.zip
11
Apr 7, 2026 6mo ago
power-contract.zip
15
Jun 18, 2025 1 year ago
power-contract.zip
45
Mar 17, 2025 1 year ago
power-contract.zip
61
Dec 13, 2024 1 year ago
power-contract.zip
57
Dec 13, 2024 1 year ago
power-contract.zip
5
Nov 26, 2024 1 year ago
power-contract.zip
34
Nov 23, 2024 1 year ago
power-contract.zip
13
Nov 21, 2024 1 year ago
power-contract.zip
147

Active-site estimate ≈50 comes from the median of recent superseded releases. Method.

F grade

Security

Power Contract 0.0.13 · audited by WP Registry

Critical findings.

1 critical 3 high
Audited release
0.0.13
Findings
4
Worst severity
critical
Content hash
225224e56fde96df54884ba0…

Findings

  • critical Unauthenticated create_contract mass-assigns POST into wp_insert_post and can mark any WooCommerce order signed

    missing_auth

    wp_ajax_nopriv_create_contract is reachable by guests. The only gate is check_ajax_referer('power-contract','nonce'); that nonce is localized as signature_pad_custom_data.env.nonce on every public contract_template (CPT is public and publicly_queryable; uid-0 nonces work). After skipping sanitization of screenshot/signature, the handler runs WP::separator($post_data, 'post'), which copies any key in the post data-field list (ID, post_author, post_content, post_title, post_status, post_type, post_name, tax_input, …) into $data and everything else into $meta_data. $data is wp_parse_args'd on top of the intended contract defaults, so attacker keys win, then wp_insert_post($args) is called with $data['meta_input']=$meta_data. wp_insert_post does not check capabilities: a guest can create a published page/post/product, or pass ID of an existing post (homepage, product, order post) and overwrite it, including WooCommerce price meta (_price, _regular_price). Passing post_status=approved bypasses merchant review. If _order_id is set, get_redirect_url() loads that order with no ownership check and writes is_signed=yes, which Checkout::redirect_before_thankyou_base_condition treats as 'contract already signed'. Pre-checkout can also be skipped with ?is_signed=yes. Observable on a site with at least one published template: POST admin-ajax.php action=create_contract with a scraped nonce, a tiny PNG data-URI screenshot, contract_template_id, and ID/post_type/post_status/_order_id as desired.

    inc/classes/Resources/Contract/Ajax.php:27-142

    Recommendation

    Do not pass raw POST through WP::separator into wp_insert_post. Allow-list contract meta keys, force post_type=contract and post_status=pending, and reject any ID/post_author/post_type/post_status supplied by the client. Require a valid published contract_template_id. When setting is_signed, verify the current user owns the order (or the order key) and that a real signature exists. Do not treat a GET is_signed flag as proof of signing.

  • high Stored XSS in the contract review metabox and notification email via unsanitized signature/meta

    xss_stored

    create_contract stores every leftover POST field as contract post meta. 'signature' (and 'screenshot' before upload) is in the sanitize skip list, so HTML is stored raw. Init::render_meta_box dumps every meta key and value into an admin table with no escaping. Values starting with data:image are concatenated into <img src> / <a href> without esc_url/esc_attr, so a payload such as data:image/png" onerror="alert(document.cookie) executes when a merchant opens the contract. Values that do not start with data:image (e.g. signature=<img src=x onerror=...>) are printed as HTML. Email::send_email captures that same metabox HTML and wp_mails it to power_contract_settings emails. Reviewing submitted contracts is the plugin's primary admin workflow, so the XSS is reliably triggered. Meta keys are also unescaped, so an HTML-bearing POST key becomes a table cell.

    inc/classes/Resources/Contract/Init.php:177-209

    Recommendation

    Escape meta keys with esc_html and values with esc_html, or wp_kses_post if HTML is required. Put data URIs only through esc_url and never into unquoted attributes. Do not skip sanitization of signature; store it as an uploaded attachment URL. Sanitize POST keys before using them as meta keys.

  • high Any logged-in user can approve or reject contracts with no capability check or nonce

    missing_capability

    admin_post_approve_contract and admin_post_reject_contract are registered without a nopriv twin (so guests are excluded) but the handlers never call current_user_can or wp_verify_nonce. They take post_id from GET, confirm post_type=contract, and wp_update_post the status to approved or rejected. The metabox renders these as bare GET links (admin-post.php?action=approve_contract&post_id=N) with a 'disabled' CSS class only — not an actual access control. A Subscriber or Customer who visits that URL, or an attacker who CSRF's any logged-in user, can approve a fraudulent contract or reject a legitimate one. LifeCycle then fires power_contract_contract_approved / power_contract_contract_rejected. Combined with finding 1, a guest can create a pending contract attached to a victim order and a subscriber account can immediately approve it.

    inc/classes/Resources/Contract/Init.php:217-288

    Recommendation

    Require current_user_can('edit_post', $post_id) (or a dedicated approve_contracts cap) and a nonce on both handlers. Switch the metabox from GET links to POST forms with wp_nonce_field. Treat the CSS 'disabled' class as UI only.

  • high Unauthenticated order PII disclosure via order_id on public contract templates

    information_disclosure

    When WooCommerce setting display_order_info is enabled, Shortcodes::set_default_value reads $_GET['order_id'] with no ownership, capability, or key check, loads that order, and writes billing full name, shipping address, billing_phone, and order total into public [pct_input] value attributes. The author left a TODO acknowledging this ('防止被猜出其他用戶個資') but the guard is commented out. After-checkout redirect already puts order_id in the contract URL (Checkout::redirect_before_thankyou), and order IDs are sequential. Anyone who can open the public contract_template can enumerate order_id and read other customers' checkout PII in the HTML. Base::get_full_address also falls back to the same unauthenticated order_id GET.

    inc/classes/Shortcodes/Shortcodes.php:319-361

    Recommendation

    Restore the ownership check (current user must own the order). For guests, require the WooCommerce order key (or a signed token) in addition to order_id. Do not auto-fill PII on a publicly cacheable template URL that only contains a sequential id.

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.