fix(cors): let the SDK's own requests and responses cross an origin - #84
Merged
Conversation
Two halves of the same defect, and together they made the documented cross-origin deployment mode not work at all. Access-Control-Allow-Headers listed Content-Type, Authorization and DPoP. The Vue SDK sends X-Requested-With on every request and X-Blob-Checksum and X-Blob-Label on blob writes. None of the three is CORS-safelisted, so the browser refused the request at preflight and it never reached vault42 -- which is why no server-side test could see it, and why every test in the suite, being same-origin, passed. There was no Access-Control-Expose-Headers at all. Only the safelisted response headers reach a cross-origin caller and none of the ones vault42 sets is on that list, so the blob helpers -- which hand the checksum and the label back to their caller -- returned null cross-origin while working perfectly same-origin. Retry-After is exposed with them: the server already sets it on the rate-limited paths and a client cannot honor a 429 it cannot read. Two headers named in the original report are deliberately absent. X-Vault-App is proxy-set, so no browser ever asks for it. X-Custom, X-Only and X-Fingerprint turned out to be test fixtures rather than anything the SDK sends. The list is what the SDK actually uses, and the new test states that separately from the middleware so the two cannot drift apart quietly. Two existing subtests had to change and were wrong in an instructive way. Both are named "allow-headers includes X" and both compared the entire header string for equality, so any legitimate addition broke them while proving nothing their names claimed. One of them already carried the message "should include DPoP". They now assert containment.
…he value The Allow-Headers row cited the whole statement including its value, so widening the header list orphaned the anchor and failed the register gate. That gate did its job: it asked whether the evidence for the row still exists or whether the row now claims something the code no longer does. It still exists. ASVS asks that sensitive functionality cannot be called by a request which skips the CORS preflight. The set is still closed and still has no wildcard, and every header added to it is itself non-safelisted, so a request carrying one preflights exactly as before. Authorization is what actually carries the argument and it has not moved. The anchor now stops before the value, the way the workflow-action anchors were changed to stop at the @ rather than quote a SHA. The notes named a set that is no longer the set, so they name the real one, and the two line references in them were stale by seven and sixteen lines.
42-v
enabled auto-merge (squash)
August 26, 2026 22:05
42-v
force-pushed
the
fix/cors-sdk-headers
branch
from
August 26, 2026 22:05
d2165bf to
769b8bb
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two halves of the same defect. Together they made the documented cross-origin deployment mode not work at all — not degrade, not error usefully, simply not work.
Requests never left the browser.
Access-Control-Allow-HeaderslistedContent-Type, Authorization, DPoP. The Vue SDK sendsX-Requested-Withon every request andX-Blob-Checksum/X-Blob-Labelon blob writes. None of the three is CORS-safelisted, so the browser refused at preflight and the request never reached vault42 — which is exactly why no server-side test could see it, and why the whole suite, being same-origin, passed.Responses came back blank. There was no
Access-Control-Expose-Headersat all. Only safelisted response headers reach a cross-origin caller and none of vault42's are on that list, so the blob helpers — which hand the checksum and label back to their caller — returnednullcross-origin while working perfectly same-origin.Retry-Afteris exposed with them: the server already sets it on the rate-limited paths (ratelimit.go:291,340) and a client cannot honor a 429 it cannot read.Two headers from the original report are deliberately absent, having checked rather than assumed:
X-Vault-Appmodel.go:608), so no browser ever asks for itX-Custom,X-Only,X-FingerprintThe new test states the SDK's needs separately from the middleware, so the two lists cannot drift apart quietly — if either moves without the other, one of them is wrong and this fails instead of a deployment doing it.
Two existing subtests had to change, and were wrong in an instructive way. Both are named
allow-headers includes Xand both compared the entire header string for equality, so any legitimate addition broke them while proving nothing their names claimed. One already carried the message"should include DPoP". They now assert containment.Verified:
go test -race ./internal/middleware/passes;golangci-lint0 issues on a cleaned cache; misspell clean. A third test confirms widening the allow-list did not widen the origin check.