> ## Documentation Index
> Fetch the complete documentation index at: https://docs.argentos.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Encrypted Secrets

> AES-256-GCM encryption for API keys and credentials at rest, with OS keychain integration and PostgreSQL secret store.

## Overview

ArgentOS stores API keys, service credentials, and authentication tokens using AES-256-GCM encryption. Secrets are encrypted before they hit disk or database -- the system never stores plaintext credentials in any persistent location.

The encryption architecture has three layers:

1. **Master key** -- Stored in the OS keychain (macOS Keychain, Linux secret-service, Windows Credential Vault)
2. **Encryption engine** -- AES-256-GCM with random IVs and authentication tags
3. **Storage backends** -- JSON files (legacy) and PostgreSQL tables (current)

## Encryption Format

All encrypted values use a self-describing format:

```
enc:v1:<iv-hex>:<authTag-hex>:<ciphertext-hex>
```

| Component        | Description                                 |
| ---------------- | ------------------------------------------- |
| `enc:v1`         | Version prefix for format identification    |
| `iv-hex`         | 12-byte initialization vector (hex-encoded) |
| `authTag-hex`    | GCM authentication tag (hex-encoded)        |
| `ciphertext-hex` | Encrypted payload (hex-encoded)             |

Example:

```
enc:v1:a1b2c3d4e5f6a7b8c9d0e1f2:1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d:8f7e6d5c4b3a...
```

<Info>
  The version prefix (`enc:v1`) enables future format upgrades without breaking existing encrypted values.
</Info>

## AES-256-GCM

The system uses AES-256-GCM (Galois/Counter Mode), which provides both confidentiality and authenticity:

* **256-bit key** -- Derived from the master key stored in the OS keychain
* **12-byte random IV** -- Generated fresh for every encryption operation using `crypto.randomBytes()`
* **Authentication tag** -- GCM produces a tag that detects any tampering with the ciphertext
* **No padding needed** -- GCM is a stream cipher mode, so no PKCS7 padding is required

```typescript theme={null}
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";

const ALGORITHM = "aes-256-gcm";
const IV_BYTES = 12;

function encryptSecret(plaintext: string): string {
  const key = getMasterKey();
  const iv = randomBytes(IV_BYTES);
  const cipher = createCipheriv(ALGORITHM, key, iv);
  let encrypted = cipher.update(plaintext, "utf8", "hex");
  encrypted += cipher.final("hex");
  const authTag = cipher.getAuthTag().toString("hex");
  return `enc:v1:${iv.toString("hex")}:${authTag}:${encrypted}`;
}
```

## OS Keychain Integration

The 256-bit master key is stored in the operating system's native credential store:

| Platform | Backend                                        |
| -------- | ---------------------------------------------- |
| macOS    | Keychain Services (`security` CLI)             |
| Linux    | Secret Service API (GNOME Keyring, KDE Wallet) |
| Windows  | Credential Vault                               |

The master key is generated once on first use and stored under the service name `argentos`. It never appears in configuration files, environment variables, or logs.

### Key Generation

On first encryption operation, if no master key exists in the keychain:

<Steps>
  <Step title="Generate random bytes">
    Generate 32 random bytes using `crypto.randomBytes(32)`
  </Step>

  <Step title="Store in keychain">
    Store in the OS keychain under the `argentos` service
  </Step>

  <Step title="Cache in memory">
    Cache in memory for the duration of the process
  </Step>
</Steps>

### Key Retrieval

On subsequent operations:

1. Check in-memory cache
2. If not cached, read from OS keychain
3. Cache for process lifetime

## Backward Compatibility

The decrypt function accepts both encrypted and plaintext values:

```typescript theme={null}
function decryptSecret(value: string): string {
  if (!value.startsWith("enc:v1:")) {
    return value; // Plaintext — backward compatible
  }
  // ... decrypt ...
}
```

<Tip>
  This enables gradual migration. Existing plaintext values in config files and databases continue to work. They are re-encrypted with AES-256-GCM when next written.
</Tip>

## PostgreSQL Secret Store

When running with PostgreSQL backend (dual-write or postgres mode), secrets are stored in two PG tables:

### Service Keys Table

Stores API keys and service credentials:

```sql theme={null}
CREATE TABLE service_keys (
  id TEXT PRIMARY KEY,
  variable TEXT UNIQUE NOT NULL,
  name TEXT NOT NULL,
  encrypted_value TEXT NOT NULL,
  service TEXT,
  category TEXT,
  enabled BOOLEAN NOT NULL DEFAULT true,
  source TEXT,
  allowed_roles TEXT[] DEFAULT '{}',
  allowed_agents TEXT[] DEFAULT '{}',
  allowed_teams TEXT[] DEFAULT '{}',
  deny_all BOOLEAN NOT NULL DEFAULT false,
  created_at TIMESTAMPTZ NOT NULL,
  updated_at TIMESTAMPTZ NOT NULL
);
```

Key fields:

| Field             | Description                                        |
| ----------------- | -------------------------------------------------- |
| `variable`        | Environment variable name (e.g., `OPENAI_API_KEY`) |
| `encrypted_value` | AES-256-GCM encrypted value                        |
| `service`         | Service identifier (e.g., `openai`, `anthropic`)   |
| `category`        | Grouping category (e.g., `llm`, `channel`, `tool`) |
| `allowed_roles`   | Roles that can access this key                     |
| `allowed_agents`  | Agent IDs that can access this key                 |
| `allowed_teams`   | Team IDs that can access this key                  |
| `deny_all`        | If true, key is inaccessible to all agents         |

### Auth Credentials Table

Stores authentication profiles (OAuth tokens, API keys with metadata):

```sql theme={null}
CREATE TABLE auth_credentials (
  id TEXT PRIMARY KEY,
  profile_id TEXT UNIQUE NOT NULL,
  provider TEXT NOT NULL,
  credential_type TEXT NOT NULL,  -- 'api_key', 'oauth', 'token'
  encrypted_payload TEXT NOT NULL,
  email TEXT,
  enabled BOOLEAN NOT NULL DEFAULT true,
  last_used_at TIMESTAMPTZ,
  cooldown_until TIMESTAMPTZ,
  error_count INTEGER DEFAULT 0,
  created_at TIMESTAMPTZ NOT NULL,
  updated_at TIMESTAMPTZ NOT NULL
);
```

The `encrypted_payload` field contains a JSON object encrypted with AES-256-GCM. This payload includes the actual credential data (API keys, OAuth tokens, refresh tokens) along with provider-specific metadata.

### Access Control

Service keys support fine-grained access control:

* **`allowed_roles`** -- Only agents with matching roles can use the key
* **`allowed_agents`** -- Only specific agent IDs can use the key
* **`allowed_teams`** -- Only agents in specific teams can use the key
* **`deny_all`** -- Emergency kill switch to revoke all access

<Note>
  When all access control arrays are empty, the key is accessible to all agents (open access).
</Note>

## CRUD Operations

### Service Keys

```typescript theme={null}
// List all keys (values decrypted in memory)
const keys = await pgListServiceKeys(sql);

// Resolve a key by variable name
const value = await pgResolveServiceKey(sql, "OPENAI_API_KEY");

// Upsert a key (value encrypted before storage)
await pgUpsertServiceKey(sql, {
  variable: "OPENAI_API_KEY",
  value: "sk-...",
  name: "OpenAI Production",
  service: "openai",
  category: "llm"
});

// Delete a key
await pgDeleteServiceKey(sql, "OPENAI_API_KEY");
```

### Auth Credentials

```typescript theme={null}
// List all credentials (payloads decrypted in memory)
const creds = await pgListAuthCredentials(sql);

// Get a credential by profile ID
const cred = await pgGetAuthCredential(sql, "anthropic:main");

// Upsert a credential
await pgUpsertAuthCredential(sql, {
  profileId: "anthropic:main",
  provider: "anthropic",
  credentialType: "api_key",
  payload: { key: "sk-ant-..." },
  email: "user@example.com"
});
```

## Migration from JSON

For installations migrating from JSON-file storage to PostgreSQL, Phoenix provides a one-shot migration function:

```typescript theme={null}
const result = await migrateSecretsToPg(sql, {
  serviceKeysPath: "~/.argentos/service-keys.json",
  authProfilesPath: "~/.argentos/agents/main/agent/auth-profiles.json"
});

// result: {
//   serviceKeys: { migrated: 15, skipped: 0 },
//   authCredentials: { migrated: 3, skipped: 0 }
// }
```

The migration:

1. Reads the JSON files
2. Decrypts any already-encrypted values (using the same master key)
3. Re-encrypts for PG storage
4. Upserts into the PostgreSQL tables
5. Reports migration counts

Existing PG entries are updated (not duplicated) via `ON CONFLICT ... DO UPDATE`.

## Key Rotation

To rotate the master key:

<Steps>
  <Step title="Export all secrets">
    They are decrypted in memory during export
  </Step>

  <Step title="Delete the old master key from the OS keychain" />

  <Step title="Re-encrypt all secrets">
    A new master key is auto-generated
  </Step>

  <Step title="Update all storage backends" />
</Steps>

<Note>
  Currently, key rotation is a manual process. The system does not support automatic key rotation.
</Note>

## Security Properties

| Property               | Status                                             |
| ---------------------- | -------------------------------------------------- |
| Encryption at rest     | AES-256-GCM for all persistent secrets             |
| Key management         | OS keychain (not in files or env vars)             |
| Authentication         | GCM auth tag prevents tampering                    |
| IV reuse prevention    | Random 12-byte IV per encryption                   |
| Backward compatibility | Plaintext values accepted, encrypted on next write |
| Access control         | Per-key role/agent/team restrictions (PG only)     |
| Audit trail            | `created_at` / `updated_at` timestamps             |

## Key Files

| File                           | Description                          |
| ------------------------------ | ------------------------------------ |
| `src/infra/secret-crypto.ts`   | AES-256-GCM encrypt/decrypt (63 LOC) |
| `src/infra/pg-secret-store.ts` | PostgreSQL secret store (365 LOC)    |
| `src/infra/keychain.ts`        | OS keychain master key management    |
| `src/infra/service-keys.ts`    | JSON-file service keys (legacy)      |
