Skip to content

[Security] Unauthenticated prompt_file agent configuration allows arbitrary local file disclosure to the model provider #283

Description

@YLChen-007

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

  1. Download the verification PoC from: verification_test.py
  2. Download the control PoC from: control-normal-behavior.py
  3. Download the helper harness from: harness.py
  4. 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
  5. Confirm the control case prints [CONTROL-PASS] and that the provider capture does not contain the canary.
  6. Run the exploit script:
    python3 llm-enhance/cve-finding/similar/Path_Traversal/CVE-2026-29611-prompt-file-exp/verification_test.py
  7. 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

Permalink Description
https://github.com/TinyAGI/tinyclaw/blob/1ae8b4eba2fcb963a076655a7293fc77eafeb84c/packages/server/src/routes/agents.ts#L56-L64 The unauthenticated agent update route persists attacker-controlled prompt_file into the saved agent configuration.
https://github.com/TinyAGI/tinyclaw/blob/1ae8b4eba2fcb963a076655a7293fc77eafeb84c/packages/core/src/invoke.ts#L201-L202 invokeAgent() forwards the persisted agent.prompt_file into prompt construction during a normal message execution path.
https://github.com/TinyAGI/tinyclaw/blob/1ae8b4eba2fcb963a076655a7293fc77eafeb84c/packages/core/src/agent.ts#L208-L215 buildSystemPrompt() reads the attacker-chosen path with fs.readFileSync() and appends its contents to the outbound system prompt.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions