Skip to content

fix(security): initialize the adapter before deriving CSP origins - #3484

Merged
kojiwakayama merged 2 commits into
mainfrom
fix/derived-csp-uninitialized-adapter
Aug 8, 2026
Merged

kojiwakayama merged 2 commits into
mainfrom
fix/derived-csp-uninitialized-adapter

Conversation

@kojiwakayama

Copy link
Copy Markdown
Contributor

This is the actual cause of derived origins never working for hosted production projects. #3474 was not it, and I said it was — the promotion notes on veryfront-server#304 are wrong about that.

The bug. getAllSourceFiles answers [] until the adapter has a content context, and the only thing that establishes one is ensureSourceSnapshotFresh, which awaits ensureInitialized first (adapter.ts:917-919). The config load calls it before reading (adapter-factory.ts:298); the derivation path never did. Compounding it, the adapter's file-list warmup is itself gated on this.initialized, so the empty read never filled in afterwards. Not a cold cache that warms a moment later — one that never warms.

Why #3474 didn't help, and how that pointed here. #3474 stopped an empty read being cached forever, so after it shipped every request re-read the source. Production still derived nothing. That is what proved the emptiness was persistent rather than a cold-start race, and sent me looking for something structural instead. #3474 was still a real bug worth fixing; it just wasn't this one.

Why preview looked fine. Its adapter had already been initialized by the time derivation asked, so the same code read a working adapter. Same release, same source, different answer — which is what the probe project showed.

Hypotheses I falsified along the way, so the next person doesn't re-run them: release files carry no content (they do — verified against the real API through the client's own listPublishedFiles path), and the file-list warmup is failing (no warmup failures in production, and Refreshed source snapshot shows the adapter reading source).

The fix. Derivation reads through the same door the config load uses — on the wrapper and on the adapter that owns the file list, since the wrapper may delegate without exposing the hook.

Why this was never caught. deriveProjectCspOrigins had no test at all. The extractor was covered and the header merge was covered, so both neighbours stayed green while the seam between them was broken in production. It is exported and tested here: the hosted-release case fails without this change and passes with it, which I verified by removing the fix rather than trusting a green run, plus the no-tenant and read-throws paths.

Lint, typecheck, fmt, docs and the full unit suite green by exit code.

Still needs a release and promotion to confirm in production, and the check is one curl: vf-csp-probe.production.veryfront.com should gain https://images.unsplash.com and https://cdn.jsdelivr.net in img-src. Pairs well with #3482, which makes every derivation outcome say which one it was, so the next failure of this kind is visible in logs instead of needing a live probe.

Derived origins have never worked for hosted production projects. This is the
cause, and it is not the one #3474 fixed.

`getAllSourceFiles` answers an empty list until the adapter has a content
context, and the only thing that establishes one is `ensureSourceSnapshotFresh`,
which awaits `ensureInitialized` first. The config load calls it before reading;
the derivation path never did. Worse, the adapter's file-list warmup is itself
gated on being initialized, so the empty read never filled in afterwards. Not a
cold cache that warms a moment later -- one that never warms.

That is why the retry introduced by #3474 changed nothing in production: it
re-read an adapter that was never going to answer. Preview looked correct only
because its adapter had already been initialized by the time derivation asked.

The derivation now reads through the same door the config load uses, on both
the wrapper and the adapter that owns the file list, since the wrapper may
delegate without exposing the hook.

`deriveProjectCspOrigins` is exported and tested for the first time. That seam
had no coverage at all -- the extractor was tested and the header merge was
tested, so both neighbours stayed green while the part that actually reads an
adapter was broken in production. The hosted-release case fails without this
change and passes with it; I verified that by removing the fix rather than
trusting a green run.
@kojiwakayama
kojiwakayama requested a review from kwakayama as a code owner August 8, 2026 19:43
@coderabbitai

coderabbitai Bot commented Aug 8, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@kojiwakayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fd1a25f4-79da-4044-859d-a91d6bc98470

📥 Commits

Reviewing files that changed from the base of the PR and between 9d1d832 and 9595e67.

📒 Files selected for processing (2)
  • src/server/runtime-handler/derive-project-csp.test.ts
  • src/server/runtime-handler/project-runtime-context.ts

Comment @coderabbitai help to get the list of available commands.

`#veryfront/types` re-exports the interfaces that use it, not the type itself.
Caught by CI's `lint:test-typecheck`, which is a separate task from
`deno task lint` and so was not covered by the gate I ran locally.
@kojiwakayama
kojiwakayama added this pull request to the merge queue Aug 8, 2026
Merged via the queue into main with commit 52e5654 Aug 8, 2026
31 checks passed
@kojiwakayama
kojiwakayama deleted the fix/derived-csp-uninitialized-adapter branch August 8, 2026 20:10
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