Skip to content

Smart ad-unit PR installer (CSP patching + PR-run tracking) - #91

Merged
ralyodio merged 1 commit into
masterfrom
ads/smart-pr-installer
Jul 8, 2026
Merged

ralyodio merged 1 commit into
masterfrom
ads/smart-pr-installer

Conversation

@ralyodio

@ralyodio ralyodio commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Problem

The Submit PR to install flow for ad slots was the "dumb" cousin of the stats-tracker installer. It only injected the <div data-cp-ad> + /ad.js embed before </body> and never patched the publisher's CSP. On any site shipping a Content-Security-Policy, the ad unit is silently blocked — the /ad.js script (script-src), the /api/ads/serve fetch (connect-src), the creative srcdoc iframe (frame-src), and its artwork (img-src) all get refused. The tracker installer has patched CSP for a while; this brings the ad installer to parity.

What changed

  • Shared CSP machinery. Exported addSourceToDirective / hasDirective / looksLikeCsp and made findCspPatchTargets take a patch fn so the tracker and ad installers reuse the same conservative, append-only scanner.
  • patchCspForAds. Appends the CrawlProof origin to script-src / script-src-elem / connect-src / img-src, and 'self' to frame-src / child-src (the ad renders in a same-origin srcdoc iframe that inherits the host page's CSP). Only rewrites files that already look like a CSP; never adds a directive that wasn't there.
  • installAdEmbed. Now opens a single PR that both injects the embed and patches every CSP config file, returns cspPaths, and treats "embed already present" as a CSP-only PR.
  • PR-run tracking. The install route records a project_pr_runs row (kind=install_ad) so ad installs show up alongside the project's other automated PRs. Migration widens the kind check constraint.
  • Tests. New tests/contract/install-ad.test.ts covers patchCspForAds and the three installAdEmbed paths (inject+CSP, CSP-only, no-op).

Verification

  • tsc --noEmit: 0 errors
  • vitest run tests/contract: 331 passed / 7 skipped (incl. 5 new)

Deploy note: applying 20260708120000_pr_runs_install_ad.sql to prod — crawlproof's prod migration history has diverged, so apply this single migration via psql over the pooler rather than db push.

🤖 Generated with Claude Code

The "Submit PR to install" flow for ad slots only injected the embed
before </body> — it never touched the site's CSP, so on any publisher
with a Content-Security-Policy the ad unit was silently blocked (the
/ad.js script, the /api/ads/serve fetch, the creative iframe, and its
images all get refused). The stats-tracker installer already patches CSP;
this brings the ad installer to parity.

- Generalize the tracker's CSP machinery: export addSourceToDirective /
  hasDirective / looksLikeCsp and make findCspPatchTargets take a `patch`
  fn so both installers share it.
- Add patchCspForAds: appends the CrawlProof origin to script-src /
  script-src-elem / connect-src / img-src, and 'self' to frame-src /
  child-src (the ad is a same-origin srcdoc iframe). Append-only and
  conservative, same as the tracker.
- installAdEmbed now opens a single PR that both injects the embed and
  patches every CSP config file, returns cspPaths, and handles the
  embed-already-present case as a CSP-only PR.
- Record the install as a project_pr_runs row (kind=install_ad) so it
  shows up with the project's other automated PRs; widen the kind check
  constraint via migration.
- Add contract tests for patchCspForAds and installAdEmbed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

vu1nz Security Review

0 finding(s) in PR #?

No security issues found.

@ralyodio
ralyodio merged commit 371a0c1 into master Jul 8, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant