fix(security): close 6 parity-drift findings from the post-#197 audit#198
Merged
Conversation
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
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
fetch()now usesredirect: 'error'so a302 → 169.254.169.254can't bypass the SSRF guard (which validated only the first hop). Python was safe (httpx follow_redirects=False).safe_path_segment/safePathSegmentfolds POSIX and Windows separators + neutralises.., so a forged carrier call id can't escape the recording/call-log dir on Windows.tool_executor.py,consult.py); TS rejects an oversizedContent-Lengthup front, timeout as the chunked backstop.Plus a consistency fix: the Python
on_messageWebSocket guard now routes through the sharedgetpatter/ssrf.pyso it folds non-canonical IP spellings like its HTTP sibling.Implementation notes
redirect: 'error'added to all 6 guarded sinks (llm-loop.ts,handler-utils.ts,consult.ts×3,remote-message.ts).client.post→client.stream(...)+aiter_bytes()with an in-loop byte cap (and aContent-Lengthpre-check). The 3test_new_features.pyexecutor tests that mocked.postwere updated to mock.stream. TS keepsresp.json()/text()(so existing fetch mocks stay valid) but adds a defensiveresp.headers?.get('content-length')pre-check — a deliberate lower-risk choice on the TS hot path, which is already time-bounded byAbortSignal.timeout; Python (no timeout backstop) gets the full streaming bound.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
pytest tests/— 2890 passed, 8 skipped, 2 xfailed (+14 new intests/security/test_hardening.py).npm test— 2301 passed, 9 skipped (+12 newSEC-6/7/8intests/security/security.test.ts) +npm run lintclean.main; no version bump (all three version files stay0.6.9).Docs updates
CHANGELOG.md### Securityentry added. No public-API doc surface changed.