Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ codeoid new <n> <d> --workstream <id> [--override] # a session's home workstrea
codeoid mcp signin <server> --credential <id> # a named OAuth slot: a second sign-in to one server (a workstream's own)
codeoid new <n> <d> --ref <workstream>[:<dir>] # another workstream it may read (its folders read-only, its memory), by that one's share policy
codeoid workstream ref add|rm <session> <workstream>[:<dir>] | move <session> <workstream> # references and moves (§5.3, §7)
codeoid secret ls | reveal <ws>:<fp> | forget <ws>:<fp> | promote <ws>:<fp> --as <id> | scrub [--dry-run] # kept secrets; scrub older data (docs/secrets-and-memory-design.md)
```

`/mcp/control` (off by default, `mcpControl.*`) lets external agents drive
Expand Down Expand Up @@ -573,6 +574,21 @@ queueing, `dispatch_tasks.workstream` judged again at claim; send-class cards
carry `targetWorkstream` (`#fleetCardWorkstream`). `workstreams/egress.ts` is
the egress stub.

Secrets (`src/daemon/secrets/`, docs/secrets-and-memory-design.md §2): what a
session stores, shows and hands on never holds a secret's value. `SecretService`
(made only by `codeoid start`, `DaemonConfig.secretsConfigDir`; tests and
embedders get none) holds `KnownSecrets` (exact values from every place codeoid
keeps one, `addDaemonKnownSources`) → `‹credential:<label>›`, and the detector
(`detect.ts`) → `‹secret:<kind>:<hmac8>›`. Applied in Session at
`#persistAndBuffer`/`#commitStreamed` (`#scrubMessage`), tool_start
(`#recordedToolInput`, by `verdict.ts`'s keep / withhold-output /
withhold-written / drop) and tool_complete (before hooks; not a `redacted`
mark), live deltas (`StreamScrubber`), the canonical history
(`CanonicalHistoryAccumulator.scrubTurn`), `MemoryEngine.scrubText`, share
packs, and MCP results in the hub (`setResultScrub`, `secrets.beforeModel`).
Detected values are dropped unless the workstream says `captureSecrets: "vault"`
(`CapturedSecretVault`, `captured_secrets`); `secret.*` is owner-only and audited.

Routines (`src/daemon/routines/`, docs/routines.md) run sessions on a schedule
or an API call. `RoutineService` owns the scheduler and runs; it reaches
sessions only through the `RoutineHost` seam that `SessionManager` implements,
Expand Down
42 changes: 42 additions & 0 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,48 @@ The conductor acts only through sessions homed where the work belongs. `fleet_sp

`codeoid workstream import [--root <dir>]` proposes workstreams from your repositories' remotes and lists Claude's global MCP servers to assign. It writes nothing; isolation stays off until you turn it on.

## Secrets in what codeoid stores

What codeoid stores never holds a secret's value: the transcript, the history a backend is seeded from, the scrollback a client replays, memory, and a shared bundle. Where one appears it is replaced by a reference:

- `‹credential:gh-highflame›` for a value codeoid already knows: a declared credential, a backend account's key, an MCP sign-in, the daemon's own keys. Its value stays where it lives.
- `‹secret:github-token:3f9a2c1b›` for one found by its shape (a token format, a private key, a URL's password, a value assigned to a name like `DB_PASSWORD`). The last part is a fingerprint: the same secret has the same reference everywhere, and the value can't be worked out from it.

A command that exists to print a credential (`gh auth token`, `printenv`, `op read`, `cat .env`, …) is stored without its output, and a file tool writing a credential file (`.env`, `~/.netrc`, a private key) without what it wrote.

The value of a detected secret is dropped. A workstream can keep them instead, so you can get one back later:

```jsonc
{ "workstreams": { "items": [ { "id": "acme", "captureSecrets": "vault" } ] } }
```

Kept secrets are encrypted under a key outside the database, belong to the workstream that kept them, and are forgotten after `secrets.vaultDays` (30) unless promoted:

```bash
codeoid secret ls # what is kept, never the values
codeoid secret reveal acme:3f9a2c1b # the value (your own token only; audited)
codeoid secret promote acme:3f9a2c1b --as acme-gh # make it a declared credential
codeoid secret forget acme:3f9a2c1b
codeoid secret scrub --dry-run # what was stored before this, scrubbed (stop the daemon first)
```

Settings, under `secrets`:

| Setting | Default | |
| --- | --- | --- |
| `enabled` | `true` | Turn it off only to debug. |
| `beforeModel` | `true` | Also scrub MCP results before they go back to the model (through codeoid's hub). |
| `captureSecrets` | `"drop"` | For sessions with no workstream; a workstream's own setting decides for its sessions. |
| `vaultDays` | `30` | Kept secrets not seen for this long are forgotten. |
| `dropCommands` | `[]` | Commands whose calls aren't stored at all (`"my-vault dump"`). |
| `credentialFiles` | `[]` | More files whose contents are credentials (globs, by name or path). |

A value found only by the name it sits under (`DB_PASSWORD=…`) is replaced there, but not remembered: repeated bare later, it isn't recognised. A token of a distinct format (a vendor's token, a JWT, a bearer token) is, for the rest of the daemon's run.

An OpenAI-compatible, Gemini or llama.cpp session is sent its earlier turns from codeoid's stored history: from the next turn on, its model sees a secret it read earlier as the reference.

Limits. The backend has already seen a value by the time codeoid stores the result, and a CLI backend keeps its own copy (Claude Code's `~/.claude/projects`, Codex's sessions), which codeoid doesn't change. A secret with no known shape, that codeoid doesn't know, in a command not on the list, still gets through. Design: [secrets-and-memory-design.md](secrets-and-memory-design.md).

## Signing a backend in, instead of pasting a key

Settings → **Claude** → **Sign in with Claude** runs the backend's own login
Expand Down
Loading
Loading