Skip to content

fix(security): 0.40.3 deferred hardening - out-of-process code, SSRF pinning, MCP scoping#274

Closed
gizmax wants to merge 1 commit into
fix/audit-sweep-0.40.2from
fix/audit-deferred-hardening
Closed

fix(security): 0.40.3 deferred hardening - out-of-process code, SSRF pinning, MCP scoping#274
gizmax wants to merge 1 commit into
fix/audit-sweep-0.40.2from
fix/audit-deferred-hardening

Conversation

@gizmax

@gizmax gizmax commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Follow-up to #273 (0.40.2). Finishes the three harder, previously-deferred audit items. Base is the 0.40.2 branch so this diff shows only the deferred work; merge after #273.

What's in this PR

  • Code steps run out-of-process by default. Validated code executes in a separate Python subprocess (python -m sandcastle.engine.code_subprocess_runner) with a secret-free env and POSIX CPU/address-space limits; the same AST validator, blocklist, and secret-free helpers are re-run in the child (imported, not forked). The child is really killed on timeout - the in-process thread path could not be. Gated by CODE_STEPS_OUT_OF_PROCESS (default true); spawn/serialization failures fall back to in-process so a step is never lost. Parent still validates before spawning.
  • HTTP-step SSRF IP-pinning. Pre-flight resolves once, validates every IP against the dispatcher blocked-ranges, and a custom httpx.AsyncHTTPTransport dials the validated IP while preserving Host + TLS SNI. Closes the DNS-rebind TOCTOU. gaierror still swallowed and no pin applied then, so mocked/non-resolvable test hosts behave as before.
  • Memory MCP per-tenant scope. MEMORY_MCP_SCOPE_PREFIX enforced on add/search/list_memories/forget; forget requires + validates the owning user_id. Unset = today's behavior.

Testing (reviewed line-by-line by lead + independently re-run)

  • Deferred modules: test_code_subprocess_runner, test_http_ssrf_pinning, test_memory_mcp_server, test_security_pentest, test_code_steps_admin_gate, test_step_types -> 325 passed.
  • Code-step regression (subprocess default): executor deep/edge + template blocklist -> 456 passed. Helm 11 passed.

Notes / residual limitations (reviewed, acceptable)

  • Perf: out-of-process adds ~170ms per code step (measured) because the child imports the engine for the shared validators. Tolerable and toggleable; a follow-up could extract the validators to a light module to cut child startup. Loop-heavy workflows may prefer CODE_STEPS_OUT_OF_PROCESS=false.
  • Memory forget: mem0 deletes by id with no owner-by-id lookup, so forget validates the supplied user_id is in-prefix but cannot cross-check that the memory_id truly belongs to it. Safe for per-tenant (one prefix per token) deployments; a stronger check needs an owner-resolve primitive in memory.py.

@gizmax
gizmax force-pushed the fix/audit-deferred-hardening branch 2 times, most recently from b5ad2dc to 9aef520 Compare July 11, 2026 21:46
…pinning, MCP scoping

Finishes the three deferred items from the 0.40.2 audit sweep.

- Code steps run out-of-process by default (CODE_STEPS_OUT_OF_PROCESS): a
  separate Python subprocess with a secret-free env and POSIX CPU/AS limits,
  really killed on timeout. Same AST/blocklist/helpers reused in the child.
  Infra failures fall back to the in-process path.
- HTTP steps pin the validated IP at connect time via a custom httpx transport
  that preserves Host + TLS SNI, closing the DNS-rebind TOCTOU. Mocked and
  non-resolvable hosts unaffected.
- Memory MCP enforces MEMORY_MCP_SCOPE_PREFIX on every tool call; forget
  requires and validates the owning user_id against the prefix.
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