Skip to content

security: fix SSRF redirect bypass, timing-safe OAuth, atomic credentials#200

Open
eesb99 wants to merge 5 commits intoCraftOS-dev:V1.2.3from
eesb99:security/v1.2.3-incremental-fixes
Open

security: fix SSRF redirect bypass, timing-safe OAuth, atomic credentials#200
eesb99 wants to merge 5 commits intoCraftOS-dev:V1.2.3from
eesb99:security/v1.2.3-incremental-fixes

Conversation

@eesb99
Copy link
Copy Markdown
Contributor

@eesb99 eesb99 commented Apr 16, 2026

Summary

Incremental security hardening on top of PR #195 (already merged to V1.2.3). These fixes address 3 remaining vulnerabilities:

  • SSRF redirect bypass (http_request.py): Disable automatic redirects and manually follow each hop with SSRF validation, preventing attackers from redirecting to internal IPs. Also fix _socket.gaierror reference bug and switch to fail-closed on DNS errors.
  • OAuth CSRF timing attack (oauth_server.py): Use hmac.compare_digest() for constant-time state comparison. Add explicit 3-way state handling: no state expected (warn + allow), state expected but missing from callback (reject), mismatch (reject).
  • Credential file permission race (credentials.py): Replace write-then-chmod with atomic os.open() using 0o600 mode to eliminate the TOCTOU race window.

Files changed (3)

File Fix
app/data/action/http_request.py SSRF redirect validation per hop, fail-closed DNS, _is_url_ssrf_safe() helper
agent_core/core/credentials/oauth_server.py Timing-safe state comparison + 3-way CSRF handling
app/external_comms/credentials.py Atomic file permissions via os.open()

Test plan

  • Verify SSRF protection blocks redirect chains to private IPs (e.g., http://attacker.com -> http://169.254.169.254)
  • Verify unresolvable hostnames return error (fail closed) instead of proceeding
  • Verify OAuth flow still works end-to-end with state parameter
  • Verify credential files are created with 0600 permissions
  • Run existing test suite

🤖 Generated with Claude Code

eesb99 and others added 2 commits April 16, 2026 13:11
…ial perms

- Fix socket.gaierror -> _socket.gaierror reference bug that silently
  disabled SSRF protection when DNS resolution failed
- Use hmac.compare_digest() for OAuth state comparison to prevent
  timing side-channel attacks
- Use os.open() with 0o600 mode for atomic credential file permissions
  instead of write-then-chmod race

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rcement

SSRF (http_request.py):
- Extract _is_url_ssrf_safe() helper for reuse across redirect hops
- Disable allow_redirects in requests lib, manually follow redirects
  with SSRF validation on each hop (closes redirect bypass)
- Remove fail-open except Exception: pass -- DNS failures now block
  the request instead of silently allowing it
- Unresolvable hostnames return an error (fail closed)

OAuth CSRF (oauth_server.py):
- Explicit 3-way state handling: no state expected (warn + allow),
  state expected but missing from callback (reject), mismatch (reject)
- Previously silently skipped validation when state was absent

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
eesb99 and others added 3 commits April 16, 2026 13:26
…l perms

Review-driven fixes:
- SSRF: add scheme whitelist (http/https only), strip auth headers on
  cross-origin redirects, strip body on GET downgrade, preserve method
  for 307/308 per RFC 7231
- OAuth: use `is None` checks to prevent empty-string state bypass,
  move `import hmac` to module top-level
- Credentials: add os.fchmod() to enforce 0o600 on pre-existing files
- Python compat: quote `str | None` annotation for 3.9 support,
  promote _BLOCKED_HOSTS to frozenset

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace 6 repeated error-response dicts with _error() helper
- Move urljoin import out of redirect loop, combine with urlparse

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
File was tracked despite .gitignore rule. Removed from index
so credentials are no longer committed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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