Skip to content
7 changes: 7 additions & 0 deletions skills/ocx/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ starring would be useful, say so and let the user decide.
The same boundary covers the session-gated `/api/codex-prompt` writes: read them with
`ocx inspect codex-prompt`, and leave the writes to the dashboard.

## Secret-bearing commands

**Do not run `ocx access key create`.** Its one-time response contains the new plaintext

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Prohibit the access-key creation alias

When an agent discovers the advertised ocx api-key create alias, this prohibition does not cover it, and src/cli/dispatch.ts forwards that alias to the same handleAccessCommand(["key", ...]) path that returns the plaintext credential. The new regression test likewise checks only the canonical spelling, so the stated secret boundary remains bypassable; prohibit all access-key creation paths from agent sessions and test both command spellings.

AGENTS.md reference: AGENTS.md:L287-L293

Useful? React with 👍 / 👎.

data-plane credential, and command output can enter the agent transcript. Ask the user to create
the key themselves in a terminal outside the agent session and confirm only the new key id before
continuing with inspection or removal commands. Never ask the user to paste the key into chat.

## Destructive verbs

`storage trash restore` and `storage policy run` refuse without `--yes` (exit 2, nothing sent).
Expand Down
17 changes: 12 additions & 5 deletions skills/ocx/references/03_recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,27 @@ Read `accounts[]`. Two things to respect:
`providers[]` and `models[]` carry `estimatedCostUsd`. Costs are estimates; `estimateReasons` in the
log rows tells you why (for example `usage_estimated`, `expected_price_overlay`).

## 5. Rotate an access key and confirm it went quiet
## 5. Prepare an access-key rotation without exposing the new key

```bash
ocx access key list --json
ocx access key create rotated --json # the plaintext key is in THIS response only
```

Creating a key returns its one-time plaintext credential. **Do not run the create command from an
agent session:** terminal output can enter the model transcript. Ask the user to create the key
themselves in a separate terminal and report only the new key id, never the key itself.

After the user confirms that the replacement is configured, remove the old key:

```bash
ocx access key remove <old-id> --yes --json
ocx access key list --json # the old id is gone; check usage on the rest
```

Note the argument style: `create <name>` and `remove <id>` are **positionals**, not `--label` and
`--id`. `remove` also refuses without `--yes`.
Note the argument style: `remove <id>` is positional, not `--id`, and refuses without `--yes`.

The list carries per-key usage, so a key whose count stops advancing is genuinely unused. The
plaintext key appears once, in the `create` response, and is never retrievable again.
replacement key's plaintext is never retrievable after creation.

An `ambiguous` footer on the list means two configured keys share an id, so per-key totals do not
exist for them — do not attribute usage to either.
Expand Down
8 changes: 8 additions & 0 deletions tests/skill-ocx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ describe("the consent boundary is stated, not implied", () => {
}
});

test("agent-facing instructions never invoke the plaintext access-key creation command", () => {
const skill = readFileSync(SKILL, "utf8");
const recipes = read("references/03_recipes.md");
expect(skill).toContain("Do not run `ocx access key create`");
expect(recipes).not.toMatch(/^\s*ocx access key create\b/m);
expect(recipes).toContain("never the key itself");
});

test("destructive verbs are documented as requiring --yes", () => {
const skill = readFileSync(SKILL, "utf8");
const recipes = read("references/03_recipes.md");
Expand Down
Loading