Skip to content

Security: LPK3215/pi-starter

Security

SECURITY.md

Security Policy

pi-starter is a local-first agent scaffold. Its design already excludes several classes of risk (no login, no multi-user session, no public-internet exposure), but you still need to understand the residual risks before deploying.

Supported versions

Version Supported
main (latest) ✅
0.x tags ⚠️ best-effort; upgrade to main for security fixes
Anything older ❌

Security patches are only applied forward — please upgrade before reporting.

Reporting a vulnerability

Do not open a public GitHub issue for security problems.

Email 17538703215@163.com with:

  • A description of the issue and the affected version / commit
  • Reproduction steps or proof of concept
  • Potential impact and any suggested mitigation

The maintainer will acknowledge within 72 hours and coordinate disclosure timing with you. Public disclosure is welcome after a fix is released, or 90 days after your initial report if no fix has shipped by then.

Built-in protections

  • guard extension (src/extensions/guard.ts) intercepts:
    • Dangerous bash patterns (e.g. rm -rf /, mkfs, dd if=/dev/zero)
    • Filesystem paths that escape the current working directory
    • Exception: read on SKILL.md is allowed, because SDK skill loading requires it
  • auth.json file mode is set to 0o600 by npm run setup.
  • Local ~/.pi/agent/skills and ~/.pi/agent/extensions are not scanned by default. Only repo-bundled and explicitly injected (extraSkillPaths / extraExtensions) resources are loaded, so unrelated user-level extensions cannot register bash / write behind your back.
  • Built-in coding tools are off by default (PI_BUILTIN_TOOLS=off). Enabling readonly or coding is an explicit opt-in.

What is not protected

Read this before running pi-starter anywhere but your own machine:

  • guard is not a sandbox. Regex-based interception can be bypassed by command substitution, encoded payloads, symlinks, and other indirect paths. Use containers or a VM if you need isolation — a ready Dockerfile is in the repo root (non-root user; the SDK's own guidance, docs/containerization.md, describes the same pattern: Docker / Gondolin micro-VM / OpenShell). Publish the container port to loopback only, e.g. -p 127.0.0.1:3000:3000.
  • The HTTP server has no authentication. npm run web binds 127.0.0.1 by default, so only local processes can reach it. Setting PI_HOST to a non-loopback address (e.g. 0.0.0.0) makes it answer any client — startup logs a warning when you do. Do not expose :3000 to the internet or a shared LAN without a reverse proxy in front (nginx / Caddy / Traefik) plus your own auth middleware. Note that auth middleware added after createApp() — or inside its configure hook — does not cover the kernel routes; mount createApp(...).app as a sub-app behind your auth instead (see docs/嵌入指南.md).
  • Single user, no authentication. One process, one operator. REST /chat shares a single session and returns 429 while it is busy; the WebSocket channel runs several conversations per client (cap 8, LRU eviction), still for one operator. There are no user identities, no per-user isolation and no quotas — multi-user deployment means one buildAgent() + one session per user, externally separated.
  • Cross-session memory is written to disk. remember / recall persist to ~/.pi/agent/pi-starter-memory.jsonl (default on; PI_MEMORY=off disables it and unregisters both tools). Anything the model is told to remember lands in that file, so treat it like a note about yourself: the file and its directory are created 0600 / 0700 (same policy as auth.json and the provider-key store; best-effort on Windows), and the same store is reachable over the unauthenticated REST /memory endpoints — which is why non-loopback exposure needs a reverse proxy in front (see above).
  • Outbound network is an exfiltration channel, so it is off by default. Enable it with PI_WEB=on, which registers web_fetch (and web_search when a search backend is injected). Both are read-only, size-capped, and refuse loopback / private / link-local / multicast destinations — including when a hostname resolves to one. Redirects are followed manually, one hop at a time, with the host check re-run before each hop is requested, so a 302 to an internal address never results in a request being sent there. What is not mitigated: the model chooses the URL and query string, so anything in the conversation can be encoded into a request to an attacker-controlled host. Treat PI_WEB=on as "this agent may talk to the internet on your behalf", exactly like coding is "this agent may edit your disk". The DNS check is also subject to a resolve-then-connect TOCTOU window (DNS rebinding); pin the resolved address before connecting if you need to close it.
  • PI_BUILTIN_TOOLS=coding hands disk edits and shell execution to the model, including exec (process execution: stdout/stderr, background jobs, kill the process tree). It is not a sandbox and not an interactive terminal. Only enable this in environments where the model is trusted and the working directory is disposable.
  • SQL is read-only (POST /db/query accepts SELECT only, db_query tool refuses writes) — but the DB file itself is a plain node:sqlite file; protect it with your OS's file permissions if you use PI_DATABASE_PATH.
  • API keys in .env: npm run setup copies them into ~/.pi/agent/auth.json (mode 0o600), and the SDK authenticates from that file. .env is additionally loaded into the server's own process.env (loadEnvFile), so the raw key value is present in the server process — which is precisely why it is stripped from every child environment (src/child-env.ts), so exec and MCP spawns cannot read it. Both doors into that file are closed: the HTTP file service (denyNames) and the guard extension that brackets the model's own read / write / edit refuse it — and both check the real target name as well as the literal one, so a symlink inside the workspace (notes.txt -> .env) cannot be used to reach it either. npm run setup also creates the copied .env with mode 0o600 (it holds the key you are about to paste). Raw key values are never returned by the HTTP API or the WS protocol. Still, treat .env as a secret file — never commit it (.gitignore already excludes it).

Dependencies

The scaffold depends on @earendil-works/pi-agent-core, @earendil-works/pi-ai, and @earendil-works/pi-coding-agent. Vulnerabilities in those upstream packages are tracked in the pi repository and should be reported there when clearly upstream-only.

Attestation & verification

Releases are not currently signed. If you need signed artifacts, open an issue and we will discuss adding Sigstore / cosign support.

There aren't any published security advisories