From ac285e7ea166d8e66a8abae79864383e7e329a7a Mon Sep 17 00:00:00 2001 From: Todd Schiller Date: Sat, 6 Jun 2026 00:22:14 -0400 Subject: [PATCH 1/4] Feat: hidden-affiliate-sanitize rule for affiliate/UTM/promo metadata (#121) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Narrow sanitize companion to form-prefill-annotate (PR1). On checkout URLs, clear `value` on `` whose `name` matches a curated affiliate / UTM / promo / coupon allowlist (utm_*, aff_*, ref_*, referral_*, promo*, coupon*, discount_code, source_id, campaign_*, partner_*, click_id, gclid, fbclid, msclkid). Annotation is the wrong tool for hidden inputs — the value is submitted regardless of any chip and never reaches the agent's snapshot, so clearing is the only useful action. FP control: - Hard denylist takes precedence (CSRF / nonce / signature / hmac / secret / session / state / cart_id / order_id / token / antiforgery / verification / authenticity, etc.). Substring-and-whole-name match so `_csrf`, `csrf-token`, `apple_pay_signature`, and `_token` are all preserved. Failure mode for these is a silently-rejected submit — strictly worse than the original dark pattern. - URL gate via isCheckoutUrl. - Form-scoped: the input must live inside an enclosing `
` (or reference one via the `form` attribute). Free-floating hidden inputs are JS-only carriers we leave alone. - Per-host kill-switch (empty at launch) for known loyalty / Apple Pay / 1-Click flows where saved attribution is the user intent. - Set-once: stamping CLEARED_ATTR prevents a re-clear fight loop with pages that repopulate the value after our scan. Value is cleared via the prototype's native setter so React/Vue value trackers see the change. No input/change event dispatch — hidden inputs don't expect them and firing one risks tripping totals-recalculation that re-fetches attribution from the same source. Ships default-on. Tests cover the curated allowlist, denylist precedence, form-scope requirement, lazy-loaded subtrees, URL gate, form-attribute association, already-empty short-circuit, and the no- double-clear guarantee. Property tests assert allowlist/denylist disjointness, denylist precedence across composed names, URL-gate invariance, and idempotency across a mix of allowed and preserved inputs. Docs updated under Dark patterns → Preselection alongside form-prefill- annotate; demo Checkout page grows hidden affiliate / UTM / promo metadata plus a csrf_token so both halves of the rule are exercised. Co-Authored-By: Claude Opus 4.7 (1M context) --- demo-site/src/pages/Checkout.tsx | 20 ++ docs/src/content/docs/rules.md | 47 ++- extension/data/rule-defaults.json | 3 +- extension/src/lib/dom-markers.ts | 8 + extension/src/lib/rule-groups.ts | 1 + extension/src/popup/rule-labels.ts | 1 + ...hidden-affiliate-sanitize.property.test.ts | 203 +++++++++++ .../hidden-affiliate-sanitize.test.ts | 301 +++++++++++++++++ .../src/rules/hidden-affiliate-sanitize.ts | 318 ++++++++++++++++++ extension/src/rules/index.ts | 2 + .../src/rules/rule-defaults.generated.ts | 1 + 11 files changed, 898 insertions(+), 7 deletions(-) create mode 100644 extension/src/rules/__tests__/hidden-affiliate-sanitize.property.test.ts create mode 100644 extension/src/rules/__tests__/hidden-affiliate-sanitize.test.ts create mode 100644 extension/src/rules/hidden-affiliate-sanitize.ts diff --git a/demo-site/src/pages/Checkout.tsx b/demo-site/src/pages/Checkout.tsx index 03dc0ac..9ce374a 100644 --- a/demo-site/src/pages/Checkout.tsx +++ b/demo-site/src/pages/Checkout.tsx @@ -47,6 +47,26 @@ export default function Checkout() { 4. Shipping & contact + {/* Hidden affiliate / UTM / promo metadata — exercises + hidden-affiliate-sanitize. The csrf_token must be + preserved; everything else is cleared. */} + + + + +