Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(agents): SSRF guard for playbook http_request and notify (P2-W7)#120

Merged
beenuar merged 2 commits into
mainfrom
fix/playbook-ssrf
May 14, 2026
Merged

fix(agents): SSRF guard for playbook http_request and notify (P2-W7)#120
beenuar merged 2 commits into
mainfrom
fix/playbook-ssrf

Conversation

@beenuar
Copy link
Copy Markdown
Owner

@beenuar beenuar commented May 14, 2026

Summary

Adds a single SSRF chokepoint (services/agents/app/playbook/ssrf_guard.py) and routes both _handle_http and _handle_notify through validate_outbound_url before any socket opens. Without this, playbook authors could point notify (channel=webhook) or http_request at http://169.254.169.254/..., http://127.0.0.1:6379, or any RFC1918 address — turning a low-privilege playbook write into a cloud-metadata exfil and internal-network pivot.

Defaults (overridable per-deployment):

  • only http:// / https:// allowed (AISOC_SSRF_ALLOWED_SCHEMES)
  • URLs that embed credentials are rejected
  • every resolved address (not just the first) must be globally routable: loopback, RFC1918, link-local, multicast, reserved are blocked
  • cloud-metadata endpoints (169.254.169.254, fd00:ec2::254, metadata.google.internal, metadata.azure.com, …) are blocked even when AISOC_SSRF_ALLOW_PRIVATE=true
  • extra deny list via AISOC_SSRF_EXTRA_BLOCKED_HOSTS

Why this PR is safe to merge

  • New module + integration calls only; no existing playbook behaviour changes for non-private destinations.
  • 77 new tests pass (pytest services/agents/tests/test_playbook_ssrf_guard.py).
  • Failures elsewhere in the agents suite (test_llm_resolver.py) reproduce on main and are unrelated.

Files

  • services/agents/app/playbook/ssrf_guard.py (new)
  • services/agents/app/playbook/engine.py (route both handlers through guard)
  • services/agents/tests/test_playbook_ssrf_guard.py (new, 77 cases)
  • apps/docs/docs/deployment/env-vars.md (document three new env vars)
  • apps/docs/docs/operations/security.md (operator-facing explanation)

Test plan

  • pytest services/agents/tests/test_playbook_ssrf_guard.py — 77 passed
  • Reviewer: confirm AISOC_SSRF_ALLOW_PRIVATE is not set in production manifests
  • Reviewer: confirm any internal-webhook integrations have been allow-listed via AISOC_SSRF_EXTRA_BLOCKED_HOSTS review or AISOC_SSRF_ALLOW_PRIVATE on agents only

Refs: P2-W7

Made with Cursor

Playbook authors can supply arbitrary URLs to `notify` (channel=webhook)
and `http_request` steps. Before this change the agents service would
happily call `http://169.254.169.254/...`, `http://127.0.0.1:6379`, or any
RFC1918 address — turning a low-privilege playbook write into an internal-
network pivot and a cloud-metadata exfiltration primitive.

This batch adds a single chokepoint, `app.playbook.ssrf_guard`, and routes
both `_handle_http` and `_handle_notify` through `validate_outbound_url`
before any socket is opened. The guard:

  - allows only `http`/`https` (override via AISOC_SSRF_ALLOWED_SCHEMES)
  - rejects URLs that embed credentials (`https://user:pass@host`)
  - resolves the host with getaddrinfo and rejects loopback, RFC1918,
    link-local, multicast, and IETF-reserved addresses — every resolved
    record must pass, not just the first
  - blocks the cloud metadata endpoints (169.254.169.254, fd00:ec2::254,
    metadata.google.internal, metadata.azure.com, …) unconditionally,
    even when AISOC_SSRF_ALLOW_PRIVATE=true
  - supports an extra deny list via AISOC_SSRF_EXTRA_BLOCKED_HOSTS

Operators with a legitimate need to call private webhooks (Slack on a
private network, internal Jira) can flip AISOC_SSRF_ALLOW_PRIVATE=true
on the agents service while keeping the metadata block list intact.

Tests
-----
- 77 new unit tests covering scheme allow/deny, credential rejection,
  loopback / RFC1918 / link-local / multicast / reserved blocks, IPv6
  loopback and link-local, metadata endpoints across clouds, extra deny
  list overrides, getaddrinfo failure handling, and integration with the
  two playbook handlers.

Docs
----
- apps/docs/docs/deployment/env-vars.md: documents the three new env vars
  (AISOC_SSRF_ALLOWED_SCHEMES, AISOC_SSRF_ALLOW_PRIVATE,
  AISOC_SSRF_EXTRA_BLOCKED_HOSTS) under the Agents service section.
- apps/docs/docs/operations/security.md: explains the guard, its default
  policy, when to enable AISOC_SSRF_ALLOW_PRIVATE, and where the chokepoint
  lives for future action handlers.

Refs: P2-W7
import socket
from urllib.parse import urlsplit

logger = logging.getLogger("aisoc.playbook.ssrf_guard")
Restores 'Python — Lint & Type-check' to green by satisfying the
repo-wide ruff lint + format gates that run across all services in CI.
@beenuar beenuar marked this pull request as ready for review May 14, 2026 10:59
@beenuar beenuar merged commit f4782c5 into main May 14, 2026
23 checks passed
@beenuar beenuar deleted the fix/playbook-ssrf branch May 14, 2026 10:59
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.

2 participants