Advisory Details
Title: TinyAGI unauthenticated administrative API allows persistent settings and agent prompt modification
Description:
TinyAGI exposes state-changing control-plane API endpoints without any authentication or authorization checks. An unauthenticated client that can reach the API port can overwrite the global settings file, create or modify agent definitions, and write attacker-controlled prompt content into agent workspaces.
Summary
TinyAGI publishes PUT /api/settings and PUT /api/agents/:id on its Hono API with no auth barrier. Any unauthenticated HTTP client can use these routes to overwrite ${TINYAGI_HOME}/settings.json, add arbitrary agents entries, and write attacker-controlled content to <workspace>/<agent>/AGENTS.md. Because the daemon later consumes these files as control-plane state, this is a persistent compromise of TinyAGI runtime behavior rather than a transient API bug.
Details
The root cause is the absence of any authentication middleware or route-level authorization around administrative API modules. In packages/server/src/index.ts, the server enables global CORS and mounts the administrative route sets directly:
app.use('/*', cors());
app.route('/', agentsRoutes);
app.route('/', teamsRoutes);
app.route('/', settingsRoutes);
No owner/admin check is applied before the state-changing handlers run.
The first sink is packages/server/src/routes/settings.ts. PUT /api/settings accepts arbitrary JSON from the request body, merges it with the current settings object, and writes the result straight back to SETTINGS_FILE:
app.put('/api/settings', async (c) => {
const body = await c.req.json();
const current = getSettings();
const merged = { ...current, ...body } as Settings;
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2) + '\n');
});
The second sink is packages/server/src/routes/agents.ts. PUT /api/agents/:id accepts untrusted JSON, persists a new or modified agent entry via mutateSettings(...), and, if system_prompt is supplied, writes that content verbatim into the target workspace AGENTS.md file at packages/server/src/routes/agents.ts#L76-L77:
const settings = mutateSettings(s => {
if (!s.agents) s.agents = {};
s.agents[agentId] = {
name: body.name!,
provider: body.provider!,
model: body.model!,
working_directory: workingDir,
};
});
if (body.system_prompt != null) {
fs.writeFileSync(path.join(workingDir, 'AGENTS.md'), body.system_prompt, 'utf8');
}
I verified the issue end-to-end against the upstream v0.0.20 release commit 1ae8b4eba2fcb963a076655a7293fc77eafeb84c. The test started the real daemon with node packages/main/dist/index.js, sent unauthenticated requests to PUT /api/agents/attacker and PUT /api/settings, and independently confirmed the attacker-controlled values on disk in runtime-vuln/settings.json and runtime-vuln/workspace/attacker/AGENTS.md.
PoC
Prerequisites
- Upstream TinyAGI checkout at
v0.0.20 or another affected release <= 0.0.20
- Node.js and repository dependencies installed
- Built server artifacts from
npm run build
- Python 3 available to run the PoC harness
- Ability to run the TinyAGI daemon locally on port
3777
Reproduction Steps
-
Check out an affected release and build it:
git checkout v0.0.20
npm install
npm run build
-
Download the verification PoC from: verification_test.py
-
From the TinyAGI repository root, run:
python3 ./verification_test.py
If you save the script outside the repository root, set the repo path explicitly:
TINYAGI_REPO_ROOT=/path/to/tinyclaw python3 /path/to/verification_test.py
-
The script launches the real daemon with an isolated TINYAGI_HOME, sends these unauthenticated administrative requests, and then checks the persisted sink files:
PUT /api/agents/attacker
{"name":"Attacker Agent","provider":"anthropic","model":"sonnet","system_prompt":"owned-by-unauth-client"}
PUT /api/settings
{"monitoring":{"heartbeat_interval":1337}}
-
Observe the resulting disk state under the isolated runtime:
runtime-vuln/settings.json contains a new agents.attacker entry
runtime-vuln/settings.json contains monitoring.heartbeat_interval = 1337
runtime-vuln/workspace/attacker/AGENTS.md contains owned-by-unauth-client
-
For a baseline comparison, download and run the control script from: control-readonly-baseline.py
python3 ./control-readonly-baseline.py
That script uses the same real service, but only performs GET /api/agents and GET /api/settings, and confirms that read-only traffic does not create workspace/attacker/AGENTS.md or mutate settings.json.
Log of Evidence
Verification run:
[Mode] End-to-End
[Test Input] unauthenticated HTTP client
[Interface] PUT /api/agents/:id and PUT /api/settings
[Expectation] administrative state changes succeed without authentication
PUT /api/agents/attacker -> HTTP 200, prompt_exists=True, content_match=True
PUT /api/settings -> HTTP 200, on_disk.monitoring.heartbeat_interval=1337
[DEFECT-CONFIRMED] Unauthenticated callers can mutate persistent admin state via the public API.
Control run:
[Mode] End-to-End
[Control] Same public HTTP interface without state-changing input
[Interface] GET /api/agents and GET /api/settings
[Expectation] baseline reads succeed and no new workspace/config mutation occurs
GET /api/agents -> HTTP 200, attacker_present=False
GET /api/settings -> HTTP 200, on_disk.monitoring.heartbeat_interval=None
workspace/attacker/AGENTS.md exists=False
[CONTROL-PASS] Baseline interface access does not create agent files or mutate settings.
Impact
This is an unauthenticated administrative control-plane compromise. Any client that can reach the TinyAGI API can:
- overwrite persistent global settings in
settings.json
- create or modify agent definitions without operator approval
- write attacker-controlled prompt content into workspace
AGENTS.md
- influence later daemon and agent behavior using persisted malicious state
In practice, this breaks the integrity of the TinyAGI deployment and can be used to poison agent behavior, alter operational settings, and disrupt or redirect future automated actions. Deployments that expose the API beyond a single trusted local operator are especially at risk.
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:L/I:H/A:L
Weaknesses
- CWE: CWE-306: Missing Authentication for Critical Function
Occurrences
Advisory Details
Title: TinyAGI unauthenticated administrative API allows persistent settings and agent prompt modification
Description:
TinyAGI exposes state-changing control-plane API endpoints without any authentication or authorization checks. An unauthenticated client that can reach the API port can overwrite the global settings file, create or modify agent definitions, and write attacker-controlled prompt content into agent workspaces.
Summary
TinyAGI publishes
PUT /api/settingsandPUT /api/agents/:idon its Hono API with no auth barrier. Any unauthenticated HTTP client can use these routes to overwrite${TINYAGI_HOME}/settings.json, add arbitraryagentsentries, and write attacker-controlled content to<workspace>/<agent>/AGENTS.md. Because the daemon later consumes these files as control-plane state, this is a persistent compromise of TinyAGI runtime behavior rather than a transient API bug.Details
The root cause is the absence of any authentication middleware or route-level authorization around administrative API modules. In
packages/server/src/index.ts, the server enables global CORS and mounts the administrative route sets directly:No owner/admin check is applied before the state-changing handlers run.
The first sink is
packages/server/src/routes/settings.ts.PUT /api/settingsaccepts arbitrary JSON from the request body, merges it with the current settings object, and writes the result straight back toSETTINGS_FILE:The second sink is
packages/server/src/routes/agents.ts.PUT /api/agents/:idaccepts untrusted JSON, persists a new or modified agent entry viamutateSettings(...), and, ifsystem_promptis supplied, writes that content verbatim into the target workspaceAGENTS.mdfile atpackages/server/src/routes/agents.ts#L76-L77:I verified the issue end-to-end against the upstream
v0.0.20release commit1ae8b4eba2fcb963a076655a7293fc77eafeb84c. The test started the real daemon withnode packages/main/dist/index.js, sent unauthenticated requests toPUT /api/agents/attackerandPUT /api/settings, and independently confirmed the attacker-controlled values on disk inruntime-vuln/settings.jsonandruntime-vuln/workspace/attacker/AGENTS.md.PoC
Prerequisites
v0.0.20or another affected release<= 0.0.20npm run build3777Reproduction Steps
Check out an affected release and build it:
Download the verification PoC from: verification_test.py
From the TinyAGI repository root, run:
If you save the script outside the repository root, set the repo path explicitly:
The script launches the real daemon with an isolated
TINYAGI_HOME, sends these unauthenticated administrative requests, and then checks the persisted sink files:Observe the resulting disk state under the isolated runtime:
runtime-vuln/settings.jsoncontains a newagents.attackerentryruntime-vuln/settings.jsoncontainsmonitoring.heartbeat_interval = 1337runtime-vuln/workspace/attacker/AGENTS.mdcontainsowned-by-unauth-clientFor a baseline comparison, download and run the control script from: control-readonly-baseline.py
That script uses the same real service, but only performs
GET /api/agentsandGET /api/settings, and confirms that read-only traffic does not createworkspace/attacker/AGENTS.mdor mutatesettings.json.Log of Evidence
Verification run:
Control run:
Impact
This is an unauthenticated administrative control-plane compromise. Any client that can reach the TinyAGI API can:
settings.jsonAGENTS.mdIn practice, this breaks the integrity of the TinyAGI deployment and can be used to poison agent behavior, alter operational settings, and disrupt or redirect future automated actions. Deployments that expose the API beyond a single trusted local operator are especially at risk.
Affected products
Severity
Weaknesses
Occurrences
agentsRoutesandsettingsRoutes.PUT /api/settingsaccepts attacker-controlled JSON, merges it into the live settings object, and writes the result directly toSETTINGS_FILE.PUT /api/agents/:idaccepts untrusted JSON and persists a new or modified agent entry tosettings.jsonwithout checking whether the caller is authorized to perform administrative changes.system_prompt, the handler writes attacker-controlled content verbatim into the target workspaceAGENTS.mdfile.