Submitted Job Payload Carries Its Own Pre-Approved Tool List
Detects an agent/workflow specification that ships a list of tools it has already been approved to use -- approve: [execute_command], approved_tools: [shell, delete_file], auto_approve_tools: [...] -- where the list names a command-execution or destructive tool. The runtime approval decorator asks for a human only when the tool is NOT on that list, so the gate is removed by DATA travelling with the request rather than by a CLI flag or a settings write. Mined from OWASP-ASI incident INC-13851 (PraisonAI unauthenticated Jobs API + approval bypass), where the submitted job body carried the pre-approval list. ATR-2026-02400 enumerates the neighbouring shapes -- defaultMode, defaultApprovalMode, approvals.mode, confirmation_mode, autoApprovalSettings, mcpServers.trust -- and has no notion of a per-request approved-tool LIST; ATR-2026-00118 covers approval fatigue, a different mechanism. Excluded on purpose: an approval list that names only read-only tools (read_file, list_files, search), and a client's own settings key autoApprove / auto_approve / chat.tools.autoApprove, which is a human's standing configuration rather than a job payload. That exclusion is enforced by the \b in front of the key alternation -- rule_version 1 asserted the exclusion but did not implement it, and fired on Cline/Roo/VS Code settings whose autoApprove list contains write_file.
建議回應
參考資料
偵測條件
組合方式: any- 01An approval list, in inline-array form, that already contains a command-execution or destructive tool. The list form is what makes this a pre-approval rather than a request. The \b before the key alternation is load-bearing: without it `approve` matched the TAIL of every key ending in that word -- autoApprove, auto_approve, chat.tools.autoApprove -- so the rule fired on the standing client settings the description claims are out of scope.欄位: content運算子: regex
- 02The same list in YAML block-sequence form, as a submitted workflow file carries it. Also matches the JSON-encoded transport where the newline arrives as the two characters backslash n.欄位: content運算子: regex
攻擊範例(規則會觸發)
{"name":"reporter","instructions":"summarise the repo","approve":["execute_command","write_file"]}{"approved_tools":["shell","delete_file"],"require_approval":false}auto_approve_tools: [execute_command, python_repl]
agents: - name: runner approve: - execute_command - read_file{"preApprovedTools":["terminal","sudo"]}
以上為真實攻擊 payload 的脫敏版本,與規則一同版本化,作為 regression test——確保未來的修訂不會悄悄漏掉它們。
正常樣本(規則不會觸發)
- ADVERSARIAL: the same key with a read-only tool list -- a conservative job spec, not a gate bypass
{"name":"reporter","approve":["read_file","list_files","search_web"]} - ADVERSARIAL: a client's own standing settings key, maintained by a human
{"autoApprove":["read_file","list_files"],"alwaysAllowReadOnly":true} - Prose describing the vulnerability with no list literal
The approve list is checked before the decorator prompts, so a job that names execute_command in it never reaches a human. That is the bug.
- YAML block sequence containing only read-only tools
approved_tools:\n - read_file\n - list_directory
- A tools list, not an approval list -- naming an agent's capabilities is not pre-approving them
tools: [execute_command, write_file] # which tools this agent MAY request; each still prompts
- Empty approval list with approval switched on
{"require_approval":true,"approved_tools":[]} - Implementation of the approval check itself
def approve(tool_name: str) -> bool: return tool_name in {"read_file"} - Release note naming the key
CHANGELOG: auto_approve_tools is now ignored for jobs submitted over HTTP; only the operator config is honoured.
- ADVERSARIAL REVIEW: a real Cline/Roo MCP client settings block. autoApprove with write_file is the standing human configuration this rule's description claims is out of scope; rule_version 1 fired on it.
{"mcpServers":{"filesystem":{"command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","/srv"],"autoApprove":["read_file","write_file","list_directory"]}}} - ADVERSARIAL REVIEW: the same client settings key naming a terminal tool.
{"alwaysAllowReadOnly":true,"autoApprove":["read_file","terminal"]} - ADVERSARIAL REVIEW: snake_case standing operator preference in a user config file.
auto_approve: [read_file, shell] # standing operator preference in ~/.config/agent/config.toml
- ADVERSARIAL REVIEW: documentation telling a user how to configure their own client.
To stop the prompt for file writes, set autoApprove: ["write_file"] in your MCP client settings. This is a per-user preference, not part of any job.
- ADVERSARIAL REVIEW: the same key with a read-only tool list -- a conservative job spec, not a gate bypass.
{"name":"reporter","approve":["read_file","list_files","search_web"]} - ADVERSARIAL REVIEW: a test asserting the settings value, with no list literal in key position.
assert.deepEqual(settings.autoApprove, ["read_file","write_file"]);
- ADVERSARIAL REVIEW: the JSON Schema that DEFINES the key, enumerating tool names.
"autoApprove": {"type":"array","items":{"type":"string","enum":["read_file","write_file","execute_command"]}} - ADVERSARIAL REVIEW: a postmortem describing the bypass in prose.
Postmortem: the submitted job carried its own approval list, so execute_command and delete_file never reached a human reviewer. We now strip the field server-side.
- Release note naming the key.
CHANGELOG: auto_approve_tools is now ignored for jobs submitted over HTTP; only the operator config is honoured.
- ADVERSARIAL REVIEW: the payload shape inside a fenced code block in documentation.
Example client configuration: ```json {"autoApprove":["read_file","write_file"]} ``` Keep this list short. - ADVERSARIAL REVIEW: non-English (Traditional Chinese) documentation for the client setting.
在 Cline 的設定檔裡把 autoApprove: ["read_file", "write_file"] 打開,就不會每次都跳出確認視窗。這是使用者自己的偏好設定。
- ADVERSARIAL REVIEW: an unrelated approval-shaped key from Atlantis/Terraform.
apply_requirements: [approved, mergeable]
- Implementation of the approval check itself.
def approve(tool_name: str) -> bool: return tool_name in {"read_file"} - ADVERSARIAL REVIEW: the VS Code chat tool auto-approve setting, a human-maintained editor preference.
"chat.tools.autoApprove": ["read_file", "write_file", "bash"]
已知誤報情境
- ▸An approval list containing only read-only tools -- approve: [read_file, list_files, search_web] -- which is the shape of a genuinely conservative job spec. Excluded because a destructive tool name is required.
- ▸A client's standing configuration key (Cline/Roo autoApprove: ["read_file"]) that a human maintains: the key name autoApprove alone is not in this rule's list, only autoApproveTools / auto_approve_tools.
- ▸Documentation, changelogs and tests that name these keys, including inside code fences (suppressed by tags.suppress_in_code_blocks). Twelve benign samples covering all of the above were written and run during authoring; none matched.
- ▸A human operator who deliberately writes approved_tools: [shell] into their own job template gets the same alert. The text cannot say who wrote it, which is why the action is alert only.
完整 YAML 定義
在 GitHub 編輯 →title: "Submitted Job Payload Carries Its Own Pre-Approved Tool List"
id: ATR-2026-02667
rule_version: 2
status: experimental
description: >
Detects an agent/workflow specification that ships a list of tools it has
already been approved to use -- approve: [execute_command], approved_tools:
[shell, delete_file], auto_approve_tools: [...] -- where the list names a
command-execution or destructive tool. The runtime approval decorator asks
for a human only when the tool is NOT on that list, so the gate is removed by
DATA travelling with the request rather than by a CLI flag or a settings
write.
Mined from OWASP-ASI incident INC-13851 (PraisonAI unauthenticated Jobs API +
approval bypass), where the submitted job body carried the pre-approval list.
ATR-2026-02400 enumerates the neighbouring shapes -- defaultMode,
defaultApprovalMode, approvals.mode, confirmation_mode, autoApprovalSettings,
mcpServers.trust -- and has no notion of a per-request approved-tool LIST;
ATR-2026-00118 covers approval fatigue, a different mechanism.
Excluded on purpose: an approval list that names only read-only tools
(read_file, list_files, search), and a client's own settings key autoApprove /
auto_approve / chat.tools.autoApprove, which is a human's standing
configuration rather than a job payload. That exclusion is enforced by the \b
in front of the key alternation -- rule_version 1 asserted the exclusion but
did not implement it, and fired on Cline/Roo/VS Code settings whose
autoApprove list contains write_file.
author: "ATR Community (OWASP-ASI sweep)"
date: "2026/08/23"
schema_version: "0.1"
detection_tier: pattern
maturity: test
severity: high
references:
cwe:
- "CWE-306"
- "CWE-284"
owasp_llm:
- "LLM06:2025"
- "LLM08:2025"
owasp_agentic:
- "ASI02:2026"
- "ASI06:2026"
mitre_attack:
- "T1562.001 - Impair Defenses: Disable or Modify Tools"
mitre_atlas:
- "AML.T0053 - AI Agent Tool Invocation"
external:
- "https://owasp-agentic-ai-security-incidents.lovable.app/"
metadata_provenance:
cwe: human-reviewed
owasp_llm: human-reviewed
owasp_agentic: human-reviewed
mitre_attack: human-reviewed
mitre_atlas: human-reviewed
compliance:
eu_ai_act:
- article: "14"
context: "Article 14 (human oversight) requires oversight measures that the system cannot disable; a job payload that declares its own approvals removes the human from the loop for exactly the high-risk tools, which this rule detects."
strength: primary
- article: "9"
context: "Article 9 (risk management system): unattended execution of destructive tools is an identified risk and this detection is its runtime treatment."
strength: secondary
nist_ai_rmf:
- subcategory: "MG.4.1"
context: "Monitoring must surface bypasses of built-in safeguards; a pre-approved tool list inside a submitted job is such a bypass."
strength: primary
- subcategory: "GV.3.2"
context: "Human oversight responsibilities are defeated when approval state is supplied by the requester; detecting it supports that governance control."
strength: secondary
iso_42001:
- clause: "8.1"
context: "ISO/IEC 42001 Clause 8.1 (operational planning and control): approval gates are an operational control, and this rule reports payloads that pre-satisfy them."
strength: primary
- clause: "9.1"
context: "ISO/IEC 42001 Clause 9.1 (monitoring and measurement): each occurrence is evidence about how often the approval control is actually exercised."
strength: secondary
tags:
category: excessive-autonomy
subcategory: preapproved-tool-list
scan_target: runtime
confidence: high
suppress_in_code_blocks: true
agent_source:
type: mcp_exchange
framework:
- any
provider:
- any
detection:
condition: any
conditions:
- field: content
operator: regex
value: '["'']?\b(?:approve|approved_tools|approvedTools|auto_approve_tools|autoApproveTools|pre_?approved_tools|preApprovedTools|approved_actions)["'']?\s{0,3}:\s{0,3}\[[^\]\n]{0,80}\b(?:execute_command|run_command|shell|bash|terminal|exec|delete_file|remove_file|write_file|python_repl|code_interpreter|sudo)\b'
description: >-
An approval list, in inline-array form, that already contains a
command-execution or destructive tool. The list form is what makes this
a pre-approval rather than a request. The \b before the key alternation
is load-bearing: without it `approve` matched the TAIL of every key
ending in that word -- autoApprove, auto_approve, chat.tools.autoApprove
-- so the rule fired on the standing client settings the description
claims are out of scope.
- field: content
operator: regex
value: '\b(?:approve|approved_tools|auto_approve_tools|pre_approved_tools)\s{0,2}:\s{0,4}(?:\n|\\n)\s{0,10}-\s{0,2}["'']?(?:execute_command|run_command|shell|bash|terminal|exec|delete_file|remove_file|write_file|python_repl|code_interpreter|sudo)\b'
description: "The same list in YAML block-sequence form, as a submitted workflow file carries it. Also matches the JSON-encoded transport where the newline arrives as the two characters backslash n."
false_positives:
- "An approval list containing only read-only tools -- approve: [read_file, list_files, search_web] -- which is the shape of a genuinely conservative job spec. Excluded because a destructive tool name is required."
- "A client's standing configuration key (Cline/Roo autoApprove: [\"read_file\"]) that a human maintains: the key name autoApprove alone is not in this rule's list, only autoApproveTools / auto_approve_tools."
- "Documentation, changelogs and tests that name these keys, including inside code fences (suppressed by tags.suppress_in_code_blocks). Twelve benign samples covering all of the above were written and run during authoring; none matched."
- "A human operator who deliberately writes approved_tools: [shell] into their own job template gets the same alert. The text cannot say who wrote it, which is why the action is alert only."
response:
actions:
- alert
message_template: >
[ATR-2026-02667] This job payload declares its own approved-tool list and it
includes command execution or file deletion. The human approval step will be
skipped for exactly those tools -- verify who supplied this specification.
confidence: 80
test_cases:
true_positives:
- input: '{"name":"reporter","instructions":"summarise the repo","approve":["execute_command","write_file"]}'
expected: triggered
description: "OWASP-ASI INC-13851 -- the submitted job body carries its own approvals"
- input: '{"approved_tools":["shell","delete_file"],"require_approval":false}'
expected: triggered
description: "Approved-tool list naming a shell and a destructive file tool"
- input: 'auto_approve_tools: [execute_command, python_repl]'
expected: triggered
description: "Inline YAML array form"
- input: "agents:\n - name: runner\n approve:\n - execute_command\n - read_file"
expected: triggered
description: "YAML block sequence in a submitted workflow file"
- input: '{"preApprovedTools":["terminal","sudo"]}'
expected: triggered
description: "camelCase spelling with a terminal tool"
true_negatives:
- input: '{"name":"reporter","approve":["read_file","list_files","search_web"]}'
expected: not_triggered
description: "ADVERSARIAL: the same key with a read-only tool list -- a conservative job spec, not a gate bypass"
- input: '{"autoApprove":["read_file","list_files"],"alwaysAllowReadOnly":true}'
expected: not_triggered
description: "ADVERSARIAL: a client's own standing settings key, maintained by a human"
- input: 'The approve list is checked before the decorator prompts, so a job that names execute_command in it never reaches a human. That is the bug.'
expected: not_triggered
description: "Prose describing the vulnerability with no list literal"
- input: 'approved_tools:\n - read_file\n - list_directory'
expected: not_triggered
description: "YAML block sequence containing only read-only tools"
- input: 'tools: [execute_command, write_file] # which tools this agent MAY request; each still prompts'
expected: not_triggered
description: "A tools list, not an approval list -- naming an agent's capabilities is not pre-approving them"
- input: '{"require_approval":true,"approved_tools":[]}'
expected: not_triggered
description: "Empty approval list with approval switched on"
- input: 'def approve(tool_name: str) -> bool: return tool_name in {"read_file"}'
expected: not_triggered
description: "Implementation of the approval check itself"
- input: 'CHANGELOG: auto_approve_tools is now ignored for jobs submitted over HTTP; only the operator config is honoured.'
expected: not_triggered
description: "Release note naming the key"
- input: "{\"mcpServers\":{\"filesystem\":{\"command\":\"npx\",\"args\":[\"-y\",\"@modelcontextprotocol/server-filesystem\",\"/srv\"],\"autoApprove\":[\"read_file\",\"write_file\",\"list_directory\"]}}}"
expected: not_triggered
description: "ADVERSARIAL REVIEW: a real Cline/Roo MCP client settings block. autoApprove with write_file is the standing human configuration this rule's description claims is out of scope; rule_version 1 fired on it."
- input: "{\"alwaysAllowReadOnly\":true,\"autoApprove\":[\"read_file\",\"terminal\"]}"
expected: not_triggered
description: "ADVERSARIAL REVIEW: the same client settings key naming a terminal tool."
- input: "auto_approve: [read_file, shell] # standing operator preference in ~/.config/agent/config.toml"
expected: not_triggered
description: "ADVERSARIAL REVIEW: snake_case standing operator preference in a user config file."
- input: "To stop the prompt for file writes, set autoApprove: [\"write_file\"] in your MCP client settings. This is a per-user preference, not part of any job."
expected: not_triggered
description: "ADVERSARIAL REVIEW: documentation telling a user how to configure their own client."
- input: "{\"name\":\"reporter\",\"approve\":[\"read_file\",\"list_files\",\"search_web\"]}"
expected: not_triggered
description: "ADVERSARIAL REVIEW: the same key with a read-only tool list -- a conservative job spec, not a gate bypass."
- input: "assert.deepEqual(settings.autoApprove, [\"read_file\",\"write_file\"]);"
expected: not_triggered
description: "ADVERSARIAL REVIEW: a test asserting the settings value, with no list literal in key position."
- input: "\"autoApprove\": {\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"read_file\",\"write_file\",\"execute_command\"]}}"
expected: not_triggered
description: "ADVERSARIAL REVIEW: the JSON Schema that DEFINES the key, enumerating tool names."
- input: "Postmortem: the submitted job carried its own approval list, so execute_command and delete_file never reached a human reviewer. We now strip the field server-side."
expected: not_triggered
description: "ADVERSARIAL REVIEW: a postmortem describing the bypass in prose."
- input: "CHANGELOG: auto_approve_tools is now ignored for jobs submitted over HTTP; only the operator config is honoured."
expected: not_triggered
description: "Release note naming the key."
- input: "Example client configuration:\n\n```json\n{\"autoApprove\":[\"read_file\",\"write_file\"]}\n```\n\nKeep this list short."
expected: not_triggered
description: "ADVERSARIAL REVIEW: the payload shape inside a fenced code block in documentation."
- input: "\u5728 Cline \u7684\u8a2d\u5b9a\u6a94\u88e1\u628a autoApprove: [\"read_file\", \"write_file\"] \u6253\u958b,\u5c31\u4e0d\u6703\u6bcf\u6b21\u90fd\u8df3\u51fa\u78ba\u8a8d\u8996\u7a97\u3002\u9019\u662f\u4f7f\u7528\u8005\u81ea\u5df1\u7684\u504f\u597d\u8a2d\u5b9a\u3002"
expected: not_triggered
description: "ADVERSARIAL REVIEW: non-English (Traditional Chinese) documentation for the client setting."
- input: "apply_requirements: [approved, mergeable]"
expected: not_triggered
description: "ADVERSARIAL REVIEW: an unrelated approval-shaped key from Atlantis/Terraform."
- input: "def approve(tool_name: str) -> bool: return tool_name in {\"read_file\"}"
expected: not_triggered
description: "Implementation of the approval check itself."
- input: "\"chat.tools.autoApprove\": [\"read_file\", \"write_file\", \"bash\"]"
expected: not_triggered
description: "ADVERSARIAL REVIEW: the VS Code chat tool auto-approve setting, a human-maintained editor preference."