diff --git a/doc/CLI.md b/doc/CLI.md index c287e4c74ff..29c494e3bed 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -161,6 +161,14 @@ For AWS-backed secrets, `secrets doctor` reports missing non-secret provider env and the expected AWS SDK runtime credential source; do not store AWS bootstrap credentials in Paperclip secrets. +Per-company provider vaults (multiple vault instances per provider, default +vault selection, coming-soon GCP/Vault) are configured from the board UI under +`Company Settings → Secrets → Provider vaults` or through +`/api/companies/{companyId}/secret-provider-configs`. There is no CLI surface +for vault management today. See the +[secrets deploy guide](../docs/deploy/secrets.md#provider-vaults) and +[API reference](../docs/api/secrets.md#provider-vaults) for the contract. + ## Approval Commands ```sh diff --git a/doc/DEVELOPING.md b/doc/DEVELOPING.md index 07a571d50c8..c9a2a19414c 100644 --- a/doc/DEVELOPING.md +++ b/doc/DEVELOPING.md @@ -480,6 +480,12 @@ CLI configuration support: - `pnpm paperclipai doctor` validates secrets adapter configuration, can create a missing local key file with `--repair`, and reports missing AWS Secrets Manager bootstrap env when that provider is selected. - Provider health is available at `GET /api/companies/:companyId/secret-providers/health` and reports local key permission warnings plus backup guidance. +Per-company provider vaults are configured in the board UI under +`Company Settings → Secrets → Provider vaults`, backed by +`/api/companies/{companyId}/secret-provider-configs`. The CLI does not own +vault lifecycle today. See `docs/deploy/secrets.md` (`Provider Vaults` section) +for the operator model. + Migration helper for existing inline env secrets: ```sh diff --git a/doc/SECRETS-AWS-PROVIDER.md b/doc/SECRETS-AWS-PROVIDER.md index 94d236adc7e..c7cce82e57a 100644 --- a/doc/SECRETS-AWS-PROVIDER.md +++ b/doc/SECRETS-AWS-PROVIDER.md @@ -8,6 +8,16 @@ Operational contract for the hosted `aws_secrets_manager` secret provider used b - 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](../docs/deploy/secrets.md#provider-vaults) for the operator + model and [Provider Vaults API](../docs/api/secrets.md#provider-vaults) 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 @@ -168,6 +178,64 @@ 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: + +```json +{ + "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: + +```json +{ + "Sid": "PaperclipResolveImportedExternalReferences", + "Effect": "Allow", + "Action": "secretsmanager:GetSecretValue", + "Resource": [ + "arn:aws:secretsmanager:::secret:/*" + ] +} +``` + +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//*`; do not broaden them for +remote import. + +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 @@ -237,6 +305,7 @@ 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: @@ -246,6 +315,21 @@ Immediate actions: 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: @@ -258,7 +342,9 @@ Response steps: 1. Stop or pause affected Paperclip runs. 2. Audit recent Paperclip secret access events for impacted secret ids and consumers. -3. Audit AWS CloudTrail for `GetSecretValue`, `PutSecretValue`, and `DeleteSecret` calls on the deployment prefix. +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. diff --git a/docs/api/secrets.md b/docs/api/secrets.md index d6441e8e2b6..93a56b60803 100644 --- a/docs/api/secrets.md +++ b/docs/api/secrets.md @@ -62,6 +62,308 @@ The equivalent CLI check is: pnpm paperclipai secrets doctor --company-id {companyId} ``` +## Provider Vaults + +Provider vaults are named, company-scoped configurations that route secret +material to one of the supported provider backends. See the +[secrets deploy guide](/deploy/secrets#provider-vaults) for the operator model +and custody rules. + +All routes below require board auth and company access. Mutating routes emit +`secret_provider_config.*` activity-log entries. No route in this surface +returns provider credential values; submitting credential-shaped fields in +`config` is rejected at validation time. + +### List Vaults + +``` +GET /api/companies/{companyId}/secret-provider-configs +``` + +Returns every vault for the company (including disabled rows for audit), each +with id, provider, displayName, status, isDefault, non-sensitive `config`, +latest health snapshot (`healthStatus`, `healthCheckedAt`, `healthMessage`, +`healthDetails`), `disabledAt`, and audit columns. + +### Create Vault + +``` +POST /api/companies/{companyId}/secret-provider-configs +{ + "provider": "aws_secrets_manager", + "displayName": "Prod US-East", + "isDefault": true, + "config": { + "region": "us-east-1", + "namespace": "paperclip", + "secretNamePrefix": "paperclip", + "kmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/abcd-...", + "environmentTag": "production" + } +} +``` + +Per-provider `config` shapes: + +- `local_encrypted`: optional `backupReminderAcknowledged: boolean`. +- `aws_secrets_manager`: required `region`; optional `namespace`, + `secretNamePrefix`, `kmsKeyId`, `ownerTag`, `environmentTag`. +- `gcp_secret_manager` (coming soon): optional `projectId`, `location`, + `namespace`, `secretNamePrefix`. +- `vault` (coming soon): optional origin-only HTTPS `address`, `namespace`, + `mountPath`, `secretPathPrefix`. `address` values with embedded credentials, + paths, query strings, or fragments are rejected. + +`status` defaults to `ready` for `local_encrypted` and `aws_secrets_manager`, +and to `coming_soon` for `gcp_secret_manager` and `vault`. Coming-soon and +disabled vaults cannot be marked `isDefault`. Setting `isDefault: true` clears +the previous default for the same provider in the same transaction. + +### Get Vault + +``` +GET /api/secret-provider-configs/{id} +``` + +### Update Vault + +``` +PATCH /api/secret-provider-configs/{id} +{ + "displayName": "Prod US-East-2", + "config": { + "region": "us-east-2", + "kmsKeyId": "arn:aws:kms:us-east-2:123456789012:key/abcd-..." + } +} +``` + +`config` is replaced wholesale on update — pass the full provider config +payload, not a partial diff. Status transitions for `gcp_secret_manager` and +`vault` are constrained to `coming_soon` and `disabled` until their runtime +modules ship. + +### Disable Vault + +``` +DELETE /api/secret-provider-configs/{id} +``` + +Soft-deletes the vault: status flips to `disabled`, `isDefault` clears, and +`disabledAt` is stamped. Disabled vaults remain in `GET` results for audit +purposes but are no longer offered in the secret create/rotate flow. + +### Set Default + +``` +POST /api/secret-provider-configs/{id}/default +``` + +Marks the target vault as the default for its provider family and clears the +previous default. Returns 422 when the target is `coming_soon` or `disabled`. + +### Run Health Check + +``` +POST /api/secret-provider-configs/{id}/health +``` + +Runs a provider-specific health probe and persists the result on the vault. +Response shape: + +```json +{ + "configId": "", + "provider": "aws_secrets_manager", + "status": "ready" | "warning" | "error" | "coming_soon" | "disabled", + "message": "Provider vault is ready to handle managed writes", + "details": { + "code": "provider_ready", + "message": "...", + "guidance": ["..."] + }, + "checkedAt": "2026-05-06T14:00:00.000Z" +} +``` + +Health responses never include provider credentials or secret values. For AWS +vaults, `details.guidance` may include missing non-secret env names and the +expected AWS SDK credential source; coming-soon vaults always return +`status: "coming_soon"` with `code: "runtime_locked"` and never call into +provider modules. + +### Selecting A Vault When Creating Or Rotating Secrets + +`POST /api/companies/{companyId}/secrets` and +`POST /api/secrets/{secretId}/rotate` both accept an optional +`providerConfigId` field that pins the secret to a specific vault. When +omitted (or null), the operation runs through the deployment-level provider +configuration — the same path existing installs already use. The board UI +preselects the company's default vault for the chosen provider before +submitting, so callers should usually send an explicit `providerConfigId`. +Coming-soon and disabled vaults are rejected with a 422; a vault that does not +match the secret's provider is rejected the same way. + +```json +POST /api/companies/{companyId}/secrets +{ + "name": "prod-stripe-key", + "provider": "aws_secrets_manager", + "providerConfigId": "", + "managedMode": "external_reference", + "externalRef": "arn:aws:secretsmanager:us-east-1:123456789012:secret:paperclip/prod/stripe" +} +``` + +### Response Redaction Rules + +Every route in this surface enforces the same redaction contract: + +- Secret values are never returned. The board UI never has a "reveal value" + affordance; resolution happens server-side at runtime under a binding. +- Provider credential values are never accepted, stored, returned, logged, or + echoed in error messages. Submitting credential-shaped fields fails + validation with a non-leaking error. +- Activity log entries record vault id, provider, displayName, status, and + isDefault transitions — never `config` payloads or health detail bodies. + +## Remote Import From AWS Secrets Manager + +Remote import links existing AWS Secrets Manager entries into Paperclip as +`external_reference` secrets. Import stores provider reference metadata only; it +does not copy the remote secret plaintext into Paperclip. + +The routes are board-only and company-scoped. `providerConfigId` must point to +a same-company AWS provider vault with status `ready` or `warning`. Disabled, +coming-soon, non-AWS, and cross-company vaults are rejected. Imported secrets +resolve later through the selected vault, so runtime reads still need +`secretsmanager:GetSecretValue` and any required KMS decrypt permission on the +selected external secret. + +### Preview Remote Import Candidates + +``` +POST /api/companies/{companyId}/secrets/remote-import/preview +{ + "providerConfigId": "", + "query": "stripe", + "nextToken": "opaque-provider-token", + "pageSize": 50 +} +``` + +`query` is optional and is passed to AWS Secrets Manager inventory filtering. +Treat it as non-secret metadata because AWS may record list request parameters +in CloudTrail. `nextToken` is an opaque AWS cursor; callers must pass it back +unchanged and must not synthesize offsets. `pageSize` is optional, defaults to +50 in the UI, and is capped at 100. + +Preview uses AWS `ListSecrets` only. It must not call `GetSecretValue` or +`BatchGetSecretValue`, must not request `SecretString`, and must not require KMS +decrypt. The response contains sanitized metadata for display and conflict +decisions: + +```json +{ + "providerConfigId": "", + "provider": "aws_secrets_manager", + "nextToken": null, + "candidates": [ + { + "externalRef": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/stripe", + "remoteName": "prod/stripe", + "name": "prod/stripe", + "key": "prod-stripe", + "providerVersionRef": null, + "providerMetadata": { + "createdDate": "2026-05-06T00:00:00.000Z", + "lastChangedDate": "2026-05-06T00:00:00.000Z", + "hasDescription": true, + "hasKmsKey": true, + "tagCount": 3 + }, + "status": "ready", + "importable": true, + "conflicts": [] + } + ] +} +``` + +Candidate statuses: + +- `ready`: the row can be selected for import. +- `duplicate`: a Paperclip secret already links the same canonical provider + reference for the same provider vault. +- `conflict`: the row has a name/key collision or provider guardrail failure. + +Conflict types are `exact_reference`, `name`, `key`, and +`provider_guardrail`. AWS refs under Paperclip's own managed namespace are +blocked as external references; use the Paperclip-managed secret flow for those +resources instead. + +### Import Selected Remote References + +``` +POST /api/companies/{companyId}/secrets/remote-import +{ + "providerConfigId": "", + "secrets": [ + { + "externalRef": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/stripe", + "name": "Stripe production key", + "key": "stripe-production-key", + "description": "Stripe key used by production checkout", + "providerVersionRef": null, + "providerMetadata": { + "createdDate": "2026-05-06T00:00:00.000Z" + } + } + ] +} +``` + +The `secrets` array accepts 1-100 rows. Each row may override the suggested +Paperclip `name`, `key`, optional Paperclip `description`, +`providerVersionRef`, and sanitized `providerMetadata`. Blank descriptions are +stored as `null`; AWS provider descriptions are not copied into Paperclip +descriptions. The backend re-checks duplicate refs and name/key conflicts at +submit time; a stale preview does not bypass those checks. + +The import response is row-level: + +```json +{ + "providerConfigId": "", + "provider": "aws_secrets_manager", + "importedCount": 1, + "skippedCount": 1, + "errorCount": 0, + "results": [ + { + "externalRef": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/stripe", + "name": "Stripe production key", + "key": "stripe-production-key", + "status": "imported", + "reason": null, + "secretId": "", + "conflicts": [] + } + ] +} +``` + +Row statuses: + +- `imported`: Paperclip created an active `external_reference` secret and one + metadata-only version row. +- `skipped`: the row had an exact-reference duplicate or name/key conflict. +- `error`: the provider rejected the reference or the row failed validation. + +Activity logs for preview/import store aggregate counts, provider id, and vault +id only. They must not store remote secret names, ARNs, descriptions, tags, +plaintext values, provider credentials, or raw AWS error blobs. + ## Rotate Secret ``` diff --git a/docs/cli/setup-commands.md b/docs/cli/setup-commands.md index 977f463a4fc..f6284aba01d 100644 --- a/docs/cli/setup-commands.md +++ b/docs/cli/setup-commands.md @@ -82,6 +82,13 @@ pnpm paperclipai configure --section secrets pnpm paperclipai configure --section storage ``` +`--section secrets` updates the deployment-level provider used as the fallback +for secrets that do not target a specific company vault. Per-company provider +vaults (named instances, default vault selection, multiple vaults per provider, +coming-soon GCP/Vault) live in the board UI under +`Company Settings → Secrets → Provider vaults` and the +`/api/companies/{companyId}/secret-provider-configs` API. + ## `paperclipai env` Show resolved environment configuration: diff --git a/docs/deploy/secrets.md b/docs/deploy/secrets.md index 6e481c41e12..41fa9df3757 100644 --- a/docs/deploy/secrets.md +++ b/docs/deploy/secrets.md @@ -128,11 +128,215 @@ For hosted Paperclip Cloud on AWS, see the AWS Secrets Manager operational contract — required env vars, IAM/KMS scoping, naming and tag conventions, and backup/rotation/incident runbooks — in `doc/SECRETS-AWS-PROVIDER.md`. +## Provider Vaults + +A *provider vault* is a named, company-scoped configuration that points secret +material at one of the supported provider backends. Each company can configure +multiple vaults, including more than one vault per provider family, and pick a +default vault per family for new secret operations. Existing secrets created +before any vault was configured continue to resolve through the deployment-level +default provider — no migration is required. + +### Where to configure + +Open `Company Settings → Secrets` in the board UI and switch to the +`Provider vaults` tab. From there you can: + +- Create a vault for any supported provider family. +- Edit the non-secret config of an existing vault. +- Set one ready vault per provider family as the company default. +- Disable a vault (a soft delete that keeps audit history). +- Run a health check against a vault and read the latest result inline. + +The same operations are exposed under +`/api/companies/{companyId}/secret-provider-configs` for automation. See the +[secrets API reference](/api/secrets#provider-vaults) for the full route table. + +### Custody Of Provider Credentials + +Provider vaults intentionally store only **non-sensitive** configuration: +region, project id, namespace, prefix, KMS key id, mount path, address, and +similar routing metadata. The API, UI, and activity log never accept, return, +or display provider credential values. Submitting fields with names like +`accessKeyId`, `secretAccessKey`, `token`, `password`, `serviceAccountJson`, +`privateKey`, `keyFile`, `unsealKey`, or any common credential alias is rejected +at validation time. + +That keeps the bootstrap rule from the AWS provider applicable to every +provider family: **provider credentials live in deployment infrastructure +identity, not in Paperclip company secrets**. Allowed credential sources are +workload identity attached to the Paperclip server (instance profile, IRSA, ECS +task role), `AWS_PROFILE` / SSO / shared config for local runs, an orchestrator +secret store that boots the server, or short-lived shell credentials for local +development. Do not paste long-lived API keys into the vault config. + +### Vault Status + +Each vault carries a status that drives what the runtime can do with it: + +| Status | Meaning | +|---------------|-----------------------------------------------------------------------------------------------| +| `ready` | Selectable for create/rotate/resolve. Eligible to be the default. | +| `warning` | Saved config exists but health needs attention (for example missing AWS env). Still selectable. | +| `coming_soon` | Visible and editable as draft metadata, but locked out of all runtime operations. | +| `disabled` | Soft-deleted. Hidden from the secret create/rotate flow. | + +`gcp_secret_manager` and `vault` are pinned to `coming_soon` until their +runtime modules ship. The settings UI lets you save draft configuration for +those providers (and surfaces them on the vault list), but secret create, +rotate, and resolve calls that target a coming-soon vault fail with a clear +runtime-locked error. + +### Default Vault Behavior + +A company can mark **one** ready (or warning) vault per provider family as the +default. The secret create and rotate dialogs preselect the default vault for +the chosen provider so operators don't have to remember which vault to pick. +Coming-soon and disabled vaults cannot be marked default; attempting to do so +returns a validation error. Setting a new default automatically clears the +previous default for that provider. + +If a secret is created without any `providerConfigId` (no vaults exist yet, or +the operator clears the selector), runtime resolution falls back to the +deployment-level provider configuration — the same path existing installs use. +This keeps secrets created before any provider vault was configured working +without migration. Picking the default in the UI is an explicit selection, not +a runtime fallback: the create call still sends an explicit `providerConfigId`. + +### Multiple Vaults Per Provider + +Multiple vaults from the same provider family are first-class. Common patterns: + +- Two AWS vaults pointing at different regions or KMS keys for environment + separation. +- A staging Vault address alongside a production address. +- A dedicated GCP project for a single product line while the rest of the + company uses another. + +Each vault has its own display name, status, default flag, and health record. +Operators choose the vault explicitly when creating or rotating a secret; the +default vault is preselected to avoid accidental routing to the wrong account. + +### Per-Vault Health Checks + +`POST /api/secret-provider-configs/{id}/health` runs a provider-specific health +probe and stores the result on the vault row. The settings UI exposes the same +action and renders the result inline. Health responses include a status, +operator-facing message, and structured guidance (such as missing env var +names, expected credential sources, and backup reminders). They never include +provider credentials or secret values. Coming-soon vaults always return a +`runtime_locked` health code and never call into provider modules. + +### Provider-Specific Notes + +**Local encrypted vaults** wrap the existing `local_encrypted` provider. The +master key path and rotation guidance described above still applies. A local +vault config is mostly bookkeeping plus an explicit acknowledgement that the +key file is backed up alongside the database. + +**AWS Secrets Manager vaults** read the per-vault `region`, `namespace`, +`secretNamePrefix`, `kmsKeyId`, `ownerTag`, and `environmentTag` to route +managed writes and external-reference reads. The vault config supplements (and +can override) the deployment-level `PAPERCLIP_SECRETS_AWS_*` env. Bootstrap +credentials still come from the AWS SDK default credential chain — see +`doc/SECRETS-AWS-PROVIDER.md` for the full IAM and KMS contract. + +**GCP Secret Manager** and **HashiCorp Vault** vaults are coming soon. You can +save draft `projectId`, `location`, `namespace`, `address`, and `mountPath` +metadata so the company is ready to flip them on when the provider modules +ship. Vault `address` values must be origin-only `http(s)://host[:port]` URLs; +addresses with embedded credentials, paths, query strings, or fragments are +rejected. + +### Remote Import From AWS Vaults + +AWS provider vaults can import existing AWS Secrets Manager entries as +Paperclip `external_reference` secrets. This is a metadata-only link: Paperclip +stores the AWS ARN/path, a fingerprint/version reference, and binding metadata. +It does not read, copy, store, log, or display the remote plaintext secret +value during preview or import. + +Operator flow in the board UI: + +1. Open `Company Settings -> Secrets`. +2. Confirm at least one AWS provider vault is `ready` or `warning`. +3. In the `Secrets` tab, choose `Import from vault`. +4. Select an AWS vault, search the remote inventory, and load more pages as + needed. +5. Check the rows to import, review/edit the Paperclip name and key, then + submit. +6. Review the result summary for created, skipped, and failed rows. + +The preview list is intentionally paged and search-first. AWS accounts can have +large per-Region inventories, and `ListSecrets` returns opaque `NextToken` +cursors. Do not expect Paperclip to crawl a whole account in the background; +load pages deliberately and retry throttled requests with backoff. + +Remote import exposes AWS secret metadata visible to the Paperclip runtime +role, including names/ARNs and safe derived fields such as dates, whether a +description or KMS key exists, and tag count. Treat names, ARNs, tags, and +search text as operational metadata that may be sensitive. The API and activity +log must not store raw descriptions, tags, plaintext values, provider +credentials, or raw AWS error blobs. + +Required AWS posture: + +- Preview needs optional `secretsmanager:ListSecrets` permission on + `Resource: "*"`. AWS does not support constraining `ListSecrets` to + individual secret ARNs or tags as an IAM boundary. +- Preview/import must not call `secretsmanager:GetSecretValue`, + `secretsmanager:BatchGetSecretValue`, or KMS decrypt. +- Runtime resolution of an imported reference still needs + `secretsmanager:GetSecretValue` on the selected external ARN/path and KMS + decrypt when that secret uses a customer-managed key. +- Keep managed create/rotate/delete permissions scoped to the Paperclip + deployment prefix. Do not broaden managed write/delete permissions just + because import inventory is enabled. + +Safe scoping comes from deployment posture rather than AWS list filtering: +dedicated Paperclip runtime roles per environment/account, AWS vaults pointed at +the intended account and Region, import-enabled roles only where inventory +exposure is acceptable, and board-only access to the import routes. Tags and +name filters are search aids, not a permission model. + +If import preview fails: + +- `AccessDenied` or `not authorized`: the runtime role is missing + `secretsmanager:ListSecrets`; add the optional inventory statement only if + remote import should be enabled for that vault. +- Throttling: retry after a short delay and narrow the search before loading + more pages. +- Invalid cursor: refresh the preview; AWS `NextToken` values are opaque and + can expire or become stale. +- Runtime resolution failure after import: verify `GetSecretValue` and KMS + decrypt scope for the selected external secret. Being visible in inventory is + not proof that the runtime role can read the value. + +### Backup And Restore + +Each provider family has a different backup story: + +- `local_encrypted`: back up the local master key file and the Paperclip + database together. Either alone is not enough to restore the encrypted + values, and the vault row only records the path and acknowledgement, not the + key bytes. +- `aws_secrets_manager`: back up Paperclip's database for vault metadata + (vault id, region, prefix, KMS key id, default flag, bindings, version + pointers). The actual secret values live in AWS Secrets Manager under the + configured prefix; restore by pointing the same Paperclip company at the + same AWS namespace and confirming the runtime role still has + `GetSecretValue` plus KMS decrypt. The full restore checklist lives in + `doc/SECRETS-AWS-PROVIDER.md`. +- `gcp_secret_manager` and `vault`: while these are coming soon, only the + draft vault config exists in Paperclip. Database backups capture it. There + is nothing to restore on the provider side until runtime support lands. + ### AWS Provider Bootstrap Boundary The AWS Secrets Manager provider cannot bootstrap itself from Paperclip `company_secrets`. Its initial AWS access must be present before the server can -create or resolve AWS-backed company secrets. +create or resolve AWS-backed company secrets, regardless of whether you use the +deployment-level default or a per-company vault. For Paperclip Cloud, provision the server runtime IAM role/workload identity, KMS key, deployment prefix, and non-secret `PAPERCLIP_SECRETS_AWS_*` environment