Tighten handoff param patterns to block prompt injection via spaces
Param values for matter_id and clause are interpolated directly into the steering-prompt templates. Their patterns previously permitted spaces, which would let a hostile document smuggle a natural-language sentence into the prompt through a field that looks like an ID. Restrict both to slug shape (no spaces); descriptive context belongs in the note/event fields, which are never interpolated and are wrapped in the data frame. Also render templates via format_map with an empty-string default so an optional param the template references (e.g. playbook_monitor's clause) degrades gracefully instead of raising KeyError, and ignore __pycache__.
This commit is contained in:
parent
d541734b08
commit
f7ccda739f
2 changed files with 18 additions and 3 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -3,3 +3,5 @@
|
||||||
/logs/
|
/logs/
|
||||||
/outputs/
|
/outputs/
|
||||||
/.claude/
|
/.claude/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,13 @@ ALLOWED_TARGETS = {
|
||||||
# Closed schema of permitted handoff intents. Parameters are typed and
|
# Closed schema of permitted handoff intents. Parameters are typed and
|
||||||
# pattern-constrained. The orchestrator builds the steering prompt from a
|
# pattern-constrained. The orchestrator builds the steering prompt from a
|
||||||
# per-intent template below — untrusted free text never becomes the prompt.
|
# per-intent template below — untrusted free text never becomes the prompt.
|
||||||
|
#
|
||||||
|
# Pattern rule: parameters that are interpolated into HANDOFF_TEMPLATES must
|
||||||
|
# stay slug-shaped — no spaces. A space-permitting pattern lets a hostile
|
||||||
|
# document smuggle a natural-language sentence into the steering prompt
|
||||||
|
# through a field that looks like an ID. Descriptive context belongs in the
|
||||||
|
# `note`/`event` fields, which are never interpolated and are wrapped in the
|
||||||
|
# <agent-handoff> data frame before reaching the model.
|
||||||
HANDOFF_INTENTS: dict[str, dict] = {
|
HANDOFF_INTENTS: dict[str, dict] = {
|
||||||
"slack_send_message": {
|
"slack_send_message": {
|
||||||
"required": ["channel", "report_path"],
|
"required": ["channel", "report_path"],
|
||||||
|
|
@ -79,7 +86,7 @@ HANDOFF_INTENTS: dict[str, dict] = {
|
||||||
"required": ["matter_id"],
|
"required": ["matter_id"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"matter_id": {"type": "string", "maxLength": 64,
|
"matter_id": {"type": "string", "maxLength": 64,
|
||||||
"pattern": r"^[A-Za-z0-9 ._/:#-]+$"},
|
"pattern": r"^[A-Za-z0-9._/:#-]+$"},
|
||||||
"note": {"type": "string", "maxLength": 500},
|
"note": {"type": "string", "maxLength": 500},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -87,7 +94,7 @@ HANDOFF_INTENTS: dict[str, dict] = {
|
||||||
"required": [],
|
"required": [],
|
||||||
"properties": {
|
"properties": {
|
||||||
"clause": {"type": "string", "maxLength": 80,
|
"clause": {"type": "string", "maxLength": 80,
|
||||||
"pattern": r"^[A-Za-z0-9 ._/-]+$"},
|
"pattern": r"^[A-Za-z0-9._/-]+$"},
|
||||||
"note": {"type": "string", "maxLength": 500},
|
"note": {"type": "string", "maxLength": 500},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -271,7 +278,13 @@ def extract_handoff(text: str, source_agent: str = "unknown") -> dict | None:
|
||||||
sanitized_event = sanitize_event(raw_event) if raw_event else ""
|
sanitized_event = sanitize_event(raw_event) if raw_event else ""
|
||||||
|
|
||||||
# Build the steering input from the typed template — NOT from free text.
|
# Build the steering input from the typed template — NOT from free text.
|
||||||
steering_input = HANDOFF_TEMPLATES[intent].format(**params)
|
# Render via format_map with a default so optional params that the
|
||||||
|
# template references (e.g. playbook_monitor's `clause`) degrade to an
|
||||||
|
# empty string instead of raising KeyError.
|
||||||
|
class _Defaulted(dict):
|
||||||
|
def __missing__(self, _key): # noqa: D105 — small render shim
|
||||||
|
return ""
|
||||||
|
steering_input = HANDOFF_TEMPLATES[intent].format_map(_Defaulted(params))
|
||||||
if sanitized_event:
|
if sanitized_event:
|
||||||
steering_input += "\n\n" + frame_handoff(source_agent, sanitized_event)
|
steering_input += "\n\n" + frame_handoff(source_agent, sanitized_event)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue