Files
PaperClipAI/doc/SECRETS-AWS-PROVIDER.md
T
ad961227f5 feat(secrets): add user-specific runtime secrets (#8825)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agent runs often need provider credentials, API tokens, and other
environment-bound secrets.
> - Company-level secrets work for shared credentials, but they do not
model values that should differ by human operator.
> - Without a user-scoped model, a run can dispatch without knowing
whether the responsible human has supplied the needed value.
> - Paperclip also needs run attribution to make those user-scoped
runtime checks deterministic and auditable.
> - This pull request adds user-specific secret definitions, per-user
values, environment bindings, responsible-user attribution, and runtime
resolution gates.
> - The benefit is that teams can define the secret once, let each user
provide their own value, and block runs before dispatch when required
user secrets or active definitions are unavailable.

## Linked Issues or Issue Description

Refs #224
Refs #6057

This PR implements user-specific secret support as a core
secret-management capability rather than a one-off adapter setting. It
is related to existing public work on company secrets UI and runtime
secret refs, but is distinct because the value is owned by the
responsible user and resolved at run dispatch time.

Related PR search before opening found existing secrets work such as
#1550, #8256, #8614, #8634, and #8647; none of those add the full
user-secret definition/value/runtime gate covered here.

## What Changed

- Added user-secret definitions and per-user "My secrets" values,
keeping stored values out of access metadata.
- Added `user_secret_ref` environment bindings and UI affordances to
pick them alongside existing secret refs.
- Added responsible-user runtime resolution so user-secret refs resolve
against the human responsible for the run.
- Added pre-dispatch missing-secret gates so runs fail before adapter
dispatch when required user values are absent or definitions are
inactive.
- Added low-trust allowlist hardening for user-secret runtime access.
- Added issue, routine, run, and agent API key responsible-user
attribution and fail-closed dispatch behavior when attribution cannot be
resolved.
- Added denial-copy mapping so responsible-user authorization failures
surface as actionable run outcomes instead of opaque setup failures.
- Added OpenAPI documentation for the user-secret routes.
- Rebases cleanly on current `master`; migrations were renumbered
incrementally as `0128_user_specific_secrets`,
`0129_agent_api_key_responsible_user`, and
`0130_run_responsible_user_invariant` after upstream `0126`/`0127`
migrations.
- Removed previously committed local design screenshots so the PR
contains code/docs/tests only.

## Verification

- PASS: PR head `2527febd106bcf3ca264ca0da7fca491084192d6` is based on
`paperclipai/paperclip:master`.
- PASS: `git diff --check`
- PASS: `git diff --name-only public/master...HEAD | rg
'^(pnpm-lock\\.yaml|\\.github/workflows/|screenshots/)' || true`
produced no files.
- PASS: migration journal audit confirmed unique indexes through `130`
with tail entries `0126_issue_comment_derived_attribution`,
`0127_environment_custom_images_instance_scoped`,
`0128_user_specific_secrets`, `0129_agent_api_key_responsible_user`, and
`0130_run_responsible_user_invariant`.
- PASS: `pnpm --filter @paperclipai/ui typecheck`
- PASS: `pnpm --filter @paperclipai/server typecheck`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-responsible-user-invariant.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-active-run-output-watchdog.test.ts
src/__tests__/heartbeat-stale-queue-invalidation.test.ts
src/__tests__/heartbeat-workspace-finalize-branch.test.ts
src/__tests__/issue-monitor-scheduler.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-comment-wake-batching.test.ts
src/__tests__/heartbeat-retry-scheduling.test.ts
src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts
src/__tests__/heartbeat-plugin-environment.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/low-trust-red-team-routes.test.ts`
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/secrets-service.test.ts` (55 tests)
- PASS: `pnpm vitest run server/src/__tests__/secrets-routes.test.ts
server/src/__tests__/secrets-service.test.ts` (89 tests after final
Greptile cleanup fixes)
- PASS: `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/heartbeat-issue-liveness-escalation.test.ts` (17 tests
after the final rebase CI fix)
- PASS: focused server Vitest batches covering heartbeat recovery,
project env, plugin env, routines, low-trust, pipelines, monitors,
watchdog, and stale queue paths.
- PASS: GitHub checks are green on
`2527febd106bcf3ca264ca0da7fca491084192d6`, including Typecheck +
Release Registry, Build, General tests, serialized server suites, e2e,
Canary Dry Run, verify, security checks, and Greptile Review.
- PASS: Greptile Review completed successfully on
`2527febd106bcf3ca264ca0da7fca491084192d6` with Confidence Score 5/5,
and GraphQL review-thread audit returned zero unresolved non-outdated
threads.

## Risks

- Runtime behavior now depends on a run having a correct responsible
user; missing or incorrect responsibility assignment can block runs
before adapter dispatch.
- `user_secret_ref` bindings intentionally expose metadata without
values, but UI/API callers may need to handle the new binding kind
explicitly.
- External secret providers and IAM policies are not automatically
provisioned by this PR; operators still need to configure provider-side
access for non-local vaults.
- The PR is broad across db/shared/server/UI/runtime paths, so release
validation should include both API and UI secret workflows before merge.
- The migration renumbering is intentionally incremental after upstream
migrations; the branch migrations use guarded
column/table/index/constraint creation so users who tested the older
draft numbering should not hit duplicate DDL for the existing objects.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

OpenAI Codex, GPT-5-based coding agent (`gpt-5`), Codex local adapter
with shell/tool use and code execution. Context window and internal
reasoning mode are not exposed by the runtime.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 05:58:20 -05:00

16 KiB

AWS Secrets Manager Provider

Operational contract for the hosted aws_secrets_manager secret provider used by Paperclip Cloud.

Scope

  • Hosted provider for Paperclip-managed company and user-scoped secrets when Paperclip Cloud runs on AWS.
  • Source of truth for secret values is AWS Secrets Manager, not Postgres.
  • Paperclip stores only metadata needed for ownership, bindings, version selection, audit, and runtime resolution.
  • AWS provider bootstrap credentials are deployment/runtime credentials, not Paperclip-managed company secrets.
  • Remote import for existing AWS secrets is metadata-only. Preview/import uses AWS inventory metadata and creates Paperclip external references; it does not copy plaintext into Paperclip.
  • Per-company AWS provider vaults (named instances of aws_secrets_manager with their own region, namespace, prefix, KMS key id, and tags) are managed in the board UI under Company Settings → Secrets → Provider vaults. See Provider Vaults for the operator model and Provider Vaults API for the routes. The bootstrap trust model in this document still applies — vault config carries non-sensitive routing metadata only, never AWS credentials.

Bootstrap Trust Model

The AWS provider has a chicken-and-egg boundary: Paperclip cannot use company_secrets to unlock the AWS provider that stores those secrets. The initial AWS trust must exist before the Paperclip server starts.

Allowed bootstrap locations:

  • Infrastructure IAM or workload identity attached to the Paperclip server runtime.
  • Process environment or orchestrator secret store used to start the Paperclip server.
  • Local AWS SDK sources such as AWS_PROFILE, AWS SSO/shared config, web identity, container metadata, or instance metadata.
  • Short-lived shell credentials for local development only.

Do not ask operators to paste AWS root credentials or long-lived IAM user access keys into the Paperclip board UI. Do not store those bootstrap keys in company_secrets.

Paperclip Cloud Bootstrap

Paperclip Cloud must provision the AWS backing resources before any board user can create AWS-backed company secrets:

  1. Create or select the deployment KMS key.
  2. Create the Paperclip server runtime role for the deployment.
  3. Attach a minimum IAM policy scoped to the deployment Secrets Manager prefix and the configured KMS key.
  4. Configure the server runtime with the non-secret provider environment variables below.
  5. Run paperclipai doctor or the provider health endpoint from the deployed runtime and confirm that the provider reports the expected region, prefix, deployment id, KMS setting, and AWS SDK credential source.

Once this is in place, the board UI can create Paperclip-managed AWS secrets and Paperclip will write them under the deployment/company namespace.

Self-Hosted And Local Bootstrap

Self-hosted AWS deployments should use the AWS SDK default credential provider chain. Preferred sources are role-based:

  • EC2 instance profile.
  • ECS task role.
  • EKS IRSA or another OIDC web identity role.
  • AWS SSO/shared config via AWS_PROFILE.

Local development can use:

aws sso login --profile paperclip-dev
AWS_PROFILE=paperclip-dev \
PAPERCLIP_SECRETS_PROVIDER=aws_secrets_manager \
PAPERCLIP_SECRETS_AWS_REGION=us-east-1 \
PAPERCLIP_SECRETS_AWS_DEPLOYMENT_ID=dev-local \
PAPERCLIP_SECRETS_AWS_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/abcd-... \
pnpm dev

Temporary AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY environment credentials are acceptable only as a local break-glass or short-lived test source. They should not be written to Paperclip config, committed to .env files, stored in company_secrets, or used as the default Paperclip Cloud bootstrap path.

Deployment Config

Required environment variables:

PAPERCLIP_SECRETS_PROVIDER=aws_secrets_manager
PAPERCLIP_SECRETS_AWS_REGION=us-east-1
PAPERCLIP_SECRETS_AWS_DEPLOYMENT_ID=prod-us-1
PAPERCLIP_SECRETS_AWS_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/abcd-...

Optional environment variables:

PAPERCLIP_SECRETS_AWS_PREFIX=paperclip
PAPERCLIP_SECRETS_AWS_ENVIRONMENT=production
PAPERCLIP_SECRETS_AWS_PROVIDER_OWNER=paperclip
PAPERCLIP_SECRETS_AWS_ENDPOINT=
PAPERCLIP_SECRETS_AWS_DELETE_RECOVERY_DAYS=30

Naming convention for Paperclip-managed secrets:

paperclip/{deploymentId}/{companyId}/{secretKey}

Tag set for Paperclip-managed secrets:

  • paperclip:managed-by=paperclip
  • paperclip:provider-owner=<owner tag>
  • paperclip:deployment-id=<deployment id>
  • paperclip:company-id=<company id>
  • paperclip:secret-key=<secret key>
  • paperclip:environment=<environment tag>

When the user-secret service creates Paperclip-managed AWS values, keep them under the same deployment/company namespace. The secret-key segment should be non-sensitive and collision-safe for the user-secret value, normally derived from the definition key plus an opaque credential-owner subject. Do not put emails, personal names, OAuth scopes, ticket identifiers, or plaintext credential material in AWS secret names, descriptions, or tags.

For operator-owned external references, prefer a separate approved prefix such as:

paperclip-ext/<environment>/<company-id>/user-secrets/<definition-key>/<opaque-owner-id>

Paperclip stores the external ref and provider version as metadata. Treat those fields as secret-adjacent: they must be redacted from comments, activity logs, run transcripts, issue documents, and broad board views unless a route is explicitly designed to show sanitized provider metadata.

IAM And KMS Assumptions

Launch posture:

  • One Paperclip app role per deployment.
  • One deployment-scoped KMS key per deployment at launch.
  • Future per-company KMS keys remain compatible because Paperclip stores provider refs and version metadata separately from values.

Minimum IAM boundary:

  • Allow secretsmanager:CreateSecret, PutSecretValue, GetSecretValue, and DeleteSecret.
  • Scope resources to the deployment prefix:
arn:aws:secretsmanager:<region>:<account-id>:secret:paperclip/<deployment-id>/*
  • Allow kms:Encrypt, kms:Decrypt, kms:GenerateDataKey, and kms:DescribeKey for the configured deployment CMK.
  • Deny wildcard access outside the deployment prefix.
  • Prefer workload identity / role-based auth. Do not store AWS credentials inline in Paperclip config.

Example minimum policy shape:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PaperclipDeploymentSecrets",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:CreateSecret",
        "secretsmanager:PutSecretValue",
        "secretsmanager:GetSecretValue",
        "secretsmanager:DeleteSecret"
      ],
      "Resource": "arn:aws:secretsmanager:<region>:<account-id>:secret:paperclip/<deployment-id>/*"
    },
    {
      "Sid": "PaperclipDeploymentKms",
      "Effect": "Allow",
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:GenerateDataKey",
        "kms:DescribeKey"
      ],
      "Resource": "arn:aws:kms:<region>:<account-id>:key/<key-id>"
    }
  ]
}

Operational expectation:

  • Paperclip-managed secrets may be deleted only by Paperclip or an operator with equivalent break-glass access.
  • External references may resolve through Paperclip runtime, but Paperclip should not delete the external secret resource.

Remote Import Inventory IAM

Remote import preview needs one additional AWS permission:

{
  "Sid": "PaperclipRemoteSecretInventory",
  "Effect": "Allow",
  "Action": "secretsmanager:ListSecrets",
  "Resource": "*"
}

This is intentionally separate from the managed create/rotate/delete policy. AWS treats ListSecrets as an account/Region inventory action; do not document secret ARNs, names, tags, or AWS request filters as an IAM boundary for it. Use Resource: "*" and decide whether inventory exposure is acceptable for the AWS account and Region behind each provider vault.

Remote import preview/import must not call:

  • secretsmanager:GetSecretValue
  • secretsmanager:BatchGetSecretValue
  • kms:Decrypt

Those permissions are only needed later when a bound runtime resolves an imported external reference. For imported refs, scope read permissions to the operator-approved external prefixes that Paperclip is allowed to consume:

{
  "Sid": "PaperclipResolveImportedExternalReferences",
  "Effect": "Allow",
  "Action": "secretsmanager:GetSecretValue",
  "Resource": [
    "arn:aws:secretsmanager:<region>:<account-id>:secret:<approved-external-prefix>/*"
  ]
}

If selected external secrets use customer-managed KMS keys, also grant kms:Decrypt and kms:DescribeKey on those keys. Keep managed write/delete permissions scoped to paperclip/<deployment-id>/*; do not broaden them for remote import.

The same rule applies to user-scoped external refs. Paperclip derives the responsible user and credential owner before resolution, but AWS IAM still decides whether the runtime role can read the selected ARN/path. If the runtime role can read a broad external prefix, that access is broader than Paperclip's metadata policy. Use dedicated provider vaults, accounts, Regions, prefixes, KMS keys, or runtime roles when provider-side isolation must match a user-secret boundary.

Safe scoping guidance:

  • Prefer one Paperclip runtime role per environment/account.
  • Point provider vaults at the intended AWS account and Region instead of a broad central admin role.
  • Enable ListSecrets only in accounts where inventory exposure is acceptable.
  • Keep preview/import board-only; agent API keys must not call these routes.
  • Treat AWS tag/name filters as search UX only, not permission enforcement.

Paperclip also blocks importing refs under its own managed namespace as external references. Use the Paperclip-managed flow for paperclip/{deploymentId}/{companyId}/{secretKey} resources.

Existing AWS Secrets

V1 keeps existing AWS Secrets Manager entries as linked external references, not adopted Paperclip-managed resources.

Use the Paperclip-managed flow when Paperclip should create and rotate the value. The AWS secret name is derived from deployment and company scope:

paperclip/{deploymentId}/{companyId}/{secretKey}

Use the external-reference flow when the secret already exists at an operator-owned path such as:

/paperclip-bench/anthropic_api_key

In that mode Paperclip stores only the path or ARN, resolves it at runtime, and records redacted access events. Operators rotate the actual value in AWS. Update the Paperclip reference only when the AWS path, ARN, or pinned provider version changes.

Paperclip does not currently offer an "adopt existing AWS secret" flow that takes over future PutSecretValue writes for an arbitrary existing secret. Adding that later requires explicit confirmation UX, scope validation, expected Paperclip tags, and security/cloud-ops review.

Data Custody

  • Paperclip stores externalRef, providerVersionRef, provider id, fingerprint hash, status, and binding metadata.
  • Paperclip does not store AWS secret plaintext in company_secret_versions.material.
  • Runtime resolution fetches the value from AWS only when a bound consumer needs it.

Rotation Runbook

Manual Paperclip-managed rotation:

  1. Write the new value through the Paperclip secret rotate flow.
  2. Paperclip creates a new AWS secret version with PutSecretValue.
  3. Paperclip records the new providerVersionRef in company_secret_versions.
  4. Re-run or restart affected workloads that consume latest, or pin consumers to a specific Paperclip version before rollout when you need staged release safety.

Guidance:

  • Prefer pinned Paperclip secret versions for risky rollouts.
  • Treat provider-native automatic rotation as a later enhancement; current V1 flow is explicit create-new-version plus controlled rollout.

Backup And Restore Runbook

What must survive:

  • Paperclip database metadata for secret ownership, bindings, status, and provider version refs.
  • User-secret definitions, declarations, company_secrets.scope = 'user' value rows, owner user ids, responsible-user snapshots, access-event metadata, and provider version refs.
  • AWS Secrets Manager namespace under the configured deployment prefix.
  • Any operator-owned external AWS prefixes linked by user-scoped external refs.
  • The configured KMS key and its decrypt permissions.

Restore checklist:

  1. Restore Paperclip database metadata.
  2. Confirm the same AWS Secrets Manager namespace still exists.
  3. Confirm any linked user-scoped external prefixes still exist.
  4. Confirm the Paperclip runtime role can call GetSecretValue on the restored managed prefix and approved external prefixes.
  5. Confirm the role still has decrypt access to the CMKs referenced by PAPERCLIP_SECRETS_AWS_KMS_KEY_ID and by any external user-secret values.
  6. Run the live smoke below or a targeted runtime secret resolution test for both a company secret and a user-secret value.

Provider Outage Runbook

Symptoms:

  • Secret create/rotate/resolve operations fail with AWS provider errors.
  • Agent runs fail before adapter invocation on required secret resolution.
  • Remote import preview fails to list AWS inventory.

Immediate actions:

  1. Confirm AWS regional health and Secrets Manager availability.
  2. Confirm the runtime role still has GetSecretValue and KMS decrypt permissions.
  3. Check for accidental prefix, region, deployment id, or KMS key config drift.
  4. Retry a single resolution after AWS service health is green.
  5. If outage persists, pause high-risk runs that require secret access rather than churning retries.

Remote import-specific actions:

  • Missing list permission: add secretsmanager:ListSecrets with Resource: "*" only when inventory import is approved for that vault's AWS account and Region.
  • Throttling: narrow the search, wait briefly, and retry with backoff. Avoid full-account enumeration.
  • Invalid or stale cursor: refresh the preview and discard the old NextToken.
  • Large account: load pages intentionally, keep one in-flight preview request per vault/search, and do not run background full-account crawls.
  • Runtime read failure after import: verify GetSecretValue and KMS decrypt on the selected external secret. Visibility in ListSecrets does not prove read permission.

Incident Response Runbook

Potential incidents:

  • Cross-company access caused by IAM scoping drift.
  • KMS policy drift causing decrypt failures or over-broad access.
  • Suspected secret exposure in logs, transcripts, or downstream agent output.

Response steps:

  1. Stop or pause affected Paperclip runs.
  2. Audit recent Paperclip secret access events for impacted secret ids and consumers. For user-scoped incidents, include credentialOwnerUserId, responsibleUserId, and userSecretDefinitionId in the review.
  3. Audit AWS CloudTrail for ListSecrets, GetSecretValue, PutSecretValue, and DeleteSecret calls on the relevant vault account, Region, deployment prefix, and approved external prefixes.
  4. Rotate impacted secrets in AWS through Paperclip-managed versioning.
  5. Re-scope IAM and KMS policies before resuming normal traffic.
  6. If a value may have reached an agent transcript or external system, treat it as exposed and rotate immediately.

Optional Live Smoke

This is safe to skip locally. Run it only against a dedicated AWS test namespace.

Prerequisites:

  • AWS credentials or workload identity with the deployment-scoped IAM permissions above.
  • PAPERCLIP_SECRETS_PROVIDER=aws_secrets_manager
  • The required PAPERCLIP_SECRETS_AWS_* environment variables set.

Suggested smoke:

  1. Create a test secret through the Paperclip board or API under a throwaway company.
  2. Confirm the resulting AWS secret name matches paperclip/{deploymentId}/{companyId}/{secretKey}.
  3. Rotate the secret once and confirm a new providerVersionRef appears in Paperclip metadata.
  4. Resolve the secret through a bound runtime path, not by adding a general-purpose reveal endpoint.
  5. Delete the throwaway secret and confirm AWS schedules deletion with the configured recovery window.