Add JavaScript asset proxy integration - #742
Conversation
6b9389b to
e0d6bf8
Compare
8b56f22 to
753da1f
Compare
7730c4f to
d79e84b
Compare
ee2a692 to
03dd7b8
Compare
aram356
left a comment
There was a problem hiding this comment.
Summary
Adds the JS Asset Proxy integration (config-driven first-party serving of exact third-party script URLs with enabled/disabled/blocked modes), stream_response plumbing through proxy_request, and ts audit generation of disabled asset-proxy candidates. The design follows the spec closely and the security defaults are right (request-header allowlist only, no EC/Cookie forwarding, Set-Cookie stripped, HTTPS-only origins, opaque generated paths). Blocking items: a guaranteed 502 on the Cloudflare adapter, a CI fmt failure, and merge conflicts with main.
Blocking
🔧 wrench
- Cloudflare adapter rejects
stream_response, so every enabled asset request 502s there: see inline comment (crates/trusted-server-core/src/integrations/js_asset_proxy.rs:264) - CI
cargo fmtfails: edition-2024 import ordering on threeuselines; see inline comment (crates/trusted-server-core/src/integrations/js_asset_proxy.rs:14) - Merge conflicts with main: GitHub reports the PR as CONFLICTING;
git merge-treeshows conflicts incrates/trusted-server-core/src/config.rs,crates/trusted-server-core/src/integrations/mod.rs, andtrusted-server.example.toml. All three are mechanical (registration list, validated-IDs list, sample config), but the branch needs a merge or rebase before landing.
Non-blocking
🤔 thinking
builders()ordering is load-bearing but undocumented (crates/trusted-server-core/src/integrations/mod.rs:289)- Path validation permits
/(crates/trusted-server-core/src/integrations/js_asset_proxy.rs:120)
♻️ refactor
- Configured
origin_urlis never normalized, so non-canonical configs silently fail to match (crates/trusted-server-core/src/integrations/js_asset_proxy.rs:244) - No test drives
IntegrationProxy::handle()end-to-end (crates/trusted-server-core/src/integrations/js_asset_proxy.rs:449)
🌱 seedling
- Conditional revalidation never 304s at the edge; future allowlist additions would turn upstream 304 into 502 (crates/trusted-server-core/src/integrations/js_asset_proxy.rs:477)
<link rel="preload" as="script">hints for blocked/rewritten assets are untouched (crates/trusted-server-core/src/integrations/js_asset_proxy.rs:496)- Audit dedup keys on the full URL including volatile query strings (crates/trusted-server-cli/src/commands/audit/mod.rs:489)
⛏ nitpick
headers.get(VARY)takes only the first of repeated headers (crates/trusted-server-core/src/integrations/js_asset_proxy.rs:340)Content-Lengthdropped on a passthrough body (crates/trusted-server-core/src/integrations/js_asset_proxy.rs:334)#[cfg(test)] build_draft_configwrapper (crates/trusted-server-cli/src/commands/audit/mod.rs:331)
CI Status
- fmt: FAIL (import ordering; reproduced locally)
- clippy/check (all adapters): PASS
- rust tests (fastly, axum, cloudflare, spin, CLI, parity, browser/integration): PASS
- js tests (vitest): PASS
- docs/ts format: PASS
- mergeable: CONFLICTING
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
The integration is well-shaped for the existing registry/proxy patterns, the header policy is tight, and the audit-side generator produces a safe disabled-by-default inventory with opaque randomized paths. Four items block: the required cargo fmt check is still red, the always-on stream_response flag is rejected outright by the Cloudflare and Spin adapters, the branch now conflicts with main, and any upstream redirect turns into a hard 502.
Note on overlap: the previous CHANGES_REQUESTED review is pinned to this exact head (6d6f5892) and no commits have landed since, so all of its threads are still open. This pass does not restate them — it confirms the two blocking ones (below) and adds what is new.
1 of the inline comments below carries a one-click GitHub
suggestion— use Commit suggestion to apply it as a commit on the PR branch. The remaining comments describe the fix in prose because the change is a design decision or spans multiple files and can't be auto-applied.
Blocking
🔧 wrench
- Upstream 3xx becomes a hard 502 — see inline at
crates/trusted-server-core/src/integrations/js_asset_proxy.rs:477 cargo fmtrequired check is failing — see Cross-cutting below- Cloudflare and Spin adapters reject
stream_response— see Cross-cutting below - Branch conflicts with
main— see Cross-cutting below
Non-blocking
🤔 thinking / ♻️ refactor / ⛏ nitpick / 📝 note
- Audit-generated drafts override upstream cache headers for every asset — see inline at
crates/trusted-server-cli/src/commands/audit/mod.rs:444 - Fixed
User-Agentcollapses UA-adaptive vendor bundles — see inline atcrates/trusted-server-core/src/integrations/js_asset_proxy.rs:281 - Integration ID duplicated as a string literal — see inline at
crates/trusted-server-core/src/config.rs:138 - Only
GETis registered for asset paths — see inline atcrates/trusted-server-core/src/integrations/js_asset_proxy.rs:445 X-TS-JS-Asset-Proxymarker is always emitted — see inline atcrates/trusted-server-core/src/integrations/js_asset_proxy.rs:350
Cross-cutting / body-level findings
-
🔧
cargo fmtrequired check is failing — reproduced locally at this head. Threeuselines injs_asset_proxy.rsneed edition-2024 import ordering: line 14 (http::{Method, Request, Response, StatusCode, header}), line 25 (crate::proxy::{ProxyRequestConfig, proxy_request}), and the test import at line 526 (crate::html_processor::{HtmlProcessorConfig, create_html_processor}). A singlecargo fmt --allfixes all three. This is the only failing check and it is branch-protection required. -
🔧 Cloudflare and Spin adapters reject
stream_response, so every proxied asset returns 502 there —build_proxy_configunconditionally sets.with_stream_response()(js_asset_proxy.rs:263). Both adapters treat that flag as an unsupported contract and error out rather than degrade:crates/trusted-server-adapter-cloudflare/src/platform.rs:269— "streaming response bodies are not supported on the Cloudflare Workers runtime"crates/trusted-server-adapter-spin/src/platform.rs:311— "Spin outbound HTTP does not support streaming responses"
proxy_requestsurfaces that as an error, andhandle()maps every error to502withX-TS-Error: js-asset-origin-unreachable— so on those runtimes every configured asset is a hard failure, and the response looks like an origin problem rather than an unsupported platform contract. The Cloudflare guard's own comment ("These fields are only set by asset routes, which are not routed to the Cloudflare adapter today") is no longer true, because core integration routes dispatch on every adapter. The Axum adapter has no guard and simply ignores the flag, so it buffers — a third behaviour. The spec's "No adapter entry-point changes are expected if the existing integration registry dispatch is sufficient" (2026-04-01-js-asset-proxy-design.md:297) needs revisiting.CI does not catch this: the cross-adapter parity suite passes only because its fixture never enables
js_asset_proxy. Whichever way this is resolved — gatestream_responseon adapter capability, make the non-Fastly adapters buffer instead of erroring, or document the integration as Fastly-only and fail config validation elsewhere — a parity or per-adapter test that enables one asset would keep it from regressing. -
🔧 The branch conflicts with
main— GitHub reportsCONFLICTING;git merge-tree origin/main <head>shows content conflicts incrates/trusted-server-core/src/config.rs,crates/trusted-server-core/src/integrations/mod.rs, andtrusted-server.example.toml. Worth flagging the last one specifically:replace_js_asset_proxy_section(audit/mod.rs:567) searches the embedded example config for a literal[integrations.js_asset_proxy]header and returns a hard CLI error if it is missing. If that header is dropped or renamed while resolving the conflict, everyts auditrun fails, not just this integration — the unit test ataudit/mod.rs:997is what guards it. -
👍 Praise — a few things worth calling out: the upstream
Set-Cookieis deliberately dropped and the request-header allowlist is genuinely minimal (build_proxy_config, verified bybuild_proxy_config_forwards_only_asset_header_allowlist); the audit generator emits opaque randomized/assets/<hex>.jspaths fromOsRngrather than mirroring vendor filenames; the precedence tests against the native GPT rewriter cover all three proxy modes; andvalidate_js_asset_proxy_configcorrectly plugs a real hole —IntegrationSettings::get_typedreturns early for explicitly-disabled configs before callingvalidate(), so without this deploy-time check an invalid disabled inventory would ship unvalidated.
CI Status
- cargo fmt: FAIL (required)
- cargo test: PASS (required)
- format-docs: PASS (required)
- format-typescript: PASS (required)
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- cargo test (axum native): PASS
- cargo test (cross-adapter parity): PASS
- cargo test (ts CLI, native): PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- prepare integration artifacts: PASS
- browser integration tests: PASS
- vitest: PASS
- Analyze (rust): PASS
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS
- CodeQL: PASS
6d6f589 to
a82aaf2
Compare
|
@ChristianPavilonis to resolve feedback |
prk-Jr
left a comment
There was a problem hiding this comment.
Summary
Adds a js_asset_proxy integration serving explicitly configured third-party JavaScript from exact first-party paths, plus ts audit generation of disabled-by-default candidate entries. The core mechanics are sound: exact-match routing with no prefix or wildcard, follow_redirects = false, a three-header request allowlist with copy_request_headers = false and a fixed User-Agent, no EC forwarding, and a response rebuilt from scratch so Set-Cookie and every other upstream header outside a small allowlist are dropped. I specifically probed SSRF, request/response header leakage, cross-adapter streaming parity, and route shadowing, and found no defect in any of them. One security-hardening gap and three smaller items below.
2 of the inline comments below carry a one-click GitHub
suggestion— use Commit suggestion (or Add suggestion to batch) to apply them as commits on the PR branch. The remaining comments describe the fix in prose because the change spans more than one range in the file or needs an accompanying test change.
Blocking
🔧 wrench
- Proxied third-party bytes are served from the publisher origin with no content-type protection — see inline at
crates/trusted-server-core/src/integrations/js_asset_proxy.rs:378-381
Non-blocking
♻️ refactor
ETag/Last-Modifiedadvertised downstream but conditional requests never forwarded upstream — see inline atcrates/trusted-server-core/src/integrations/js_asset_proxy.rs:288-296
🤔 thinking
Cache-Control: publicon a route that can also mint an EC cookie — see inline atcrates/trusted-server-core/src/integrations/js_asset_proxy.rs:415-420
⛏ nitpick
- Example config's sample asset is
proxy = "enabled", so one edit activates it — see inline attrusted-server.example.toml:124
Cross-cutting / body-level findings
-
📝 The verification commands in the PR description are not this workspace's gates. The body lists
cargo clippy --workspace --all-targets --all-features -- -D warningsandcargo test --workspace. PerCLAUDE.md, a workspace-wide clippy trips the Cloudflare adapter's non-wasm32guard; run against this head it exits 101 atcrates/trusted-server-adapter-cloudflare/src/lib.rs:5, so it cannot have passed as written. No quality problem behind it — I ran the real target-matched gates against8fc2477and all pass:cargo fmt --all -- --check, all sixclippy-*aliases, all fourtest-*aliases, and the cross-adapter parity suite. Please update the description to theCLAUDE.mdgate list. -
👍 The
supports_streaming_responses()gating inproxy.rsfixes a latent cross-adapter break. Onmain,handle_asset_proxy_requestsetwith_stream_response()unconditionally (proxy.rs:1196) — a contract both the Cloudflare (adapter-cloudflare/src/platform.rs:307) and Spin (adapter-spin/src/platform.rs:318) clients hard-reject. Gating it plus the buffered fallback (proxy.rs:1211-1228), covered by new tests atproxy.rs:4265andproxy.rs:4334, is a real fix beyond this PR's stated scope. Worth calling out in the description since it changes shared proxy behaviour.
CI Status
- cargo fmt: PASS (required)
- cargo test: PASS (required)
- format-typescript: PASS (required)
- format-docs: PASS (required)
- cargo test (axum native): PASS
- cargo test (ts CLI, native): PASS
- cargo test (cross-adapter parity): PASS
- cargo check (cloudflare native + wasm32-unknown-unknown): PASS
- cargo check/build/test (spin native + wasm32-wasip1): PASS
- vitest: PASS
- integration tests: PASS
- integration tests (Fastly EC lifecycle): PASS
- browser integration tests: PASS
- prepare integration artifacts: PASS
- CodeQL: PASS
- Analyze (rust): PASS
- Analyze (actions): PASS
- Analyze (javascript-typescript): PASS (reported twice, from two workflow runs)
…xy-spec # Conflicts: # trusted-server.example.toml
Summary
js_asset_proxyintegration<script src>rewriting, disabled assets, and blocked script removalapplication/javascript; charset=utf-8withX-Content-Type-Options: nosniffts auditRelated
Closes #762
Verification
cargo fmt --all -- --checkcargo clippy-fastly && cargo clippy-axum && cargo clippy-cloudflare && cargo clippy-cloudflare-wasm && cargo clippy-spin-native && cargo clippy-spin-wasmcargo test-fastly && cargo test-axum && cargo test-cloudflare && cargo test-spin./scripts/test-cli.shcargo test --manifest-path crates/trusted-server-integration-tests/Cargo.toml --test paritycd crates/trusted-server-js/lib && npx vitest runcd crates/trusted-server-js/lib && npm run formatcd docs && npm run format