Advisory Details
Title: Unauthenticated prompt_file agent configuration allows arbitrary local file disclosure to the model provider
Description:
Summary
TinyAGI exposes an unauthenticated agent-configuration API that accepts attacker-controlled prompt_file values and later reads that path from disk during prompt construction. Any client that can reach the HTTP API can point an agent at an arbitrary local file readable by the TinyAGI process, trigger a normal /api/message request, and cause that file's contents to be sent to the configured model provider.
Details
The vulnerable path starts in the public PUT /api/agents/:id route. The route accepts caller-supplied JSON and persists prompt_file directly into the agent configuration without any authentication, path allowlist, canonicalization, or containment check.
const settings = mutateSettings(s => {
if (!s.agents) s.agents = {};
s.agents[agentId] = {
name: body.name!,
provider: body.provider!,
model: body.model!,
working_directory: workingDir,
...(body.prompt_file ? { prompt_file: body.prompt_file } : {}),
};
});
Later, when the attacker or any other user sends a normal POST /api/message request for that agent, invokeAgent() passes the persisted agent.prompt_file into buildSystemPrompt(). That function reads the attacker-controlled path with fs.readFileSync() and appends the file contents to the system prompt that is sent to the provider backend.
const systemPrompt = buildSystemPrompt(
agentId,
agentDir,
agents,
teams,
agent.system_prompt,
agent.prompt_file
);
if (configPromptFile) {
try {
promptFileContent = fs.readFileSync(configPromptFile, 'utf8').trim();
if (promptFileContent) {
prompt += '\n\n' + promptFileContent;
}
} catch {
// Ignore missing prompt file
}
}
This is not a theoretical source-code pattern. The PoC uses the real TinyAGI daemon, drives the real HTTP API, and captures the resulting outbound provider request. In the control run, no prompt_file is configured and the provider capture does not contain the canary. In the exploit run, prompt_file is set to an attacker-chosen local file, the provider request includes the canary from that file, and the assistant response becomes LEAKED_CANARY.
PoC
Prerequisites
- A TinyAGI build that includes the vulnerable code path, verified on release
v0.0.20
- Python 3 available locally
- Node.js dependencies installed so
packages/main/dist/index.js can start
- Network access to the TinyAGI HTTP API
- A local environment where the TinyAGI process can read the target file
Reproduction Steps
- Download the verification PoC from: verification_test.py
- Download the control PoC from: control-normal-behavior.py
- Download the helper harness from: harness.py
- From the repository root, run the control script:
python3 llm-enhance/cve-finding/similar/Path_Traversal/CVE-2026-29611-prompt-file-exp/control-normal-behavior.py
- Confirm the control case prints
[CONTROL-PASS] and that the provider capture does not contain the canary.
- Run the exploit script:
python3 llm-enhance/cve-finding/similar/Path_Traversal/CVE-2026-29611-prompt-file-exp/verification_test.py
- Confirm the exploit case prints
[DEFECT-CONFIRMED], verification_settings_snapshot.json contains the attacker-chosen prompt_file, and verification_provider_requests.json shows provider_contains_canary: true.
Log of Evidence
Control run:
Verification mode: End-to-End
Prompt file omitted from agent configuration.
Assistant text: NO_CANARY
- [verifier]
Provider contains canary: False
Provider request count: 2
[CONTROL-PASS]
Exploit run:
Verification mode: End-to-End
Prompt file set to: /tmp/tinyagi-cve-2026-29611-02c6xjln/sensitive.txt
Assistant text: LEAKED_CANARY
- [verifier]
Provider contains canary: True
Provider request count: 2
[DEFECT-CONFIRMED]
Captured runtime evidence from the exploit run:
verification_settings_snapshot.json persists the attacker-controlled prompt_file
verification_provider_requests.json records provider_contains_canary: true
verification_provider_requests.json also records assistant_text: LEAKED_CANARY
Impact
This is an arbitrary local file disclosure vulnerability reachable through an unauthenticated network API. Any deployment exposing the TinyAGI HTTP interface allows a remote caller to change an agent's prompt_file, read local files accessible to the TinyAGI process, and exfiltrate those contents to the configured model provider. In practice this can expose workspace documents, source code, operational notes, API credentials stored in readable files, and other local secrets in the TinyAGI runtime context.
Affected products
- Ecosystem: npm
- Package name: tinyagi
- Affected versions: <= 0.0.20
- Patched versions:
Severity
- Severity: High
- Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N
Weaknesses
- CWE: CWE-73: External Control of File Name or Path
Occurrences
Advisory Details
Title: Unauthenticated
prompt_fileagent configuration allows arbitrary local file disclosure to the model providerDescription:
Summary
TinyAGI exposes an unauthenticated agent-configuration API that accepts attacker-controlled
prompt_filevalues and later reads that path from disk during prompt construction. Any client that can reach the HTTP API can point an agent at an arbitrary local file readable by the TinyAGI process, trigger a normal/api/messagerequest, and cause that file's contents to be sent to the configured model provider.Details
The vulnerable path starts in the public
PUT /api/agents/:idroute. The route accepts caller-supplied JSON and persistsprompt_filedirectly into the agent configuration without any authentication, path allowlist, canonicalization, or containment check.Later, when the attacker or any other user sends a normal
POST /api/messagerequest for that agent,invokeAgent()passes the persistedagent.prompt_fileintobuildSystemPrompt(). That function reads the attacker-controlled path withfs.readFileSync()and appends the file contents to the system prompt that is sent to the provider backend.This is not a theoretical source-code pattern. The PoC uses the real TinyAGI daemon, drives the real HTTP API, and captures the resulting outbound provider request. In the control run, no
prompt_fileis configured and the provider capture does not contain the canary. In the exploit run,prompt_fileis set to an attacker-chosen local file, the provider request includes the canary from that file, and the assistant response becomesLEAKED_CANARY.PoC
Prerequisites
v0.0.20packages/main/dist/index.jscan startReproduction Steps
python3 llm-enhance/cve-finding/similar/Path_Traversal/CVE-2026-29611-prompt-file-exp/control-normal-behavior.py[CONTROL-PASS]and that the provider capture does not contain the canary.python3 llm-enhance/cve-finding/similar/Path_Traversal/CVE-2026-29611-prompt-file-exp/verification_test.py[DEFECT-CONFIRMED],verification_settings_snapshot.jsoncontains the attacker-chosenprompt_file, andverification_provider_requests.jsonshowsprovider_contains_canary: true.Log of Evidence
Control run:
Exploit run:
Captured runtime evidence from the exploit run:
verification_settings_snapshot.jsonpersists the attacker-controlledprompt_fileverification_provider_requests.jsonrecordsprovider_contains_canary: trueverification_provider_requests.jsonalso recordsassistant_text: LEAKED_CANARYImpact
This is an arbitrary local file disclosure vulnerability reachable through an unauthenticated network API. Any deployment exposing the TinyAGI HTTP interface allows a remote caller to change an agent's
prompt_file, read local files accessible to the TinyAGI process, and exfiltrate those contents to the configured model provider. In practice this can expose workspace documents, source code, operational notes, API credentials stored in readable files, and other local secrets in the TinyAGI runtime context.Affected products
Severity
Weaknesses
Occurrences
prompt_fileinto the saved agent configuration.invokeAgent()forwards the persistedagent.prompt_fileinto prompt construction during a normal message execution path.buildSystemPrompt()reads the attacker-chosen path withfs.readFileSync()and appends its contents to the outbound system prompt.