# How Paperclip Manages Secrets and Injects Them Into Agent Execution Contexts

> Discover how Paperclip securely manages and injects secrets into agent execution contexts. Learn about encrypted storage, pluggable providers, and automatic log redaction for enhanced security.

- Repository: [Paperclip/paperclip](https://github.com/paperclipai/paperclip)
- Tags: how-to-guide
- Published: 2026-08-16

---

**Paperclip treats every secret as a first-class resource with encrypted storage, pluggable providers, strict authorization checks, and automatic log redaction to ensure secrets never leak in agent runtime environments.**

The open-source Paperclip platform (`paperclipai/paperclip`) provides a comprehensive **secrets management system** that secures sensitive data from storage through injection into agent execution contexts. This article examines the complete lifecycle—from provider abstraction and binding configuration to runtime resolution and automatic redaction—based on the actual source code implementation.

## Secret Storage Architecture and Provider Abstraction

Paperclip's secrets system begins with a **pluggable provider architecture** that abstracts how secrets are encrypted, stored, and retrieved.

### The Secret Service Core

The `secretService` factory in [`server/src/services/secrets.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/secrets.ts) serves as the central orchestrator. It exposes CRUD operations and implements the core resolution logic used throughout the platform.

Providers are registered in [`server/src/secrets/provider-registry.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/secrets/provider-registry.ts) and must implement the common interface defined in [`packages/shared/src/types/secrets.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/shared/src/types/secrets.ts). The default implementation is the **local encrypted provider** ([`server/src/secrets/local-encrypted-provider.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/secrets/local-encrypted-provider.ts)), which:

- Encrypts secret values using AES encryption
- Derives the master key from the `PAPERCLIP_SECRETS_MASTER_KEY` environment variable or a file on disk
- Locates the key file via [`server/src/home-paths.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/home-paths.ts) (default: `~/.paperclip/instances/{instance}/secrets/key`)

Alternative providers include AWS Secrets Manager and GCP Secret Manager implementations, selectable per-secret.

## Resolving Secret Values: The Internal Flow

When a secret value is needed, `resolveSecretValueInternal` in [`secrets.ts`](https://github.com/paperclipai/paperclip/blob/main/secrets.ts) executes a multi-step retrieval process:

1. Fetches the secret row and validates its status
2. Identifies the requested version (or "latest")
3. Invokes the provider's `resolveVersion` method to obtain plaintext
4. Records access via `recordAccessEvent` for audit compliance

The plaintext value is never cached beyond the immediate request, and every access is logged to the **secret-access audit log**.

## Agent-Specific Secret Access and Authorization

Agents cannot directly request arbitrary secrets. Instead, they use `resolveSecretValueForAgentAccess`, which enforces a strict authorization protocol.

### Required Credentials

Agents must present:

- A valid **agent JWT** (`agent_jwt`)
- A verified **heartbeat run ID** from the `heartbeatRuns` table

The function validates the run exists, confirms the agent's authorization through `authorizationService`, and verifies the binding context.

### Binding Context Verification

Bindings define which secrets an agent can access and where they map in the runtime environment. The system checks:

- **Config path patterns**: `env.*` for environment variables or `access.*` for API access
- **Binding ID validation**: If supplied, the binding must appear in the run's manifest (`paperclipSecrets.manifest`)

```typescript
// Resolve a secret for an agent (called from an adapter)
import { secretService } from "@paperclipai/server/src/services/secrets";

async function getOpenAiKey(
  db: Db,
  companyId: string,
  agentId: string,
  runId: string,
) {
  const ctx = {
    agentId,
    configPath: "env.OPENAI_API_KEY",           // configPath is the binding key
    heartbeatRunId: runId,
    actorSource: "agent_jwt" as const,
    registerForRedaction: async (value) => {/* store for later redaction */},
  };

  // Returns { value: string, version: number }
  const { value } = await secretService(db).resolveSecretValueForAgentAccess(
    companyId,
    "secret-openai",   // secretId from DB
    "latest",
    ctx,
  );
  return value; // plaintext key that the adapter can set in the process env
}

```

## Injecting Secrets Into Agent Runtime Environments

Once authorized and resolved, secrets flow into agent execution through a controlled injection mechanism.

The `registerForRedaction` callback—passed in the binding context—captures the plaintext value for later log scrubbing. The adapter code then places the secret into the agent's environment, typically:

- Setting `process.env[KEY]` in Docker containers
- Passing values via sandbox APIs
- Injecting into configuration files for isolated runtimes

The adapter layer (implemented in `adapter-utils` or provider-specific adapters) handles the final delivery without exposing secrets to unauthorized components.

## Automatic Secret Redaction in Logs

Paperclip prevents accidental secret exposure through **runtime redaction** that intercepts all logged output.

The `run-secret-redaction` module ([`server/src/services/run-secret-redaction.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/run-secret-redaction.ts)) maintains per-run registries of secret fingerprints in the `contextSnapshot`. When an agent produces output:

1. `createRunSecretRedactionRegistry` initializes helpers for the run
2. `redactForRun` and `redactForIssue` scan for registered secret values
3. Matches are replaced with `REDACTED_EVENT_VALUE` before persistence or API response

```typescript
// Register a secret value for redaction (used by the runtime when logging)
import { createRunSecretRedactionRegistry } from "@paperclipai/server/src/services/run-secret-redaction";

async function logSomething(
  db: Db,
  companyId: string,
  runId: string,
  msg: string,
) {
  const redactor = createRunSecretRedactionRegistry(db);
  const safeMsg = await redactor.redactForRun(companyId, runId, msg);
  console.log(safeMsg); // secret values replaced with "***REDACTED***"
}

```

## Binding Secrets to Agents

Before runtime resolution, secrets must be explicitly bound to their consumers. The `companySecretBindings` table stores these relationships with metadata about target type, target ID, and config path mapping.

```typescript
// Adding a binding for an agent (e.g., via API)
import { secretService } from "@paperclipai/server/src/services/secrets";

await db.insert(companySecretBindings).values({
  companyId,
  secretId,
  targetType: "agent",
  targetId: agentId,
  configPath: "env.OPENAI_API_KEY",
  // other fields …
});

```

The `assertBindingContext` function in [`secrets.ts`](https://github.com/paperclipai/paperclip/blob/main/secrets.ts) enforces these constraints during resolution, ensuring agents cannot access unbound secrets even if they bypass higher-level checks.

## Complete Secret Lifecycle in Paperclip

| Step | Implementation | Key File |
|------|----------------|----------|
| **Create/Import** | Provider-specific encryption | `localEncryptedProvider.createSecret` or cloud provider equivalents |
| **Persist** | Database rows with encrypted material | `secretService` CRUD in [`server/src/services/secrets.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/secrets.ts) |
| **Bind** | Config path mapping to consumer | `companySecretBindings` table; `assertBindingContext` |
| **Resolve for Agent** | JWT + run validation → binding check → provider resolution | `resolveSecretValueForAgentAccess` |
| **Inject** | Adapter receives plaintext, sets environment | Adapter implementations (Docker, sandbox, etc.) |
| **Redact** | Fingerprint-based log scrubbing | [`run-secret-redaction.ts`](https://github.com/paperclipai/paperclip/blob/main/run-secret-redaction.ts) |

## Key Implementation Files

- **[`server/src/services/secrets.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/secrets.ts)** — Central secret service with resolution, validation, and audit logging
- **[`server/src/secrets/provider-registry.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/secrets/provider-registry.ts)** — Provider registration and lookup
- **[`server/src/secrets/local-encrypted-provider.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/secrets/local-encrypted-provider.ts)** — Default AES encryption provider with master key management
- **[`server/src/services/run-secret-redaction.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/run-secret-redaction.ts)** — Runtime log redaction registry
- **[`server/src/home-paths.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/home-paths.ts)** — Master key file location resolution
- **[`packages/shared/src/types/secrets.ts`](https://github.com/paperclipai/paperclip/blob/main/packages/shared/src/types/secrets.ts)** — Core type definitions (`SecretProvider`, `EnvBinding`)
- **[`server/src/services/authorization.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/services/authorization.ts)** — Authorization layer for agent access control
- **Heartbeat runs schema** — Run context snapshots containing redaction state

## Summary

- **Encrypted at rest**: All secrets use AES encryption (local) or cloud-native encryption (AWS/GCP) with master key protection
- **Bound before access**: Explicit `companySecretBindings` rows with config path mapping prevent unauthorized retrieval
- **Strict agent authorization**: JWT + verified run ID + binding manifest verification required for every resolution
- **Automatic runtime redaction**: All secrets registered during injection are scrubbed from logs via fingerprint matching
- **Full audit trail**: Every access recorded with context for compliance and security monitoring

## Frequently Asked Questions

### How does Paperclip encrypt secrets when using the default local provider?

The local encrypted provider in [`server/src/secrets/local-encrypted-provider.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/secrets/local-encrypted-provider.ts) uses AES encryption with a master key loaded from either the `PAPERCLIP_SECRETS_MASTER_KEY` environment variable or a file located via [`server/src/home-paths.ts`](https://github.com/paperclipai/paperclip/blob/main/server/src/home-paths.ts). The provider encrypts secret values before storage and decrypts them only during resolution, with the master key never persisted in the database.

### What prevents an agent from accessing secrets belonging to another agent?

Three layers enforce isolation: the `authorizationService` validates the agent JWT, the heartbeat run ID is verified against the `heartbeatRuns` table, and the binding context (`assertBindingContext`) confirms the secret is explicitly bound to that specific agent via `companySecretBindings`. If the binding ID is supplied, it must also appear in the run's `paperclipSecrets.manifest`.

### How does Paperclip ensure secrets don't appear in agent logs or API responses?

Before any secret value is returned to an agent, the `registerForRedaction` callback captures its fingerprint. The [`run-secret-redaction.ts`](https://github.com/paperclipai/paperclip/blob/main/run-secret-redaction.ts) module maintains these fingerprints in the run's `contextSnapshot`. All logging passes through `redactForRun` or `redactForIssue`, which replace any registered secret values with `REDACTED_EVENT_VALUE` before storage or transmission.