http: add SSM-bearer HTTP proxy verb to close 'token on cmdline' leak path #99

Open
opened 2026-05-26 19:36:29 +00:00 by coilysiren · 0 comments
Owner

Problem

Today's datastore-token rotation surfaced a real class of leak: any time an agent needs to make an authenticated HTTP request to a coilysiren backend with an SSM-stored bearer token, the only available shape is

curl -H "Authorization: Bearer $(coily ops aws ssm get-parameter ...)" <url>

That's the textbook anti-pattern. If the command substitution fails (auto-mode classifier denies the substitution but lets the literal through, agent retries with the resolved value inlined, transcript compaction stitches the value into chat, etc.) the token lands in the transcript. We hit exactly that today and had to rotate.

The audited wrappers (coily channel <verb>, coily ops aws ssm, etc.) already resolve and inject the bearer internally - the value never crosses the agent boundary. The gap is the long tail: arbitrary endpoints not yet wrapped, ad-hoc verification curls, one-off probes against new modes on the backend.

Proposal

A coily http (or coily ops http) verb that takes an SSM ref for the auth header and proxies the request, so the token only exists inside the coily process:

coily http get   <url> [--bearer-ssm /coilysiren/backend/datastore-token]
coily http post  <url> --bearer-ssm <path> --json @body.json
coily http head  <url> --bearer-ssm <path>
  • --bearer-ssm resolves a SecureString param, sets Authorization: Bearer <value>, never echoes it.
  • Default to --bearer-ssm /coilysiren/backend/datastore-token when <url> is http://api/*, so verification curls become coily http get http://api/agent-channel.
  • --header-ssm KEY=path for non-bearer schemes (basic, custom).
  • Output is the response body. -i for full headers. Status code in exit code on --fail.

Why now

  • Closes the "agent retries with the secret on the cmdline" leak path that the classifier and secrets.compare_digest together cannot prevent.
  • Subsumes one-off curls against the backend's mode routes (channels, items, future modes) without writing a coily verb per mode.
  • Routes every authed HTTP call through the audit log, same as the existing pass-throughs.

Out of scope

  • A general MITM proxy that intercepts arbitrary tool calls - that's a much bigger boundary. The verb is just curl with SSM-resolved headers.
  • Caching the resolved value across invocations. Each call resolves fresh; SSM rate limit is generous.

Related

  • coilysiren/coily#98 - coily channel list (this proposal would also serve as the fallback when a mode lacks a dedicated verb).
  • The Never echo decrypted values rule in agentic-os-kai/AGENTS.md - this verb is how that rule stays enforceable from the agent side.
**Problem** Today's `datastore-token` rotation surfaced a real class of leak: any time an agent needs to make an authenticated HTTP request to a coilysiren backend with an SSM-stored bearer token, the only available shape is ```sh curl -H "Authorization: Bearer $(coily ops aws ssm get-parameter ...)" <url> ``` That's the textbook anti-pattern. If the command substitution fails (auto-mode classifier denies the substitution but lets the literal through, agent retries with the resolved value inlined, transcript compaction stitches the value into chat, etc.) the token lands in the transcript. We hit exactly that today and had to rotate. The audited wrappers (`coily channel <verb>`, `coily ops aws ssm`, etc.) already resolve and inject the bearer internally - the value never crosses the agent boundary. The gap is the long tail: arbitrary endpoints not yet wrapped, ad-hoc verification curls, one-off probes against new modes on the backend. **Proposal** A `coily http` (or `coily ops http`) verb that takes an SSM ref for the auth header and proxies the request, so the token only exists inside the coily process: ```sh coily http get <url> [--bearer-ssm /coilysiren/backend/datastore-token] coily http post <url> --bearer-ssm <path> --json @body.json coily http head <url> --bearer-ssm <path> ``` - `--bearer-ssm` resolves a SecureString param, sets `Authorization: Bearer <value>`, never echoes it. - Default to `--bearer-ssm /coilysiren/backend/datastore-token` when `<url>` is `http://api/*`, so verification curls become `coily http get http://api/agent-channel`. - `--header-ssm KEY=path` for non-bearer schemes (basic, custom). - Output is the response body. `-i` for full headers. Status code in exit code on `--fail`. **Why now** - Closes the "agent retries with the secret on the cmdline" leak path that the classifier and `secrets.compare_digest` together cannot prevent. - Subsumes one-off curls against the backend's mode routes (channels, items, future modes) without writing a coily verb per mode. - Routes every authed HTTP call through the audit log, same as the existing pass-throughs. **Out of scope** - A general MITM proxy that intercepts arbitrary tool calls - that's a much bigger boundary. The verb is just `curl with SSM-resolved headers`. - Caching the resolved value across invocations. Each call resolves fresh; SSM rate limit is generous. **Related** - coilysiren/coily#98 - `coily channel list` (this proposal would also serve as the fallback when a mode lacks a dedicated verb). - The `Never echo decrypted values` rule in agentic-os-kai/AGENTS.md - this verb is how that rule stays enforceable from the agent side.
coilysiren added
P0
and removed
P1
labels 2026-05-31 06:59:44 +00:00
Sign in to join this conversation.
No labels
P0
P1
P2
P3
P4
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
coilyco-bridge/coily#99
No description provided.