Skip to content

fix(security): scs-legacy refuses acs_token over remote plaintext http (H4) - #13

Merged
willamhou merged 1 commit into
masterfrom
sec/pr-f-scs-legacy-transport
Jun 24, 2026
Merged

fix(security): scs-legacy refuses acs_token over remote plaintext http (H4)#13
willamhou merged 1 commit into
masterfrom
sec/pr-f-scs-legacy-transport

Conversation

@willamhou

Copy link
Copy Markdown
Owner

PR-F of the security-hardening plan. Finding H4: the legacy SCS adapter sends an acs_token in the GET query / POST body (an upstream-mandated contract), and the default base URL is plaintext http://.

Change

  • Refuse to send the token over plaintext http to a non-loopback host — it would leak through access logs, proxies, and caches. https and loopback http are allowed; RELAIS_SCS_ALLOW_INSECURE=1 (or true) is an explicit per-area override for trusted private networks.
  • Loopback is decided by real IpAddr::is_loopback() (127.0.0.0/8, ::1), so a lookalike like 127.0.0.1.evil.com is correctly not loopback. The scheme is lowercased so HTTP:// can't bypass; bracketed IPv6 and user:pass@host userinfo are handled.
  • Enforced at the start of exec (AdapterError::Unsupported → 400); with_base_url warns at construction if the configured URL would be refused.
  • Defense in depth: redact the acs_token from any upstream error body before it can reach logs/audit/callers.

The token transport mechanism (query for GET) is unchanged — it is the upstream contract; this PR governs where it's allowed to go and scrubs it from error text.

Tests

  • check_transport: https/loopback-http allowed; remote-http blocked unless override; lookalike + userinfo + uppercase-scheme rejected; 127/8 + [::1] accepted.
  • cargo test --workspace green; clippy clean.

Codex-reviewed; the flagged case-sensitive-scheme BLOCKER, error-body token leak, and env-format consistency are all fixed.

🤖 Generated with Claude Code

…p (H4)

PR-F of the security-hardening plan.

- Refuse to send the acs_token over plaintext http to a non-loopback host
  (it leaks via access logs/proxies/caches). https and loopback http are
  allowed; RELAIS_SCS_ALLOW_INSECURE=1 (or =true) is an explicit override.
  Loopback is decided by real IpAddr::is_loopback() (127.0.0.0/8, ::1) so a
  lookalike like 127.0.0.1.evil.com is correctly NOT loopback; scheme is
  lowercased so HTTP:// can't bypass; userinfo can't smuggle a host.
- Enforced at exec start (AdapterError::Unsupported); with_base_url warns at
  construction.
- Redact the acs_token from any upstream error body before it reaches
  logs/audit/callers (defense in depth).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@willamhou
willamhou merged commit 7d69ea1 into master Jun 24, 2026
2 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7546da6dcd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +233 to +234
let authority = rest.split(['/', '?']).next().unwrap_or("");
let hostport = authority.rsplit('@').next().unwrap_or(authority);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject fragments before extracting the host

When SCS_LEGACY_BASE_URL contains a fragment with an @, for example http://scs.example.com#@127.0.0.1, URL parsing still targets scs.example.com because the authority ends before #, but this manual split keeps the fragment in authority and rsplit('@') treats 127.0.0.1 as the host. That lets the new guard allow a remote plaintext HTTP endpoint and send the injected acs_token; parse with a URL parser or stop the authority at # before stripping userinfo.

Useful? React with 👍 / 👎.

Comment on lines +74 to +75
"SCS legacy base_url '{base_url}' is plaintext http to a non-loopback host; \
acs_token requests will be refused unless RELAIS_SCS_ALLOW_INSECURE=1 — prefer https"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Sanitize userinfo before logging base URLs

If a remote plaintext base URL includes userinfo, such as http://user:pass@scs.example.com, this new warning logs the full base_url verbatim before the request is refused. Since the transport parser explicitly accepts userinfo-shaped URLs, this can leak embedded credentials into tracing logs; log only a sanitized scheme/host or strip userinfo first.

Useful? React with 👍 / 👎.

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