Skip to content

Add CrawlProof ad units, without spending the CSP on them - #1

Merged
ralyodio merged 2 commits into
masterfrom
crawlproof/install-ad-embed-f54a21ad-mtdz1bvk
Aug 29, 2026
Merged

Add CrawlProof ad units, without spending the CSP on them#1
ralyodio merged 2 commits into
masterfrom
crawlproof/install-ad-embed-f54a21ad-mtdz1bvk

Conversation

@crawlproof

@crawlproof crawlproof Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Adds the CrawlProof ad units to d3vices, rewritten for this codebase.

Slot f54a21ad-3db6-4a62-a9f4-93d505c221c9, which is already active and serving.

Why the generated version was replaced

It imported next/script into a Bun/Hono app with no Next in it — that is what CI was red on — and stacked all four sizes at the bottom of <body> on every page.

The quieter problem: ad.js injects each creative as a srcdoc iframe, and a srcdoc document inherits the embedder's CSP. The creative's own <style> block and its remote images would only have rendered if this site added 'unsafe-inline' to style-src and opened img-src to the web — on every page, permanently, for an ad. app.js allows a single inline script by hash on purpose; that is not a good trade.

/api/ads/frame returns the same creative as a real cross-origin document, which carries its own policy. Advertising therefore costs exactly one CSP directive:

frame-src https://crawlproof.com

No third-party script runs on the page, no cookie is set, and nothing is written to local storage — the script tag mints a permanent crawlproof.visitor id in the publisher's origin, which would have contradicted the privacy page.

Placements

Two, both text_link — the only format built to fill its container. The banner creatives are laid out at a fixed pixel width inside the frame, so a narrowed frame crops one rather than reflowing it, and without a script nothing here can measure the viewport.

Page Where
Test pages (24) Below the instrument, its permissions and its FAQ, above "Nearby instruments"
Home In the seam after the rack, before the copy about it

Never above a test: someone who came to find out whether their microphone works reaches it without passing an ad.

An unsold slot answers with a blank document, and nothing on this side can see that across an origin — so the unit is a strip of whitespace with no border and no ground of its own. Empty, it reads as the gap between two sections, which is where it was put.

The rest

  • ADS_SLOT turns it off when empty. The slot id is a public identifier that ships in the markup, so it is the default rather than a variable somebody has to remember to set — merging this is not a silent no-op. Nothing to configure on Railway.
  • The desktop app is unaffected. export.js sets ADS_SLOT='' before rendering: the packaged app ships that output and makes no network request unless you run the network test, and an ad frame is a network request. Verified — zero crawlproof references in dist/site.
  • Privacy page now describes the ad, what CrawlProof does and does not see, and why there is no ad script. While that section was open: it claimed no scripts or fonts were loaded from anyone else's server, which has not been true since the page started loading two typefaces from Google. Fixed.
  • loading="lazy", so the impression is only spent once the unit is near the viewport.
  • Sandboxed with neither allow-scripts nor allow-same-origin; the popup permissions are only what lets the click-through open a tab.

Verified

biome check clean, 55 tests pass, static export clean, all 24 pages pass the browser smoke test. In a real browser, on both pages: the frame fills the column (984px desktop, 320px on a 390px phone, no horizontal overflow), the click-through resolves, and there are no CSP violations and no page errors.

One known rough edge

/api/ads/frame does not accept a theme parameter the way /api/ads/serve does, and without a script this end cannot detect the viewer's theme anyway. So the ad always renders in the slot's theme. On this site's default dark it looks native; on the light theme it is a dark strip — legible and clearly an ad, but louder than it should be. Fixing it properly means forwarding theme from the frame route in crawlproof.com, which is a separate change.

crawlproof Bot and others added 2 commits August 29, 2026 06:00
The generated embed did not belong to this codebase. It imported
`next/script` into a Bun/Hono app that has no Next in it, which is what
CI was failing on, and it stacked all four ad sizes at the bottom of
`<body>` on every page, where three of them would have been laid out at a
fixed pixel width in a column that is not that width.

The deeper problem was quieter. `ad.js` injects each creative as a
`srcdoc` iframe, and a srcdoc document inherits the embedder's
Content-Security-Policy — so the creative's own `<style>` block and its
remote images would only have rendered if this site added
`'unsafe-inline'` to `style-src` and opened `img-src` to the web, on
every page, for an ad. The policy is a large part of what this site is.

`/api/ads/frame` serves the same creative as a real cross-origin
document, which carries its own policy. So the whole of advertising here
is one iframe and one `frame-src` entry: no third-party script runs, no
cookie is set, and nothing is written to local storage — the ad tag would
have minted a permanent `crawlproof.visitor` id there, which is exactly
what the privacy page promises does not happen.

Two placements, both `text_link`, the one format built to fill its
container: below the instrument and its FAQ on a test page, and in the
seam after the rack on the home page. Never above a test — someone who
came to find out whether their microphone works should reach it without
passing an ad. An unsold slot answers with a blank document, and nothing
on this side can see that across an origin, so the unit is a strip of
whitespace with no border and no ground of its own: empty, it reads as
the gap between two sections, which is where it was put.

The static export switches advertising off before it renders anything.
The desktop app ships that output and makes no network request unless you
run the network test, and an ad frame is a network request.

The privacy page now says all of this, and while it was open: it claimed
no scripts or fonts were loaded from anyone else's server, which has not
been true since the page started loading two typefaces from Google.
@ralyodio ralyodio changed the title Add CrawlProof ad units Add CrawlProof ad units, without spending the CSP on them Aug 29, 2026
@ralyodio
ralyodio merged commit e23c8fe into master Aug 29, 2026
4 checks passed
@ralyodio
ralyodio deleted the crawlproof/install-ad-embed-f54a21ad-mtdz1bvk branch August 29, 2026 06:17
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