Skip to content

fix(security): close 6 parity-drift findings from the post-#197 audit#198

Merged
nicolotognoni merged 1 commit into
mainfrom
fix/security-hardening
Jun 30, 2026
Merged

fix(security): close 6 parity-drift findings from the post-#197 audit#198
nicolotognoni merged 1 commit into
mainfrom
fix/security-hardening

Conversation

@nicolotognoni

Copy link
Copy Markdown
Collaborator

Summary

Closes the 6 confirmed findings from the post-#197 security audit. Five are cross-language parity drift — a guard present in one SDK but missing or weaker in the other (the same genre as #195/#197). No version bump (deferred to a release PR).

Findings fixed

Sev Fix SDK
HIGH Outbound fetch() now uses redirect: 'error' so a 302 → 169.254.169.254 can't bypass the SSRF guard (which validated only the first hop). Python was safe (httpx follow_redirects=False). TS
MED Caller DTMF no longer logged at INFO (PIN/card/SSN) — dropped to DEBUG with no value, matching Python. TS
LOW Call-id path traversal: new shared safe_path_segment/safePathSegment folds POSIX and Windows separators + neutralises .., so a forged carrier call id can't escape the recording/call-log dir on Windows. both
LOW Tool/consult response size cap enforced during the read — Python streams + bounds (tool_executor.py, consult.py); TS rejects an oversized Content-Length up front, timeout as the chunked backstop. both
LOW Transfer-destination phone numbers masked in logs (last-4), parity with Python. TS
LOW Variable sanitiser strips Unicode line separators (U+0085/U+2028/U+2029), closing a line-break prompt-injection gap. both

Plus a consistency fix: the Python on_message WebSocket guard now routes through the shared getpatter/ssrf.py so it folds non-canonical IP spellings like its HTTP sibling.

Implementation notes

  • Redirect (HIGH): redirect: 'error' added to all 6 guarded sinks (llm-loop.ts, handler-utils.ts, consult.ts ×3, remote-message.ts).
  • Response cap: Python switches client.postclient.stream(...) + aiter_bytes() with an in-loop byte cap (and a Content-Length pre-check). The 3 test_new_features.py executor tests that mocked .post were updated to mock .stream. TS keeps resp.json()/text() (so existing fetch mocks stay valid) but adds a defensive resp.headers?.get('content-length') pre-check — a deliberate lower-risk choice on the TS hot path, which is already time-bounded by AbortSignal.timeout; Python (no timeout backstop) gets the full streaming bound.
  • Path/sanitiser helpers are shared and mirrored across SDKs to prevent future drift.

Breaking change?

No. These reject inputs that were always meant to be blocked or move PII off INFO logs — no public API change. redirect: 'error' matches Python's long-standing default.

Test plan

  • Python pytest tests/2890 passed, 8 skipped, 2 xfailed (+14 new in tests/security/test_hardening.py).
  • TypeScript npm test2301 passed, 9 skipped (+12 new SEC-6/7/8 in tests/security/security.test.ts) + npm run lint clean.
  • No new ruff findings vs main; no version bump (all three version files stay 0.6.9).
  • E2E smoke with a real call before release (non-blocking).

Docs updates

CHANGELOG.md ### Security entry added. No public-API doc surface changed.

A deep audit after #197 surfaced six confirmed issues, five of them a guard
present in one SDK but missing/weaker in the other.

- (TS) Outbound fetches now fail closed on redirects (redirect: 'error').
  Every guarded fetch (tool webhook, on_message, consult, MCP) followed 3xx by
  default, so an attacker-influenced 302 -> 169.254.169.254 bypassed the SSRF
  guard (validated only the first hop). Python was never affected (httpx
  follow_redirects=False). llm-loop.ts, handler-utils.ts, consult.ts,
  remote-message.ts.
- (TS) Caller DTMF is no longer logged at INFO (PINs/cards/SSNs); dropped to
  DEBUG with no value, matching Python. server.ts.
- Call-id path traversal closed (both SDKs): new shared safe_path_segment /
  safePathSegment folds POSIX and Windows separators and neutralises '..' so a
  forged carrier call id can't escape the recording/call-log dir on Windows.
- Tool/consult response size cap enforced during the read: Python streams +
  bounds (tool_executor.py, consult.py); TS rejects an oversized Content-Length
  up front, with the per-request timeout as the chunked-body backstop.
- (TS) Transfer-destination phone numbers masked in logs (parity with Python).
- Variable sanitiser strips Unicode line separators (U+0085/U+2028/U+2029),
  closing a line-break prompt-injection gap (both SDKs).

Plus a consistency fix: the Python on_message WebSocket guard now routes through
the shared getpatter/ssrf.py so it folds non-canonical IP spellings like its
HTTP sibling.

Tests: +14 Python (tests/security/test_hardening.py), +12 TS (security.test.ts).
Full suites green (Python 2890, TS 2301). No version bump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D52f6rBsvzicXg45C6MKVM
@nicolotognoni nicolotognoni merged commit a0eaaa0 into main Jun 30, 2026
10 checks passed
@nicolotognoni nicolotognoni mentioned this pull request Jun 30, 2026
3 tasks
nicolotognoni added a commit that referenced this pull request Jun 30, 2026
Minor bump (new backward-compatible public API accumulated since 0.6.9).
Datestamps the Unreleased CHANGELOG section as 0.7.0 (2026-06-30) and opens a
fresh Unreleased. All three version files move together (getpatter __init__.py,
pyproject.toml, package.json).

Highlights since 0.6.9:
- Inworld integration: corrected TTS pricing + providers.inworld() + InworldLLM
  Realtime Router preset (220+ models) (#199).
- Security: SSRF guard folds non-canonical IP encodings (#197); 6 parity-drift
  hardening fixes — TS redirect SSRF, DTMF/phone log PII, path traversal,
  response cap, Unicode-newline sanitizer (#198).
- Pipeline: fixed false barge-in that self-interrupted the user-turn LLM
  (agent went mute) (#200).
- Audio/Skills/Memory: rate-aware resampler + HPF/AGC front-end, user-supplied
  agent skills, token-aware pipeline compaction (#196).


Claude-Session: https://claude.ai/code/session_01D52f6rBsvzicXg45C6MKVM

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot deleted the fix/security-hardening branch July 1, 2026 08:15
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