---
title: Secret Paste Guard
---

# Secret Paste Guard

## What it is

**Secret Paste Guard** is a shipped, opt-in feature (off by default) that keeps API keys and
other secrets out of your AI conversation history. When you paste a secret into the session
composer, Omniscio stores it locally under an opaque handle. For Claude-based engines it injects
the real value only at the moment the agent actually uses it — in a shell command, a file write,
or a web-form field — so the raw value never enters the model's conversation. For every engine,
Omniscio's own stored history is scrubbed of the value. The protection is honest about what each
engine can and can't do (see "The guarantee").

## Where to find it

### How to use it

1. **Turn it on.** Secret Paste Guard ships **off by default** — turn it on at **Settings →
   Features → "Secret Paste Guard"** (the `secretHandleEnabled` setting). Nothing about it runs
   until you enable it.

2. **Paste your secret into the composer.** When the feature is enabled and Omniscio detects
   that what you typed looks like an API key or secret, it offers to store it securely.

3. **The secret is stored locally.** Omniscio saves the raw value in an encrypted, per-session
   store on your machine. On a Claude-based engine it also replaces the value in the composer
   with the opaque handle (`AMCSECRET-<hex>`) — that handle is what the AI sees. On another
   engine it keeps the raw value in place (so the engine's tools still work) but tells you
   plainly that the engine still receives the key.

4. **The agent works with the handle (Claude engines).** When the agent writes a shell command
   or file containing the handle, a PreToolUse hook substitutes the real value at execution —
   before the command runs — so the secret reaches its destination without appearing in the
   chat. Once the session ends, a log-scrub also cleans the value out of the Claude CLI's own
   local transcript.

5. **Turn it off.** Toggle **secretHandleEnabled** off in Settings; pasted secrets are then
   treated as plain text (the previous behavior).

## How it behaves

### What problem it solves

Normally, if you paste an API key directly into a chat message, that value lives in your history
forever: in the database, in search indexes, in exports, and — for Claude engines — in the Claude
CLI's own local transcript. Secret Paste Guard intercepts the secret before it gets that far. On
Claude-based engines the model only ever sees a placeholder like `AMCSECRET-a3f9b2…`; the real
value is substituted in privately, at execution time, by a hook that runs outside the
conversation. On other engines the value still reaches the engine, but Omniscio keeps it out of
your saved, searchable history.

### The guarantee — three honest tiers by engine

**Every engine:** Omniscio's own stored history is always scrubbed — the raw value is never
written to `conversation_messages`, never indexed for search, never included in exports, and
never shown in the live chat (a persistence scrub strips it even if an agent echoes it back).

**Tier 1 — Claude-based engines (the native Claude CLI plus the vendors that reuse it):** the
model never sees the value. The composer swaps your paste for a handle, and the PreToolUse hook
substitutes the real value only at execution. A log-scrub also cleans the value out of the Claude
CLI's own local transcript once the session's process exits.

**Tier 2 — codex / gemini / cursor (planned fast-follow):** server-side secret tools will let
these engines use a stored key without the model seeing the raw value. Until that lands they run
in the partial tier below.

**Tier 3 — every other engine:** partial protection. The key is kept in the composer so the
engine's tools still work, but it is stored so Omniscio's saved history is scrubbed — the engine
(and its own logs) still receive the value, and the offer says so honestly.

**Honest residual (inherent):** for the secret to be *usable*, it must be reachable at execution,
so an actively-compromised or prompt-injected agent could still use or exfiltrate it. The
guarantee is "never *passively* persisted in Omniscio history; scrubbed from the local Claude
transcript" — not "immune to an actively-hijacked agent," the same trade-off other
credential-proxy patterns accept.

### Scope

- **Full substitution (Tier 1) = the Claude-binary family** — the native Claude CLI plus the
  vendors that reuse it. SSH/remote is out of scope.
- **codex / gemini / cursor (Tier 2)** get server-side secret tools as a planned fast-follow;
  until then they run in the partial tier.
- **Every other engine (Tier 3)** gets partial protection — Omniscio's saved history is scrubbed,
  but the engine still receives the value.
- **Web-form fill** via the AI Browser (`type`/`fill_form` with a `secretHandle`) resolves the
  value server-side.
- The feature is desktop-only. The stash channel that accepts the secret is not reachable from
  the mobile or web bridge (blocked by design — a headless secret-injection endpoint would be a
  security risk).

### Where secrets are stored

Secrets are kept in a per-session, file-based store at `<userData>/session-secrets/<id>.json`,
encrypted with Electron's OS keyring (`safeStorage` — Windows DPAPI / macOS Keychain / Linux
libsecret). If `safeStorage` is unavailable, secrets are kept in memory only and never written
to disk as plaintext. The store is cleared when the session is deleted or reaches its retention
limit. There is no database table; no migration was added.

### See also

- [INDEX.md](INDEX.md)
- [api-keys.md](api-keys.md) — manage and view stored API keys and secrets in one inventory
- [automation-credentials.md](automation-credentials.md) — saved credentials for v2 automation actions (a separate, complementary secret-storage layer)
- [git-guardrails.md](git-guardrails.md) — the PreToolUse hook system Secret Paste Guard composes alongside

## For agents

### For AI agents

- **Detection:** the shared `detectPotentialSecret` classifier flags a pasted value as
  secret-shaped; negative results pass through as normal text.
- **Engine-aware offer:** `SESSION_STASH_SECRET` (IPC) accepts a secret and returns the handle
  plus a `swap` flag = `usesClaudeBinary(provider)`. The renderer swaps the value for the handle
  ONLY when `swap` is true (Tier 1); otherwise it keeps the raw value (Tier 3 partial), so a
  non-substituting engine is never handed a handle it cannot expand
  ([secret-stash-apply.ts](../../src/renderer/src/features/sessions/useSessionPanel/secret-stash-apply.ts)).
  The channel returns only the handle (never the value), is a CLI-parity exemption, and is in
  `BLOCKED_CHANNELS`.
- **Substitution hook (Tier 1):** a bundled `PreToolUse` hook
  ([resources/secret-substitution/secret-substitution.mjs](../../resources/secret-substitution/secret-substitution.mjs))
  fast-paths when no `AMCSECRET-` is present. On any error or timeout it emits no output and
  exits 0 — the literal handle stays and the action fails harmlessly (fail-safe, never a leak).
  A co-resident `deny` hook from git-guardrails still wins (deny is terminal).
- **Transcript log-scrub (Tier 1):** when a session's Claude CLI process EXITS (the `sessionEnd`
  bus — file closed, safe to rewrite), the value the hook injected (which Claude logs into its own
  `~/.claude` transcript) is rewritten back to its handle
  ([transcript-secret-scrub.ts](../../src/main/services/secret-store/transcript-secret-scrub.ts)):
  raw + JSON-escaped forms, per-line integrity revert (never invalid JSON), atomic temp+rename,
  self-gated on `hasSecrets`, kill-switch `AMC_DISABLE_SECRET_TRANSCRIPT_SCRUB`.
- **Resolve route:** `POST /session/secret/resolve`
  ([src/main/services/cli/cli-server-secret-routes.ts](../../src/main/services/cli/cli-server-secret-routes.ts))
  404s when the feature is hidden, requires bearer auth, and expands handles only for the
  `X-AMC-Source-Session-Id` session — never cross-session.
- **Gate:** the spawn wiring checks both `isUnreleasedFeatureVisible('secret-handle', …)` AND
  `resolveSecretHandleEnabled(…)` in
  [src/main/process/secret-handle-gate.ts](../../src/main/process/secret-handle-gate.ts); the
  renderer composer offer checks `isSecretHandleActiveInRenderer(settings)` — both key on the
  `secretHandleEnabled` setting (not visibility), so there's zero per-tool-call/paste cost when off.
- **Handle format:** `AMCSECRET-<>=12 hex chars>` (`SECRET_HANDLE_RE` / `makeSecretHandle`).
- **Persistence scrub (every engine):** `redactSessionSecrets(sessionId, content)` runs inside
  `prepareAddMessage` ([src/main/db/queries-messages/write.ts](../../src/main/db/queries-messages/write.ts))
  before any cap, derive, index, or store step AND before the returned message content reaches
  the renderer — so a value an agent echoes is stripped from both the database and the live UI.
- **Guarding tests:**
  [secret-detection.test.ts](../../tests/unit/shared/secret-detection.test.ts),
  [session-secret-store.test.ts](../../tests/unit/services/secret-store/session-secret-store.test.ts),
  [secret-handle-gate.test.ts](../../tests/unit/process/secret-handle-gate.test.ts),
  [transcript-secret-scrub.test.ts](../../tests/unit/services/secret-store/transcript-secret-scrub.test.ts),
  [transcript-scrub-on-session-end.test.ts](../../tests/unit/services/secret-store/transcript-scrub-on-session-end.test.ts),
  [secret-stash-apply.test.ts](../../tests/unit/features/sessions/secret-stash-apply.test.ts).

## Related

[api-keys.md](api-keys.md) covers managing and viewing stored API keys in one inventory, [automation-credentials.md](automation-credentials.md) the separate and complementary secret-storage layer for automation actions, [git-guardrails.md](git-guardrails.md) the hook system this feature composes alongside, and [INDEX.md](INDEX.md) is the library index.
