mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-03 09:37:19 +01:00
Add dynamic secrets operator guide
Document the shipped dynamic secret host-command contract, generator safety guidance, trust posture, TTL guidance, and a GitHub App installation-token example. Link the guide from existing secrets docs and surface the guidance requirement in the experimental settings copy. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
6 files changed
+154
-2
No files matched your search
@@ -189,6 +189,11 @@ For local/default installs, the active provider is `local_encrypted`:
|
||||
- Backup/restore requires both the database metadata and the local master key file; either artifact alone is insufficient.
|
||||
- The server best-effort enforces `0600` key file permissions and provider health reports permission warnings.
|
||||
|
||||
Dynamic command-backed secrets are experimental and use host-side generator
|
||||
commands instead of stored secret material. For the generator invocation
|
||||
contract, trust posture, TTL guidance, and a GitHub App token example, see
|
||||
[DYNAMIC-SECRETS.md](./DYNAMIC-SECRETS.md).
|
||||
|
||||
Optional overrides:
|
||||
|
||||
- `PAPERCLIP_SECRETS_MASTER_KEY` (32-byte key as base64, hex, or raw 32-char string)
|
||||
|
||||
@@ -645,6 +645,11 @@ 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.
|
||||
|
||||
Dynamic secrets are experimental host-command generators. Before enabling
|
||||
`enableDynamicSecrets` or attaching a generator, read
|
||||
`doc/DYNAMIC-SECRETS.md` for the shipped invocation contract, fail-closed
|
||||
behavior, trust posture, and TTL 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
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
# Dynamic Secrets Operator Guide
|
||||
|
||||
Dynamic secrets are experimental command-backed secrets. They let an operator
|
||||
store a generator definition instead of storing secret material. At runtime,
|
||||
Paperclip runs the generator on the host and injects the generated value into the
|
||||
target environment.
|
||||
|
||||
This guide is about safe generator authoring. Paperclip can enforce the runtime
|
||||
contract described here, but it does not certify that a generator is safe or that
|
||||
the token it mints has the right privileges. The operator owns that trust
|
||||
decision.
|
||||
|
||||
## Runtime Contract
|
||||
|
||||
A dynamic secret uses `managedMode: "dynamic_command"` and provider
|
||||
`host_command`. It is available only when the `enableDynamicSecrets`
|
||||
experimental setting is enabled.
|
||||
|
||||
The shipped runtime behavior is:
|
||||
|
||||
- The generator command is stored on the secret as a single executable path or
|
||||
command string.
|
||||
- Static positional arguments are stored on each secret binding. Agents cannot
|
||||
provide or alter these arguments at runtime.
|
||||
- Paperclip invokes the generator with `spawn(command, staticArgv, { shell:
|
||||
false })`. There is no shell interpolation, and stdin is ignored.
|
||||
- The generator inherits the Paperclip server process environment.
|
||||
- `stdout` is the generated secret value. Paperclip trims surrounding whitespace
|
||||
from stdout before injection.
|
||||
- `stderr` is not returned to the agent or board UI. It only affects the failure
|
||||
message by indicating that host logs may contain details.
|
||||
- Exit code `0` with non-empty stdout succeeds.
|
||||
- Missing command, non-zero exit, signal termination, timeout, or empty stdout
|
||||
fails closed with no value injected.
|
||||
- The default timeout is 30 seconds. `PAPERCLIP_DYNAMIC_SECRET_COMMAND_TIMEOUT_MS`
|
||||
can lower it or set another positive value up to that 30 second ceiling.
|
||||
- Successful generated values are cached by binding and static argv for the
|
||||
secret's configured TTL. During that TTL, Paperclip injects the cached value
|
||||
instead of re-running the generator.
|
||||
|
||||
Dry runs from the board UI use the same command contract, but return only a
|
||||
pass/fail result plus non-sensitive metadata such as byte length. They never
|
||||
return the generated value.
|
||||
|
||||
## Safety Practices
|
||||
|
||||
Treat the generator as privileged host-side code.
|
||||
|
||||
- Keep the root credential on the host side, outside Paperclip agent workspaces
|
||||
and outside agent-readable configuration.
|
||||
- Prefer a dedicated helper binary or script with a narrow interface over a
|
||||
general-purpose shell wrapper.
|
||||
- Mint the narrowest token possible for the specific binding. Use static argv
|
||||
for operator-controlled selectors such as organization, repository, account,
|
||||
role, or installation id.
|
||||
- Make the minted token short-lived. Avoid long-lived bearer tokens when the
|
||||
upstream service supports scoped temporary credentials.
|
||||
- Never print the root secret, refresh token, private key, or credential source.
|
||||
Print only the derived runtime token to stdout.
|
||||
- Keep stderr non-sensitive. Stderr may be referenced in host logs during
|
||||
debugging.
|
||||
- Make generators idempotent and side-effect-light. A generator may be retried
|
||||
after a timeout or process failure, and a failed injection should not leave
|
||||
durable external state behind.
|
||||
- Fail with a non-zero exit when the generated credential would be missing,
|
||||
over-broad, expired, or otherwise unsafe.
|
||||
- Avoid reading mutable input from the agent workspace. Runtime arguments are
|
||||
already fixed by the operator on the binding.
|
||||
- Run the board UI dry-run after authoring or changing a generator, then verify
|
||||
the target workflow with the least-privilege binding you expect agents to use.
|
||||
|
||||
## Trust Posture
|
||||
|
||||
Dynamic secrets reduce storage of long-lived secret material in Paperclip, but
|
||||
they do not automatically create a hard isolation boundary.
|
||||
|
||||
Under the current local/default posture, dynamic generators are a soft boundary.
|
||||
The generator runs as the same operating-system user as the Paperclip server,
|
||||
similar to the `local_encrypted` provider posture. A same-UID agent or process
|
||||
with enough local access can potentially observe process state, host files, or
|
||||
runtime values. Use this posture for trusted local operation, not as containment
|
||||
against a hostile same-UID agent.
|
||||
|
||||
A hard boundary requires an approved sandbox or environment driver that isolates
|
||||
the generator and injected value from the host and from untrusted agent code.
|
||||
Do not describe a deployment as hard-isolated just because it uses dynamic
|
||||
secrets.
|
||||
|
||||
## TTL Guidance
|
||||
|
||||
TTL controls Paperclip's successful-value cache. It should be no longer than the
|
||||
usable lifetime of the minted token and usually shorter.
|
||||
|
||||
Choose a TTL by balancing freshness and upstream rate limits:
|
||||
|
||||
- For tokens valid for minutes, use a TTL of seconds to a few minutes.
|
||||
- For tokens valid for about one hour, a TTL around 5 to 15 minutes is a common
|
||||
starting point.
|
||||
- Lower the TTL when revocation speed or least privilege matters more than API
|
||||
rate limits.
|
||||
- Raise the TTL only when the generator is expensive or the upstream service has
|
||||
tight token-minting limits.
|
||||
- Do not set the TTL equal to or beyond the upstream token expiry. Clock skew,
|
||||
queued runs, and retry timing can otherwise inject a nearly expired token.
|
||||
|
||||
## Worked Example: GitHub App Installation Token
|
||||
|
||||
Goal: let an agent push to one repository without giving it the GitHub App
|
||||
private key.
|
||||
|
||||
Host setup:
|
||||
|
||||
- Store the GitHub App private key outside the repo and outside agent
|
||||
workspaces, for example under a root- or operator-owned secrets directory.
|
||||
- Install a small generator at an absolute path such as
|
||||
`/usr/local/bin/paperclip-mint-github-installation-token`.
|
||||
- Make the generator accept only operator-fixed static argv such as:
|
||||
`["--installation-id", "123456", "--repository", "owner/repo"]`.
|
||||
- The generator authenticates as the GitHub App, requests an installation access
|
||||
token scoped to that repository and only the permissions needed for the task,
|
||||
prints the token to stdout, and exits `0`.
|
||||
- On any API, key, permission, or repository mismatch, the generator prints no
|
||||
token and exits non-zero.
|
||||
|
||||
Paperclip setup:
|
||||
|
||||
- Create a dynamic secret with command
|
||||
`/usr/local/bin/paperclip-mint-github-installation-token`.
|
||||
- Set the binding static argv for the target agent, project, or routine to the
|
||||
installation id and repository it may access.
|
||||
- Set TTL lower than the GitHub installation token lifetime. For a one-hour
|
||||
GitHub App installation token, start around 600 seconds and adjust for rate
|
||||
limits and run frequency.
|
||||
- Run the board UI generator test before saving, then run a task that needs only
|
||||
that repository permission.
|
||||
|
||||
The agent receives only the installation token selected for that binding. It
|
||||
does not receive the GitHub App private key and cannot choose a different
|
||||
installation or repository unless the operator changes the binding argv.
|
||||
@@ -358,6 +358,9 @@ Operational policy:
|
||||
- Config read APIs redact sensitive plain values.
|
||||
- Activity and approval payloads must not persist raw sensitive values.
|
||||
- Config revisions may include redacted placeholders; such revisions are non-restorable for redacted fields.
|
||||
- Dynamic command-backed secrets are experimental host-command generators. The
|
||||
operator-facing runtime contract and safe authoring guidance live in
|
||||
`doc/DYNAMIC-SECRETS.md`.
|
||||
|
||||
## 7.14 Required Indexes
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ describe("InstanceExperimentalSettings — Conference Room Chat card (PAP-11233)
|
||||
|
||||
expect(container.textContent).toContain("Dynamic Secrets");
|
||||
expect(container.textContent).toContain(
|
||||
"Enables experimental dynamic secret resolution for later-stage secret bindings and command-backed values.",
|
||||
"Enables experimental command-backed secret resolution. Review the dynamic secrets operator guide before attaching generator commands.",
|
||||
);
|
||||
|
||||
const toggle = container.querySelector<HTMLButtonElement>(DYNAMIC_SECRETS_TOGGLE_SELECTOR);
|
||||
|
||||
@@ -341,7 +341,7 @@ export function InstanceExperimentalSettings() {
|
||||
<div className="space-y-1.5">
|
||||
<h2 className="text-sm font-semibold">Dynamic Secrets</h2>
|
||||
<p className="max-w-2xl text-sm text-muted-foreground">
|
||||
Enables experimental dynamic secret resolution for later-stage secret bindings and command-backed values.
|
||||
Enables experimental command-backed secret resolution. Review the dynamic secrets operator guide before attaching generator commands.
|
||||
</p>
|
||||
</div>
|
||||
<ToggleSwitch
|
||||
|
||||
Reference in new issue
Block a user