Repository navigation
Allow a signed object storage folder as the origin - #1377
Open
jwrosewell wants to merge 48 commits into
Open
jwrosewell wants to merge 48 commits into
jwrosewell wants to merge 48 commits into
Conversation
A publisher whose site is static files in object storage had to be served by an asset route, which skips the page pipeline. Three optional [publisher] settings let the ordinary publisher proxy serve it, so its pages pass through the fetch and serve middleware. Each is off unless set, and a document without them sends the origin request it sent before and serializes to the same bytes. A path in origin_url is kept, and the request path is added after it with one slash. With a path set, a request path with a dot segment in any spelling is refused with 400, so no request reaches outside the folder. origin_auth takes the table an asset route's auth takes and signs through the same function. Only GET and HEAD are signed and any other method is refused with 405, so the publisher's credentials are only used to read. A signed request carries none of the reader's cookies, credentials, referrer, conditional validators or range, and none of the signing namespace's headers. Its key references resolve from the secret store as an asset route's do. directory_index names a file added to a request path ending in a slash, for the origin request only. A malformed origin_auth or directory_index is refused when the settings load, with the setting named. The asset route refusals keep their wording.
…proven masked The design page for the publisher origin in object storage follows the shape of the stack's design documents, and the changelog and the guide's list of static secret references name the new settings. The configuration view's test of every declared secret now holds the three key references of publisher.origin_auth, which it requires of any declared secret, so they are proven masked in the view and absent from what it serves. A further test keeps them out of debug output. The comments on the signer and on the auth type say the publisher origin uses them too.
# Conflicts: # CHANGELOG.md
The publisher origin check and the closed path check share one bounded percent-decoding helper. A closed path still decoding after the last round is now closed, as a dot segment already was.
…pters and the tests it lacks Review round 1 found that a path in origin_url changes every deployment whose document has one, that the closed path check now answers 404 to a path still changing after four rounds of percent-decoding on every deployment, that the query is not always kept, and that AWS signing is a vendor's. The design now says each of these, with the upgrade and rollback order, and adds the adapters and the Host that is signed, what a signed read sends and what a reader sees when storage refuses it, the exact rule for directory_index, the tests that are missing and the review round. The changelog entry gains one sentence on the closed path check.
Review round 2 found that the changelog entry still counted the path in origin_url as a setting that is off unless set and listed both breaking changes under Added, that the x-amz- header rule read as if core already had it, that the signed address is rebuilt through the URL parser and a signed origin answers 405 before the dot segment check, and that the statements about S3 had no source in the tree. The changelog now lists the two breaking changes under Changed with the label and counts two settings. The design says each of the other three and attributes what it says about S3 to AWS's documentation, and the review log records the round.
The header rewrite from main takes the reader's own path and query for its loop guard, the read-through reader URL and the template cache key. This row maps the origin path for object storage, so request_path_and_query is read from the request itself rather than from the mapped origin path.
jwrosewell
marked this pull request as ready for review
October 19, 2026 20:23
This branch has not been deployed
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.
Summary
Changes
crates/trusted-server-core/srcdocs/superpowersdocs/guidetrusted-server.example.tomlCHANGELOG.mdCloses
Closes #1329
Spec
Spec: publisher-object-storage-origin
Test plan
CI on this pull request at e8b8809 passed all 20 checks.
cargo test-fastly && cargo test-axumcargo clippy-fastly && cargo clippy-axumcargo fmt --all -- --checkcd crates/trusted-server-js/lib && npx vitest runcd crates/trusted-server-js/lib && npm run formatcd docs && npm run formatcargo build --package trusted-server-adapter-fastly --release --target wasm32-wasip1fastly compute serveChecklist
unwrap()in production code (clippy deniesunwrap_usedin CI)logmacros as AGENTS.md asks (notprintln!)On top of a deployment addressing a request by its path (#1376). Issue: #1329.
Kept current. This update only merges the pull request below (#1376), which brings Tech Lab's
mainat 7aa5c99, the changes made lower in the stack andfiftyone-caching4.5.13 (#1372).What this adds
What changes.
A publisher whose site is static files in object storage is served by the ordinary publisher proxy, so its pages pass through the fetch and serve middleware, the template cache and the making of a missing file, all of which an asset route skips. Three optional
[publisher]settings do it, each off unless set.//bucket/site/index.html, signed/news//bucket/site/news/index.html, signed/news/bucket/site/news, signed/style.css?v=2/bucket/site/style.css?v=2, signed/news/../../other-site/page400POST, or any method butGETandHEAD405withAllow: GET, HEADHow it works.
origin_urlis kept and the request path is added after it with exactly one/, whichever side carries one. The query is kept. Anorigin_urlwith no path sends the request path as it arrived, which is what every existing document does. The address is built in one place, so the streamed page, the buffered page, the template cache key, aHEADand every other method take the same one.origin_authis the table an asset route'sauthtakes, read as the same type with the same fields, defaults and secret store key references, and it signs through the same function, so there is one signing path. The request is signed last, once its address andHostare final. OnlyGETandHEADare signed and any other method is answered405, so the publisher's credentials are only used to read. A signed request carries none of the reader'sCookie,Authorization,Proxy-Authorization,Referer, conditional validators orRange, and none of the signing scheme's own headers.origin_urlmust usehttpswhenorigin_authis set.origin_url, a request path with a.or..segment, as written or after up to four rounds of percent-decoding, is answered400and nothing is sent.directory_indexnames one file, added to a request path ending in/for the origin request only. The reader's address and the path the middleware see do not change.origin_auth, anorigin_authbeside anorigin_urlthat is nothttps, an empty key reference (byts config validateandts config push), and adirectory_indexthat is not one file name.Why
The page pipeline, the template cache, the making of a file the origin does not have and the cache policy for a path all hang off the publisher path. A second path for a site kept in storage would carry each of them again, and a page would behave differently according to where its bytes are kept. Each of the three settings changes the request core already makes to the one origin
[publisher]names, none is a vendor's, and the signer is the one core already has for asset routes.What breaks
Nothing for a document that sets none of the three. It sends the origin request it sent before and serializes to the same bytes. A document that sets one is refused by a build that predates them, because
[publisher]refuses a field it does not know, so the build is deployed before the document is pushed.Publishergains two public fields,origin_authanddirectory_index, so Rust code outside the crate that buildsPublisherwith a struct literal stops compiling until it adds them or uses..Default::default().The commits
origin_auth,s3_sigv4andREADER_HEADERS_NOT_SIGNEDThe other commits merge the branch below into this one, and through it Tech Lab main at 7aa5c99.
What is not in this pull request
403, and with404only where the credentials may also list the bucket. No storage service was called in testing this change.The design page is
docs/superpowers/specs/2026-10-13-publisher-object-storage-origin-design.md.