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.
| Version | Supported |
|---|---|
main (latest) |
✅ |
0.x tags |
main for security fixes |
| Anything older | ❌ |
Security patches are only applied forward — please upgrade before reporting.
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.
guardextension (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:
readonSKILL.mdis allowed, because SDK skill loading requires it
- Dangerous bash patterns (e.g.
auth.jsonfile mode is set to0o600bynpm run setup.- Local
~/.pi/agent/skillsand~/.pi/agent/extensionsare not scanned by default. Only repo-bundled and explicitly injected (extraSkillPaths/extraExtensions) resources are loaded, so unrelated user-level extensions cannot registerbash/writebehind your back. - Built-in coding tools are off by default (
PI_BUILTIN_TOOLS=off). Enablingreadonlyorcodingis an explicit opt-in.
Read this before running pi-starter anywhere but your own machine:
guardis 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 readyDockerfileis 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 webbinds127.0.0.1by default, so only local processes can reach it. SettingPI_HOSTto 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:3000to 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 aftercreateApp()— or inside itsconfigurehook — does not cover the kernel routes; mountcreateApp(...).appas a sub-app behind your auth instead (seedocs/嵌入指南.md). - Single user, no authentication. One process, one operator. REST
/chatshares a single session and returns429while 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 onebuildAgent()+ one session per user, externally separated. - Cross-session memory is written to disk.
remember/recallpersist to~/.pi/agent/pi-starter-memory.jsonl(default on;PI_MEMORY=offdisables 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 created0600/0700(same policy asauth.jsonand the provider-key store; best-effort on Windows), and the same store is reachable over the unauthenticated REST/memoryendpoints — 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 registersweb_fetch(andweb_searchwhen 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 a302to 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. TreatPI_WEB=onas "this agent may talk to the internet on your behalf", exactly likecodingis "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=codinghands disk edits and shell execution to the model, includingexec(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/queryacceptsSELECTonly,db_querytool refuses writes) — but the DB file itself is a plainnode:sqlitefile; protect it with your OS's file permissions if you usePI_DATABASE_PATH. - API keys in
.env:npm run setupcopies them into~/.pi/agent/auth.json(mode0o600), and the SDK authenticates from that file..envis additionally loaded into the server's ownprocess.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), soexecand MCP spawns cannot read it. Both doors into that file are closed: the HTTP file service (denyNames) and theguardextension that brackets the model's ownread/write/editrefuse 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 setupalso creates the copied.envwith mode0o600(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.envas a secret file — never commit it (.gitignorealready excludes it).
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.
Releases are not currently signed. If you need signed artifacts, open an issue and we will discuss adding Sigstore / cosign support.