Skip to content

feat(secret): read Secrets from 1Password through Secret Grants - #476

Merged
ian-pascoe merged 3 commits into
mainfrom
feat/secret-providers
Oct 20, 2026
Merged

ian-pascoe merged 3 commits into
mainfrom
feat/secret-providers

Conversation

@ian-pascoe

Copy link
Copy Markdown
Owner

Summary

Pi Secret can now read Secrets from a Secret Provider (1Password, through the op CLI) using Secret Grants the user approves. It can also save a typed Secret into a new 1Password item. The session stores only a grant's Secret Locator, never a value, and the value is read again at each use, so one-time passwords stay current.

secret_list { query, provider, vault, item, status, limit }   # held Secrets + provider items/fields, never values
secret_ask_grant { grants: [{ name, locator: "op://Dev/GitHub/token" }] }
  ApprovalDialog (allow/deny each)  →  check read  →  pi-secret-grant entry { name, locator, expiresAt }
tool_call referencing {{secret:NAME}} or $PI_SECRET_NAME
  refreshSecret(name)  →  op read  →  substitute + export + register for redaction
  read fails           →  block the call (never fall back to the last value)
secret_save { name, locator, category? }   # typed Secret → new 1Password item (value on stdin) → becomes a grant, same expiry
 packages/pi-secret/src/
+├── secret-provider.ts        # internal seam: one provider per locator scheme
+├── one-password-provider.ts  # op CLI: describe/read/search/save, vault allowlist
+├── approval-dialog.ts        # Grant Request + save confirmation
 ├── secret-references.ts      # + backslash escape: \{{secret:NAME}} / \PI_SECRET_NAME are literal
 └── pi-secret-extension.ts    # + secret_ask_grant, secret_save, filtered secret_list, grant/forget entries
 packages/pi-utils/src/secret-registry.ts   # + holdRefreshableSecret / refreshSecret / isSecretHeld (ADR-0010)
 packages/pi-guardian/src/tool-policy.ts    # + allow secret_ask_grant, secret_save by default
  • secret_ask_grant, secret_save and one guideline line are registered only when op is on PATH at load. That is decided once, so the cached prompt prefix stays stable within a session.
  • Optional secret.providers.1password.{account, vaults} setting. The vault allowlist is checked on describe, search, save and every read, and errors never name a vault outside the list. Invalid settings fall back to no vaults.
  • Also fixes literal reference syntax in documentation and tests being blocked as unheld references. A backslash right before a reference makes it literal text, and the shell's own \$PI_SECRET_NAME is left as written.
  • Decisions: pi-secret ADR-0001 (read grants at each use, never seal them) and ADR-0010 (registry refresh callbacks for Child Agents). The glossary gains Secret Provider, Secret Grant, Secret Locator, 1Password Reference and Grant Request.

Evidence

  • Before: a tool call containing \{{secret:NAME}} was blocked ("references {{secret:NAME}}, which this session does not hold"). There was no way to use a 1Password item without the user pasting its value.
    After: focused offline suites, all with a fake provider or a fake op:
    • pi-secret: 72 passed (6 files). New: secret-grants.test.ts and one-password-provider.test.ts, plus escape cases in pi-secret-extension.test.ts.
      grant OTP → echo {{secret:BANK_OTP}} twice → tool receives otp-code-000002, then otp-code-000003; session file holds the locator only
      provider locked at use → call blocked, grant still listed
      resume → grant restored with zero reads until first use; forgotten/expired grants not restored
      child agent → reads the root's grant fresh through the registry
      prompt cache → first system message (prompt + ordered tool declarations) identical across grant/use/forget turns
      op save → value only on stdin, never in argv; read with vaults configured → vault checked, looked up once
      
    • pi-utils secret-registry.test.ts: 14 passed. pi-guardian tool-policy.test.ts: 86 passed. Root secret-guardian-combined.test.ts: 3 passed.
  • The full pnpm verify was not run locally; CI covers it.
  • Not verified against a live 1Password account (op was signed out). Check secret_save item creation and an ?attribute=otp read by hand.

Merge Danger

Door: two-way

The new session entry types (pi-secret-grant, pi-secret-forget) are additive. Older versions ignore them, and the existing typed-Secret path is unchanged apart from the escape handling.

Blast Radius: moderate

  • Every Pi Secret user: tool calls now remove a backslash right before a reference.
  • Machines with op on PATH: two new tools, and secret_list searches 1Password by default, sending item titles and field labels from every vault the CLI can see to the model unless vaults is set.
  • Pi Guardian: default policy now allows the two new tools.
  • pi-utils minor release: patch-bumps every consumer.

Also includes a separate chore commit: the pre-push hook now runs pnpm verify:push (format, lint, typechecks) instead of the full pnpm verify. Test suites run only in CI, which still runs pnpm verify. AGENTS.md and README.md are updated to match.

Add Secret Providers to Pi Secret, starting with 1Password through the
op CLI. secret_list searches a provider's items and fields (never values),
secret_ask_grant asks the user to approve Secret Grants that are read again
from the provider at each use, and secret_save saves a typed Secret into a
new 1Password item after approval. Sessions store only a grant's locator.

pi-utils' secret registry gains refresh callbacks so Child Agents read
grants fresh (ADR-0010), and Pi Guardian allows the new tools by default.
A backslash before a Secret Reference now makes it literal text.
The pre-push hook now runs pnpm verify:push (format, lint, typechecks). CI still runs the full pnpm verify.
… appears in them

Redacting the whole history with every value held rewrote earlier messages
when a Secret Grant read a value the model had already seen, discarding the
provider's prompt cache. Each message is now redacted at the Redaction
Generation it was first sent at (pi-secret ADR-0002).
@ian-pascoe
ian-pascoe merged commit 911033b into main Oct 20, 2026
3 checks passed
@ian-pascoe
ian-pascoe deleted the feat/secret-providers branch October 20, 2026 11:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant