Repository navigation
Make creative click rewriting a separate feature with its own switch #1234
Description
Summary
Click wrapping is not its own feature today. The creative rewrite pass rewrites every <a href> and <area href> in a winning creative to a signed /first-party/click redirect. It does this in the same pass, behind the same switch and the same per-URL check that it uses to proxy assets. An operator can't keep first-party click tracking while leaving assets direct, or proxy assets while leaving landing links alone.
Make click rewriting a separate feature with its own switch.
Related: #1231 adds a host allowlist for asset rewriting and explicitly leaves anchors out of it. Both issues touch to_abs and its call sites in creative.rs, so coordinate if they're worked in parallel.
Spec first
Write a design spec and get it approved before opening an implementation PR.
- Put it at
docs/superpowers/specs/YYYY-MM-DD-1234-<slug>-design.mdand follow the existing specs there. Include the issue link, date, status, problem, goals, non-goals, design, test plan and rollout. - Land the spec in its own PR. The implementation PR links the approved spec and follows it. If the design changes during implementation, update the spec in the same PR.
- The spec must settle these open decisions:
- The switch's name, table and type. An optional bool that follows
rewrite_creativeswhen unset is one way to keep existing configs unchanged. Write down the rollout and rollback order givendeny_unknown_fields. - Whether the switch also covers click wrapping inside HTML fetched through
/first-party/proxy, which ignoresrewrite_creativestoday. - When
<base>removal and TSJS injection run once assets and clicks are separate. This issue proposes running both when either switch is on. - How the server switch relates to the client-only
TsCreativeConfig.clickGuardflag. - How the change lands alongside Add a host allowlist for creative asset rewriting #1231 if both are in flight.
- The switch's name, table and type. An optional bool that follows
Current behavior
Line numbers are at 8048301.
- The anchor handler in
rewrite_creative_html_impl(crates/trusted-server-core/src/creative.rs:1283-1291) callsto_abs, thenbuild_click_url, then sets bothhrefanddata-tsclickto the signed click URL. to_abs(creative.rs:54-75) is the per-URL check for assets and anchors alike. It skips relative URLs, non-http schemes such asmailto:andjavascript:, and hosts in[rewrite] exclude_domains. An excluded anchor keeps its rawhrefand gets nodata-tsclick.[auction] rewrite_creatives(checked atcreative.rs:1031) turns the whole pass on or off, so assets, clicks,<base>removal and TSJS injection all go together. The comment on it intrusted-server.example.tomlsays as much: "Set false to skip proxy/click-URL conversion and creative TSJS injection."- Three entry points share the pass:
POST /auctionadm, viaprocess_auction_creative(auction/formats.rs:398). Injects TSJS, so the click guard runs in the creative frame.- SSAT and page-bids inline creatives, via
process_inline_auction_creative(publisher.rs:5835). Emits absolute click URLs anddata-tsclickbut injects no TSJS, so no click guard runs there. - HTML fetched through
/first-party/proxy(CreativeHtmlProcessor,proxy.rs:645-680). This path ignoresrewrite_creativesand always rewrites, clicks included.
- Runtime pieces, always routed in every adapter and gated by no setting:
/first-party/click(handle_first_party_click,proxy.rs:1545) validates the signature, appendsts-ecto the target, and returns a 302./first-party/proxy-rebuild(proxy.rs:1812) re-signs a click after creative script added or removed query params.- The click guard (
crates/trusted-server-js/lib/src/integrations/creative/click.ts) acts only on anchors with a server-setdata-tsclick. It defaults to on throughTsCreativeConfig.clickGuard, which is a client-only flag that no server setting maps to.
Proposed change
- Add a click switch, for example
[auction] rewrite_clicks, that gates the anchor handler. rewrite_creativesthen governs asset rewriting only. Keep the name so existing configs still parse, and update its doc comment and the example TOML.<base>removal and TSJS injection run when either switch is on.<base>removal protects root-relative click URLs as well as proxy URLs, and on the/auctionpath TSJS injection is what delivers the click guard.- Operators with
rewrite_creatives = falsetoday also have clicks off, and upgrading must not turn click wrapping on for them. One option is an optionalrewrite_clicksthat followsrewrite_creativeswhen unset. exclude_domainskeeps applying to clicks. The asset allowlist from Add a host allowlist for creative asset rewriting #1231 does not.- Whether the switch also covers click wrapping in proxied HTML is an open decision for the spec.
Constraints
- Config structs use
deny_unknown_fields, so older binaries reject blobs that carry the new key. Skip serializing the default the wayrewrite_creativesdoes (auction_config_types.rs:46-50, tested inconfig_payload.rs:715). The upgrade order stays binary first, config second. - EdgeZero env overlays only replace scalar leaves that already exist in the TOML. Add the new leaf to
trusted-server.example.tomlso overlays can reach it.
Out of scope
- The asset host allowlist (Add a host allowlist for creative asset rewriting #1231).
- Unregistering
/first-party/clickand/first-party/proxy-rebuildwhen the switch is off. Creatives that were already served can still hold signed click URLs. - Adding a click guard to the inline SSAT path.
Done when
- The design spec is approved and merged under
docs/superpowers/specs/, and the implementation PR links it. - With the new setting unset, rewritten output is unchanged for both values of
rewrite_creatives. - Tests cover all four combinations of the asset and click switches on the
/auctionand inline paths, including<base>removal and TSJS injection when only one switch is on. - With clicks off, anchors keep their raw
hrefand get nodata-tsclick. - With clicks on and assets off, anchors are wrapped and asset URLs are untouched.
-
exclude_domainsstill applies to anchors. - A config round-trip test shows the default is left out of the serialized blob.
-
docs/guide/configuration.md,docs/guide/creative-processing.md,trusted-server.example.tomlandCHANGELOG.mdare updated.
Affected area
HTML processing / JS injection
Activity