T01 · Skill Instruction Hijacking
Error
- Location
- scripts/ops-incident-router.sh:63
- Finding
- Untrusted detector trigger is embedded in downstream agent instructions<![CDATA[ ## Vulnerability Details **File Location**: `scripts/ops-incident-router.sh:63-73, 90-105` **Vulnerability Type**: Prompt injection through untrusted detector data **Risk Level**: High ### Vulnerable Code ```bash map_trigger_to_check_id() { case "$1" in cron_failure) echo "cron_failure" ;; heartbeat_gap|paymaster_gap) echo "heartbeat_gap" ;; context_crit|context_100pct) echo "context_crit" ;; context_high|context_90pct) echo "context_high" ;; context_warn|context_80pct) echo "context_warn" ;; dangling_sessions) echo "dangling_sessions" ;; token_spike) echo "token_spike" ;; *) echo "unknown_${1}" ;; esac } ``` ```bash while IFS= read -r alert; do trigger="$(jq -r '.trigger // "unknown"' <<<"$alert")" severity="$(jq -r '.sev // "Sev-3"' <<<"$alert")" check_id="$(map_trigger_to_check_id "$trigger")" guard_raw="$(bash "$SCRIPT_DIR/incident-guard-check.sh" --check-id "$check_id" --severity "$severity" --state-file "$STATE_FILE")" allowed="$(jq -r '.allowed // false' <<<"$guard_raw")" reason="$(jq -r '.reason // "unknown"' <<<"$guard_raw")" if [[ "$allowed" == "true" ]]; then action_json="$(jq -cn \ --arg action "spawn" \ --arg check_id "$check_id" \ --arg severity "$severity" \ --arg mode "$([[ "$LIVE" == "true" ]] && echo live || echo dry-run)" \ --arg task "Investigate incident: ${check_id}. Gather evidence, classify severity, propose low-risk remediations with rollback." \ '{action:$action,check_id:$check_id,severity:$severity,mode:$mode,task:$task}')" ``` ### Technical Analysis The router accepts detector JSON from standard input or a caller-selected file. Known trigger names are mapped to fixed identifiers, but an unknown trigger is copied into `check_id` through the default `unknown_${1}` branch. That value is subsequently interpolated into the natural-language `task` field intended for an investigator agent. The use of `jq --arg` safely encodes the value ...[truncated 1651 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Reject unknown trigger identifiers instead of reflecting them: ```bash map_trigger_to_check_id() { case "$1" in cron_failure) echo "cron_failure" ;; heartbeat_gap|paymaster_gap) echo "heartbeat_gap" ;; context_crit|context_100pct) echo "context_crit" ;; context_high|context_90pct) echo "context_high" ;; context_warn|context_80pct) echo "context_warn" ;; dangling_sessions) echo "dangling_sessions" ;; token_spike) echo "token_spike" ;; *) return 1 ;; esac } ``` 2. Generate task text exclusively from fixed templates selected by an allowlisted identifier. 3. Validate the complete detector schema, including allowed trigger and severity values, before processing alerts. 4. Keep source-provided descriptions in a separate structured data field clearly labeled as untrusted evidence; never interpolate them into agent instructions. 5. Configure downstream agents to treat detector fields as data rather than instructions and enforce least-privilege tool access. 6. Add tests using newlines, control characters, long strings, and instruction-like trigger values to verify that unknown triggers are rejected. ]]>
