{"id":50868734,"url":"https://github.com/liamvinberg/pi-secrets","last_synced_at":"2026-06-15T03:34:19.552Z","repository":{"id":364144957,"uuid":"1266604014","full_name":"liamvinberg/pi-secrets","owner":"liamvinberg","description":"Pi extension: agent requests secrets via a masked prompt. Env var delivery, never in model context, redacted from output","archived":false,"fork":false,"pushed_at":"2026-06-11T20:17:42.000Z","size":278,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-11T21:16:00.585Z","etag":null,"topics":["coding-agent","extension","pi","pi-coding-agent","pi-package","secrets","security","tui"],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/liamvinberg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-11T19:24:26.000Z","updated_at":"2026-06-11T20:17:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/liamvinberg/pi-secrets","commit_stats":null,"previous_names":["liamvinberg/pi-secrets"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/liamvinberg/pi-secrets","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liamvinberg%2Fpi-secrets","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liamvinberg%2Fpi-secrets/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liamvinberg%2Fpi-secrets/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liamvinberg%2Fpi-secrets/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/liamvinberg","download_url":"https://codeload.github.com/liamvinberg/pi-secrets/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/liamvinberg%2Fpi-secrets/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34346867,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coding-agent","extension","pi","pi-coding-agent","pi-package","secrets","security","tui"],"created_at":"2026-06-15T03:34:15.672Z","updated_at":"2026-06-15T03:34:19.547Z","avatar_url":"https://github.com/liamvinberg.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# pi-secrets\n\n[![CI](https://github.com/liamvinberg/pi-secrets/actions/workflows/ci.yml/badge.svg)](https://github.com/liamvinberg/pi-secrets/actions/workflows/ci.yml)\n[![npm](https://img.shields.io/npm/v/pi-secrets)](https://www.npmjs.com/package/pi-secrets)\n[![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\nA [pi](https://github.com/badlogic/pi-mono) extension for handing secrets to the agent without them ever entering the model's context window.\n\nWhen a task needs an API key, token, or password, the model calls the `request_secret` tool. You get a **masked prompt** in the TUI, paste the value, and the model only learns the secret's *name* and *length*. The value itself becomes an environment variable available to every subsequent `bash` command, and a redaction layer scrubs the value from all tool output as a backstop.\n\n![You see a masked prompt; the model sees only the name, length, and redaction markers](https://raw.githubusercontent.com/liamvinberg/pi-secrets/main/assets/preview.png)\n\n## Install\n\n```bash\npi install npm:pi-secrets\n```\n\nOr try it ephemerally: `pi -e npm:pi-secrets`\n\n## How it works\n\n1. **Capture**: `request_secret({ name, reason })` pops a masked single-line input. The value goes straight into the agent process's memory; nothing is written to disk, chat, or session files.\n2. **Delivery**: pi's bash tool builds its child environment from the agent's `process.env`, so the captured value is available as `$NAME` in every subsequent command. Need it in a file? The model runs `echo \"NAME=$NAME\" \u003e\u003e .env`, still without seeing the value.\n3. **Redaction**: every tool result (and `!` user-bash output) is scrubbed before it reaches the model or the session file: the exact value plus its base64 and URL-encoded variants are replaced with `[REDACTED:NAME]`. If the model runs `echo $NAME`, it sees the redaction marker, not the secret.\n4. **Subagents**: child pi processes inherit the environment. `PI_SECRETS_NAMES` carries the *names* (never values), so a child running this extension rebuilds its own redaction map.\n\n### Reuse rules\n\n| Situation | Behavior |\n|---|---|\n| Name already captured this process | Silent reuse; the model is told it's available |\n| Name in your shell environment at pi startup | Confirm dialog; allowing also registers the value for redaction |\n| Name in environment, headless (`pi -p`) | Silent reuse + redaction (the model could read the env there anyway) |\n| Nothing anywhere, headless | Tool errors with guidance to export the variable or run interactively |\n| You press Esc / decline | Tool errors telling the model not to re-ask |\n\n### Lifetime\n\nSecrets live for the **pi process lifetime**: they survive session switches (`/new`), and vanish when pi exits. Nothing persists to disk. After a restart the model simply asks again.\n\n## Commands\n\n- `/secrets`: list held secrets (name · source · length), interactively clear one or all\n- `/secrets clear \u003cNAME|all\u003e`: clear directly\n\nClearing a *captured* secret also removes it from the agent environment; clearing an *inherited* one only stops tracking/redaction.\n\n## Security model (read this)\n\nThis is **cooperative, not adversarial** (see [ADR-0002](docs/adr/0002-cooperative-security-model.md)). It protects against *accidental* disclosure: secrets pasted into chat, echoed by commands, or persisted in session transcripts. It does **not** defend against a malicious model: one that can run bash can exfiltrate anything the process can reach. Known gaps, accepted by design:\n\n- Transformations beyond exact/base64/URL-encoded (hex, JWTs signed with the secret, etc.) are not caught.\n- Values shorter than 4 chars are rejected at capture; 4 to 7 chars are accepted with a warning (short values risk redaction over-matching).\n- Multi-line secrets (PEM keys, service-account JSON) are rejected; point the agent at a file path instead. Planned for a later version.\n- MCP servers spawned at pi startup never see secrets captured later ([ADR-0001](docs/adr/0001-env-var-injection-over-file-relay.md)).\n\n## Claude Code?\n\nThis extension is pi-native on purpose ([ADR-0003](docs/adr/0003-pi-native-over-mcp-broker.md)). The equivalent for Claude Code is a different build: an MCP server using **elicitation** (responses stay server-side), env delivery via a `CLAUDE_ENV_FILE` SessionStart/hook, and a PostToolUse redaction hook. Same design, different code. Contributions welcome.\n\n## Development\n\n```bash\nnpm install\nnpm run check   # tsc --noEmit\nnpm test        # bun test (pure logic: validation, scrubbing, seeding)\npi -e .         # try the extension in an ephemeral pi run\n```\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliamvinberg%2Fpi-secrets","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fliamvinberg%2Fpi-secrets","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fliamvinberg%2Fpi-secrets/lists"}