Skip to content

Reject a non-string target_agent instead of raising TypeError - #107

Open
LibertasSpZ wants to merge 1 commit into
anthropics:mainfrom
LibertasSpZ:fix/orchestrate-unhashable-target
Open

Reject a non-string target_agent instead of raising TypeError#107
LibertasSpZ wants to merge 1 commit into
anthropics:mainfrom
LibertasSpZ:fix/orchestrate-unhashable-target

Conversation

@LibertasSpZ

Copy link
Copy Markdown

Problem

extract_handoff checks the handoff target against the allowlist with

if target not in ALLOWED_TARGETS:

Set membership hashes the value. The blob is parsed out of the orchestrator's own text output, which is downstream of untrusted-document readers — the threat model the module docstring already sets out — so whoever controls a processed document controls the JSON type of every field in it, and a JSON array or object is unhashable:

>>> extract_handoff('{"type":"handoff_request",'
...                 '"target_agent":["reg-monitor"],"payload":{}}')
TypeError: unhashable type: 'list'

run() calls extract_handoff inside the stream loop with no exception handling, so this stops the orchestrator rather than logging a rejected handoff — a denial of service reachable from document text, through the gate that exists to stop document text. It fails stopped rather than open, so it is not an allowlist bypass.

Every other field is type-checked by jsonschema a few lines further down. target_agent is read before that validation runs, which is why it is the only field with this exposure.

Fix

One line, plus a comment so the isinstance is not tidied away later:

if not isinstance(target, str) or target not in ALLOWED_TARGETS:

Rejection now takes the normal path and is audited under the existing target_not_allowlisted reason — no new code path, no new reason string.

Verified against the patched script: ["reg-monitor"] and {"slug": "reg-monitor"} are now rejected and logged, while "reg-monitor" still approves and "nope", null and 7 reject as before.

Relationship to #106

That PR marks this defect xfail(strict=True) at
tests/test_orchestrate_handoff.py::test_an_unhashable_target_is_rejected_rather_than_raising,
asserting the behaviour this change introduces. If this lands first, the marker has to be dropped in the same merge or the suite goes red on XPASS(strict). Happy to add that removal to this PR if you would prefer to take the fix ahead of the tests.

extract_handoff checks the handoff target with `target not in
ALLOWED_TARGETS`, which hashes the value. The blob is parsed out of the
orchestrator's own text output, which is downstream of untrusted-document
readers, so whoever controls a processed document controls the JSON type
of every field in it — and a JSON array or object is unhashable:

    >>> extract_handoff('{"type":"handoff_request",'
    ...                 '"target_agent":["reg-monitor"],"payload":{}}')
    TypeError: unhashable type: 'list'

run() calls extract_handoff inside the stream loop with no handler, so
this stops the orchestrator rather than logging a rejected handoff — a
denial of service reachable from document text, through the gate that
exists to stop document text. Every other field is type-checked by
jsonschema, but target_agent is read before that validation runs.

The isinstance check goes first so membership is only ever attempted on a
string. Rejection now takes the normal path and is audited under the
existing target_not_allowlisted reason.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@LibertasSpZ

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@LibertasSpZ LibertasSpZ changed the title fix/orchestrate-unhashable-target Reject a non-string target_agent instead of raising TypeError Aug 2, 2026
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