T09 · Insecure Skill Coding Practices
- Location
index.ts:52- Finding
Subagent Status Confusion Caused by Reusable Labels
- Content
View full analysis
a.label === label && a.status === 'done' ) if (agent) { console.log(`✅ ${label} completed`) return agent } const failed = status.recent?.find((a: any) => a.label === label && a.status === 'failed' ) ``` ### Technical Analysis `sessions_spawn` returns a unique `childSessionKey`, but the pipeline does not use that identifier while waiting for completion. Instead, `waitForAgent` searches every subagent reported within the preceding 60 minutes and accepts the first session whose reusable label and status match. The pipeline repeatedly uses predictable labels such as `planner`, `pm`, `coder`, `tester`, `fixer`, and `reporter`. Consequently, a stale session or a concurrently running pipeline with the same label can satisfy the lookup. The waiting code may then treat an unrelated session as the stage it just spawned. This is a time-of-check and identity-confusion flaw. The code verifies a mutable, non-unique display label rather than the immutable identifier returned when the relevant subagent was created. ### Attack Path 1. An attacker with the ability to start subagents in the same observable environment, or another concurrent pipeline invocation, starts an agent using a predictable label such as `planner`. 2. That unrelated agent reaches the `done` or `failed` state while remaining within the `recentMinutes: 60` query window. 3. A victim invokes `spawnDevTeam`, which starts its own agent with the same label. 4. `waitForAgent` lists recent subagents and searches only by label and status. 5. The unrelated agent is selected before the victim's newly spawned agent has completed. 6. The pipeline incorrectly advances to the next stage or t ...[truncated 1081 chars]- Remediation
View remediation
candidate.childSessionKey === childSessionKey ) if (agent?.status === 'done') { return agent } if (agent?.status === 'failed') { throw new Error(`Subagent ${childSessionKey} failed`) } await new Promise(resolve => setTimeout(resolve, 30000)) } throw new Error(`Subagent ${childSessionKey} timed out`) } ``` Each pipeline stage should call `waitForAgent` with the exact identifier returned by its corresponding `spawnAgent` call. ]]>
