Files
alkhttp/tasks/adapters/review-001-forward-url-safety.md
T
glm-5.3-flash 12b35e2c5f docs(tasks): decompose review 001 remediation Units 1-5 into taskgraph tasks
21 review-001 tasks across server/adapters/client/gateway/websocket/infra,
chunked from the 7-unit remediation plan in
docs/reviews/001-initial-implementation-review.md.

- Scope split by mechanism, not one-per-finding: 15 tasks in generation 1
  (parallelizable), 6 sequenced after their file-sharing precursors
- Deliberately deferred until dependent fixes land: projection/doc
  fidelity partial (Unit 6 beyond dependency hygiene), coverage backfills
  (COV-01..07 via in-task acceptance for forward.rs), and per-finding
  minors (OAI-06/07, HY-02/04/06/10/11, CON-08)
- Cross-crate WS-12 (alkcall demux 4 GiB discard alloc) noted for filing
  in alkcall, not here

taskgraph: validate clean, no cycles, 6 generations
2026-08-29 07:10:15 +00:00

2.9 KiB

id, name, status, depends_on, scope, risk, impact, level, tags
id name status depends_on scope risk impact level tags
review-001-forward-url-safety Safe outbound URL construction — encoding, base-path, host validation (FWD-01, FWD-02) pending
narrow high component implementation
adapters
review-001
security

Description

Review 001 findings FWD-01 + FWD-02, both empirically verified against src/adapters/forward.rs and both security-bearing with the operation's injected credentials attached:

  • FWD-01: path-parameter values are substituted raw (forward.rs:56-71, 133-141). Url::join normalizes .., so {owner} = "../../admin" escapes a path-scoped prefix (cross-tenant IDOR); ? and # in a value split/inject into the URL; values containing a later placeholder get re-expanded by the iterative substitution. Fix: percent-encode each segment (utf8_percent_encode with a path-segment set), reject/encode ?/#, and render the template in a single pass (not iterative replace). The query path is already correctly encoded via query_pairs_mut — keep that shape.
  • FWD-02: Url::join resolves against the base directory, so base_url = "https://api.openai.com/v1" + /chat/completions silently drops /v1 (forward.rs:74-78); every test uses origin-only base URLs so the suite can't see it. Worse: a path key that is an absolute URL replaces scheme+host entirely (verified — reqwest only rejects non-http(s) schemes), and credential injection happens after URL construction, so a spec-controlled absolute path sends the namespace's credentials to an arbitrary host (SSRF). Specs are assembly-layer-supplied (trusted per ADR-066) but nothing enforces that boundary. Fix: append to the base path (not origin), require the joined URL to keep the base host (fail loudly on host change), and require https by default (explicit opt-out for http).

Acceptance Criteria

  • Traversal test: {owner} = "../../admin" cannot escape the template path (segment encoded or rejected)
  • ?/#/later-placeholder-in-value tested (encoded or rejected, never URL-structural)
  • Base URL with a path prefix keeps the prefix (…/v1 + /chat/completions…/v1/chat/completions, test)
  • Absolute-URL path template is rejected loudly; joined host ≠ base host is rejected; http base refused unless opted out (tests)
  • Rendering is single-pass (a rendered value is never re-substituted)
  • Both from_openapi and from_jsonschema paths covered (they share forward.rs)
  • cargo test and cargo clippy --all-targets -- -D warnings pass

References

  • docs/reviews/001-initial-implementation-review.md (Part D, FWD-01, FWD-02)
  • docs/architecture/decisions/066-from-jsonschema-as-http-adapter.md

Notes

Agent fills during implementation. One of the two gate tasks for any deployment-facing milestone (with review-001-client-timeout-retry).

Summary

Filled on completion.