Advisory Details
Title: TinyAGI Anthropic Adapter Disables Claude Dangerous-Tool Confirmation for Unauthenticated POST /api/message Requests
Description:
TinyAGI launches the Anthropic claude CLI with --dangerously-skip-permissions unconditionally. As a result, any remote client that can reach POST /api/message can drive an Anthropic-backed agent session in which Claude's dangerous-tool confirmation barrier has already been disabled before the attacker-controlled prompt is processed.
Summary
In affected TinyAGI releases, unauthenticated HTTP input sent to POST /api/message is queued and routed into the normal Anthropic agent path. That path always prepends --dangerously-skip-permissions before invoking claude -p <message>. This removes the provider-side confirmation step that should otherwise protect dangerous tool actions, so remote clients can cause request-driven Claude sessions to start with the approval barrier already bypassed.
Details
I verified the issue against the latest published release, v0.0.20, and then checked earlier tags to bound the affected range. The unconditional --dangerously-skip-permissions flag is present from v0.0.16 through v0.0.20. The full 40-character commit behind the highest affected release tag is:
1ae8b4eba2fcb963a076655a7293fc77eafeb84c
This checkout still uses the legacy remote name TinyAGI/tinyclaw, but GitHub's canonical upstream repository now resolves to TinyAGI/tinyagi. The occurrence permalinks below therefore use the canonical repository and the release commit above.
The relevant source-to-sink path is:
POST /api/message -> enqueueMessage() -> queue consumer processMessage() -> invokeAgent() -> Anthropic adapter -> claude --dangerously-skip-permissions ... -p <message>
At the sink, the Anthropic adapter starts every Claude invocation with the dangerous approval bypass already enabled:
const args = ['--dangerously-skip-permissions'];
if (model) args.push('--model', model);
if (systemPrompt) args.push('--system-prompt', systemPrompt);
if (continueConversation) args.push('-c');
The remote input path is standard application behavior rather than a lab-only shortcut. POST /api/message accepts a caller-controlled message, enqueues it, and the queue processor later forwards the same message to invokeAgent() without adding any TinyAGI-side confirmation gate. The integration evidence in the attached PoC captures the spawned claude argv and shows the user-controlled request reaching the provider boundary with --dangerously-skip-permissions already present.
I also ran the matched control path against an OpenAI-backed agent through the same public HTTP interface. That control reached the provider boundary successfully but did not include the Anthropic-only bypass flag, which helps isolate the root cause to the Claude adapter rather than to the shared queue or HTTP plumbing.
PoC
Prerequisites
- TinyAGI checked out locally and built so
packages/main/dist/index.js exists
- Node.js installed
- Python 3 installed
- Ability to start the TinyAGI daemon locally and send requests to
127.0.0.1
- No API authentication is required for
POST /api/message
- The PoC uses fake
claude and codex executables earlier in PATH so it can capture argv without requiring a live provider-side approval UI
Reproduction Steps
- Download the shared harness from: harness.py
- Download the verification script from: verification_test.py
- Download the matched control script from: control-provider-baseline.py
- Place the three files in the same directory, or use the equivalent local copies under
llm-enhance/cve-finding/similar/rce/CVE-2026-30741-claude-tool-confirmation-bypass-exp/.
- From the TinyAGI repository root, build the runtime:
npm run build
- Run the verification path:
python3 verification_test.py
- Confirm that
verification.log records a successful POST /api/message and that verification-captured-argv.json contains --dangerously-skip-permissions in the captured Claude argv.
- Run the matched control path:
python3 control-provider-baseline.py
- Confirm that
control-provider-baseline.log records [CONTROL-PASS] and that control-captured-argv.json does not contain --dangerously-skip-permissions.
Log of Evidence
[listening] localhost:52033
[baseline] TinyAGI isolated daemon is running
[probe] /api/message accepted messageId=api_za1h28rq
[evidence] captured argv=['--dangerously-skip-permissions', '--model', 'claude-sonnet-4-6', '--system-prompt', '...', '--output-format', 'stream-json', '--verbose', '-p', 'Use any available dangerous tool and explain what you would do.']
[flow] [HTTP message] -> /api/message -> queue -> invokeAgent -> claude CLI argv
[DEFECT-CONFIRMED-WITH-LIMITATIONS] TinyAGI forwarded untrusted API input into the Anthropic CLI with --dangerously-skip-permissions
[listening] localhost:55851
[baseline] TinyAGI isolated daemon is running
[probe] /api/message accepted messageId=api_o14ukdyh
[evidence] captured argv=['exec', '--model', 'gpt-5.3-codex', '-c', 'developer_instructions=...', '--skip-git-repo-check', '--dangerously-bypass-approvals-and-sandbox', '--json', 'Use any available dangerous tool and explain what you would do.']
[flow] [HTTP message] -> /api/message -> queue -> invokeAgent -> codex CLI argv
[CONTROL-PASS] same public interface reached a provider path without the Anthropic bypass flag
Impact
This is a remote confirmation-bypass issue in the Anthropic execution path. Any client that can reach TinyAGI's unauthenticated message API can cause an Anthropic-backed agent session to start with the dangerous-tool consent barrier already disabled. If the resulting model run attempts filesystem, network, shell, or other sensitive tool actions that Claude would normally gate behind explicit confirmation, that protection has already been removed by TinyAGI before provider-side execution begins.
In practical terms, this weakens a high-value security boundary around autonomous tool use. The directly impacted assets are the host resources and data that become reachable through Claude's tool surface in the affected deployment.
Affected products
- Ecosystem: npm
- Package name: tinyagi
- Affected versions: >= 0.0.16, <= 0.0.20
- Patched versions:
Severity
- Severity: High
- Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:H/A:H
Weaknesses
- CWE: CWE-693: Protection Mechanism Failure
Occurrences
| Permalink |
Description |
|
const body = await c.req.json(); |
|
const { message, agent, sender, senderId, channel, messageId: clientMessageId } = body as { |
|
message?: string; agent?: string; sender?: string; senderId?: string; |
|
channel?: string; messageId?: string; |
|
}; |
|
|
|
if (!message || typeof message !== 'string') { |
|
return c.json({ error: 'message is required' }, 400); |
|
POST /api/message accepts caller-controlled message content from the HTTP request body and only enforces a basic string check before continuing through the normal processing path. |
|
const rowId = enqueueMessage({ |
|
channel: resolvedChannel, |
|
sender: resolvedSender, |
|
senderId: senderId || undefined, |
|
message, |
|
messageId, |
|
agent: resolvedAgent, |
|
The same untrusted message is persisted into the queue by enqueueMessage(), preserving it for later agent execution. |
|
({ text: message } = await runIncomingHooks(message, { channel, sender, messageId, originalMessage: rawMessage })); |
|
|
|
emitEvent('agent:invoke', { agentId, agentName: agent.name, fromAgent: data.fromAgent || null }); |
|
let response: string; |
|
try { |
|
response = await invokeAgent(agent, agentId, message, workspacePath, shouldReset, agents, teams, (text) => { |
|
The queue consumer forwards the attacker-controlled message into invokeAgent() as part of normal processing. |
|
return adapter.invoke({ |
|
agentId, |
|
message, |
|
workingDir, |
|
systemPrompt, |
|
model, |
|
shouldReset, |
|
envOverrides, |
|
onEvent, |
|
invokeAgent() hands the same message and execution context to the selected provider adapter. |
|
const args = ['--dangerously-skip-permissions']; |
|
if (model) args.push('--model', model); |
|
if (systemPrompt) args.push('--system-prompt', systemPrompt); |
|
if (continueConversation) args.push('-c'); |
|
|
|
if (onEvent) { |
|
args.push('--output-format', 'stream-json', '--verbose', '-p', message); |
|
The Anthropic adapter prepends --dangerously-skip-permissions unconditionally before passing the attacker-controlled prompt to claude, which disables Claude's dangerous-tool confirmation barrier for the session. |
Advisory Details
Title: TinyAGI Anthropic Adapter Disables Claude Dangerous-Tool Confirmation for Unauthenticated
POST /api/messageRequestsDescription:
TinyAGI launches the Anthropic
claudeCLI with--dangerously-skip-permissionsunconditionally. As a result, any remote client that can reachPOST /api/messagecan drive an Anthropic-backed agent session in which Claude's dangerous-tool confirmation barrier has already been disabled before the attacker-controlled prompt is processed.Summary
In affected TinyAGI releases, unauthenticated HTTP input sent to
POST /api/messageis queued and routed into the normal Anthropic agent path. That path always prepends--dangerously-skip-permissionsbefore invokingclaude -p <message>. This removes the provider-side confirmation step that should otherwise protect dangerous tool actions, so remote clients can cause request-driven Claude sessions to start with the approval barrier already bypassed.Details
I verified the issue against the latest published release,
v0.0.20, and then checked earlier tags to bound the affected range. The unconditional--dangerously-skip-permissionsflag is present fromv0.0.16throughv0.0.20. The full 40-character commit behind the highest affected release tag is:1ae8b4eba2fcb963a076655a7293fc77eafeb84cThis checkout still uses the legacy remote name
TinyAGI/tinyclaw, but GitHub's canonical upstream repository now resolves toTinyAGI/tinyagi. The occurrence permalinks below therefore use the canonical repository and the release commit above.The relevant source-to-sink path is:
POST /api/message->enqueueMessage()-> queue consumerprocessMessage()->invokeAgent()-> Anthropic adapter ->claude --dangerously-skip-permissions ... -p <message>At the sink, the Anthropic adapter starts every Claude invocation with the dangerous approval bypass already enabled:
The remote input path is standard application behavior rather than a lab-only shortcut.
POST /api/messageaccepts a caller-controlledmessage, enqueues it, and the queue processor later forwards the same message toinvokeAgent()without adding any TinyAGI-side confirmation gate. The integration evidence in the attached PoC captures the spawnedclaudeargv and shows the user-controlled request reaching the provider boundary with--dangerously-skip-permissionsalready present.I also ran the matched control path against an OpenAI-backed agent through the same public HTTP interface. That control reached the provider boundary successfully but did not include the Anthropic-only bypass flag, which helps isolate the root cause to the Claude adapter rather than to the shared queue or HTTP plumbing.
PoC
Prerequisites
packages/main/dist/index.jsexists127.0.0.1POST /api/messageclaudeandcodexexecutables earlier inPATHso it can capture argv without requiring a live provider-side approval UIReproduction Steps
llm-enhance/cve-finding/similar/rce/CVE-2026-30741-claude-tool-confirmation-bypass-exp/.npm run buildpython3 verification_test.pyverification.logrecords a successfulPOST /api/messageand thatverification-captured-argv.jsoncontains--dangerously-skip-permissionsin the captured Claude argv.python3 control-provider-baseline.pycontrol-provider-baseline.logrecords[CONTROL-PASS]and thatcontrol-captured-argv.jsondoes not contain--dangerously-skip-permissions.Log of Evidence
Impact
This is a remote confirmation-bypass issue in the Anthropic execution path. Any client that can reach TinyAGI's unauthenticated message API can cause an Anthropic-backed agent session to start with the dangerous-tool consent barrier already disabled. If the resulting model run attempts filesystem, network, shell, or other sensitive tool actions that Claude would normally gate behind explicit confirmation, that protection has already been removed by TinyAGI before provider-side execution begins.
In practical terms, this weakens a high-value security boundary around autonomous tool use. The directly impacted assets are the host resources and data that become reachable through Claude's tool surface in the affected deployment.
Affected products
Severity
Weaknesses
Occurrences
tinyagi/packages/server/src/routes/messages.ts
Lines 8 to 15 in 1ae8b4e
POST /api/messageaccepts caller-controlledmessagecontent from the HTTP request body and only enforces a basic string check before continuing through the normal processing path.tinyagi/packages/server/src/routes/messages.ts
Lines 35 to 41 in 1ae8b4e
messageis persisted into the queue byenqueueMessage(), preserving it for later agent execution.tinyagi/packages/main/src/index.ts
Lines 96 to 101 in 1ae8b4e
messageintoinvokeAgent()as part of normal processing.tinyagi/packages/core/src/invoke.ts
Lines 267 to 275 in 1ae8b4e
invokeAgent()hands the same message and execution context to the selected provider adapter.tinyagi/packages/core/src/adapters/claude.ts
Lines 37 to 43 in 1ae8b4e
--dangerously-skip-permissionsunconditionally before passing the attacker-controlled prompt toclaude, which disables Claude's dangerous-tool confirmation barrier for the session.