---
title: Automation Credentials (saved secrets for v2 actions)
---

# Automation Credentials (saved secrets for v2 actions)

## What it is

The **Automation Credentials** manager is where you store the API tokens, webhook URLs, service-account keys, and SMTP passwords that the new v2 automation actions need at run time. Without it, every Slack / GitHub / SMTP action would have to embed its secret in the action's JSON config — which would leak it into the database row, into automation backups, and into the inbox approval payload an external AI sees before you click Approve. Instead, an action carries only a `credentialId` reference, and the executor resolves the real secret from this manager (decrypted in the main process) at the moment it needs it.

## Where to find it

### How to use it

1. **Open the panel.** Go to **Settings &rarr; Automations &rarr; Credentials** (the Credentials tab of the Automations hub, key icon). The header explains what credentials are and confirms that secrets are encrypted using the OS keyring.

2. **Decide whether you need the high-risk gate.** The first control is a toggle labeled **Allow high-risk credential kinds**. It's off by default. Two of the nine credential kinds are classified high-risk because they hand out broad data access or outbound-abuse potential: `google-service-account` (broad GCP / Workspace access on the project the key belongs to) and `smtp` (full outbound mail relay, useful as a spam / phishing vector if leaked). With the toggle off, you can still view, rename, and delete an existing high-risk credential, but **creating a new one or rotating its secret is blocked at the IPC handler with a clear error message**. The other seven kinds (Slack, GitHub, Sentry, Discord webhook, Notion, Telegram, generic HTTP auth) are scoped enough to never be gated.

3. **Add a credential.** Click **Add credential** at the bottom of the list. A modal titled `New credential` opens with three fields: a **Kind** dropdown populated from the registry (`slack`, `github`, `sentry`, `discord-webhook`, `notion-token`, `telegram-bot`, `http-auth`, `google-service-account`, `smtp`), a friendly **Name** so you can find it later, and a per-kind **secret editor** that swaps fields based on the kind you pick:
   - `slack` / `telegram-bot` &mdash; single `Bot token` password field
   - `github` / `sentry` / `notion-token` &mdash; single `Token` password field
   - `discord-webhook` &mdash; `Webhook URL` text field
   - `http-auth` &mdash; `Scheme` selector (`bearer` / `basic` / `header`) with the matching sub-fields underneath
   - `google-service-account` &mdash; `Client email` plus a tall monospaced `Private key` textarea
   - `smtp` &mdash; `Host`, `Port`, `TLS` checkbox, `Username`, `Password`

   Press **Save** (or **Ctrl+Enter** / **Cmd+Enter** anywhere inside the modal). The server validates the secret shape against the per-kind Zod schema before encrypting; a mismatch surfaces inline as a red error message in the modal &mdash; no save happens.

4. **Use it in an action.** Open an automation in the v2 editor. For any action that has a `credentialKind` field (currently `slack.send-message`, `discord.send-message`, `email.send-via-smtp`, `github.add-comment`, `github.create-issue`, `google-sheets.append-row`, `http.request`, `notion.append-block`, `notion.create-page`, and `telegram.send-message`), the config panel renders a compact `<select>` &mdash; the **Credential picker** &mdash; with the placeholder `— Pick a saved credential —` and one option per credential of that kind. If the high-risk gate is off, gated rows render disabled with a trailing ` (blocked)` suffix and a callout below the picker links you back to the toggle. If you have no credentials of that kind yet, the picker shows an empty-state with an **Add credential** button that deep-links to this Settings page.

5. **Rotate or rename a credential.** Each list row has three buttons: **Rename**, **Rotate**, **Delete**. **Rename** opens an inline name-only editor &mdash; never gated. **Rotate** opens an inline secret-only editor with the same per-kind fields as the create modal; rotating a high-risk credential re-checks the gate. **Delete** prompts you with a danger-variant confirmation explaining that any automations using the credential will fail until you pick a replacement.

6. **The id is what gets referenced.** Actions point at credentials by their UUID, not by name &mdash; so renaming a credential is safe (no automations break) but deleting one is destructive. When the credential is missing at run time, the resolver throws a clear, user-actionable error into the automation run log instead of silently no-op'ing with an empty secret.

## How it behaves

### How it works

The settings panel lives at [../../src/renderer/src/features/settings/sections/automation-credentials/AutomationCredentialsSettings.tsx](../../src/renderer/src/features/settings/sections/automation-credentials/AutomationCredentialsSettings.tsx) and is the sole consumer of `useAutomationCredentialsStore()` from [../../src/renderer/src/stores/automation-credentials-store.ts](../../src/renderer/src/stores/automation-credentials-store.ts). The store is a thin Zustand wrapper over four IPC handlers &mdash; `automation:credential-list` / `-create` / `-update` / `-delete` &mdash; defined in [../../src/main/ipc/automation-credential-handlers.ts](../../src/main/ipc/automation-credential-handlers.ts). The store deliberately re-fetches after every mutation rather than merging optimistically: the encrypted-blob round-trip means a parallel un-encrypted shadow copy would be the only way to merge locally, and the operations are infrequent enough that a refetch is cheap.

The nine per-kind Zod schemas + the high-risk allow-list live in [../../src/shared/automation-credential-schemas.ts](../../src/shared/automation-credential-schemas.ts) so both processes import the same source of truth. The credential service at [../../src/main/services/automation/legacy/automation-credentials.ts](../../src/main/services/automation/legacy/automation-credentials.ts) layers four guarantees on top of the dumb persistence layer:

- **Encryption** &mdash; delegated to the canonical `encryptCredential` / `decryptCredential` helpers, which prefix the ciphertext with `enc:` and use Electron's `safeStorage` (Windows DPAPI / macOS Keychain / Linux libsecret). When the keyring is unavailable, encryption **throws** &mdash; the service fails CLOSED and nothing is persisted as plaintext. A wrapped, user-actionable error message replaces the internal F065 jargon.
- **Per-kind shape validation** &mdash; the Zod schema for the credential's kind is parsed on create, on every secret-bearing update, and **again on resolve** as defense in depth against a corrupt or pre-schema blob.
- **High-risk write-gate** &mdash; checked in BOTH the IPC handler (so the error envelope is shaped by the handler, not the service) AND inside the service (so non-IPC callers can't bypass it). Both layers fail CLOSED on a missing setting flag.
- **Secrets never cross IPC** &mdash; `listCredentialsMetadata` returns id / kind / name / timestamps only, augmented in the handler with `isHighRisk: boolean`. The store carries that shape verbatim; the unit test cross-checks that `JSON.stringify(state)` doesn't contain a secret string after a create.

The **Credential picker** component at [../../src/renderer/src/features/automations/components/CredentialPicker.tsx](../../src/renderer/src/features/automations/components/CredentialPicker.tsx) is a read-only sibling of the settings panel &mdash; it never creates, edits, or deletes, only filters the store to its `kind` prop and renders the gated rows as `disabled` `<option>`s. The picker mirrors the IPC-level write-gate for visual feedback but **never enforces it** &mdash; the handler is the sole authority. The empty-state's `Add credential` button deep-links via `navigateToSettingsProject({ section: 'automation-credentials' })`.

Resolution happens at run start, never mid-run: the executor calls `resolveCredential(id, { automationId, runId })`, the service decrypts, JSON-parses, re-validates, and audit-logs the successful resolve to `credential_access_log`. Failed resolves are NOT logged &mdash; nothing usable was returned, so there's nothing to record as "accessed"; the thrown error lands in the automation run log instead.

### Where this fits in the v2 automation flow

This page is the secrets-storage layer that the v2 action chain depends on. The integration-by-id model (action references `credentialId`, never the secret itself) is what lets credentials be rotated without editing every automation, and what keeps the inbox approval payload clean &mdash; when an external AI queues an automation create through the CLI server, the approval modal shows you the `credentialId` reference, not the secret. See [automations-and-auto-replies.md](automations-and-auto-replies.md) for the full action-chain model, the cron-like / message / session-state triggers, and the per-rule preset catalog.

## For agents

### CLI GET routes — metadata only

External AI agents (via the omniscio-control skill) can enumerate credentials over the CLI control server. These routes return **metadata only** — the encrypted secret is never present in any response:

| Method | Path                          | What it returns                                                      |
| ------ | ----------------------------- | -------------------------------------------------------------------- |
| `GET`  | `/automation/credentials`     | Array of all credentials: `{ id, kind, name, createdAt, updatedAt }` |
| `GET`  | `/automation/credentials/:id` | Single credential by id; `404` on unknown id                         |

Use `GET /automation/credentials` to discover existing credential IDs before referencing one in an automation action. The `id` from this list is what you pass as `credentialId` in a `cli_session` or `forward_email` action chain.

## Related

- [automations-and-auto-replies.md](automations-and-auto-replies.md) &mdash; the parent feature; v2 actions reference credentials stored here
- [cli-pending-actions.md](cli-pending-actions.md) &mdash; the approval queue an external AI uses; automation-rule creates ride on the separate per-row `approval_status='pending'` flag described there
