Skip to content

Security: earthwalker17/MiniDSH

SECURITY.md

Security Policy

Reporting a vulnerability

Please use GitHub's private vulnerability reporting for this repository: https://github.com/earthwalker17/MiniDSH/security/advisories/new. Do not open a public issue for a defect that can be exploited.

Include what you can of: the version (minidsh --version), the operating system, the enforcement value on the session's opening sandbox/mode event (minidsh sessions show <id> --json carries it; --audit prints it for a confinable mode), and the session log if one is involved — minidsh sessions show <id> --json, or the file under ~/.minidsh/sessions/. Logs carry credential names, never values, but read a log before attaching it. This is a small project with one maintainer; expect an acknowledgement within a week.

Supported versions

The latest 1.x release.

Where the boundaries are

MiniDSH is a coding agent that edits files and runs commands on your machine, so it is worth being precise about what is a security boundary and what is not. The authoritative description is docs/ARCHITECTURE.md §7 (authority) and §13 (known limitations); this is the summary.

  • The model is never the boundary. It proposes; policy decides; the effect boundary enforces; every decision is a durable event in the session log.
  • File writes are fenced inside the fs provider, in process, against the session's recorded sandbox mode: canonicalize, contain, refuse before any effect. Reads are unrestricted by design. The fence follows symbolic links and cannot follow a hard link, so a hard link inside the workspace whose inode lives outside it lets a write through. That is a documented limitation, not an undisclosed one.
  • Shell commands are confined by the operating system where a backend exists — bubblewrap on Linux, Seatbelt on macOS — and the backend is probed functionally before the harness claims it. Windows has no backend, so by default the harness refuses to run a command confined there: the model asks for a one-shot escalation and a person grants it (--approve grants them all on a headless run). A session may instead record that it accepts weaker enforcement (--accept none, or /accept none), which runs the shell unconfined under the session's own mode with the in-process file fence intact — a durable, per-session, replayable fact. On serve, web and chat the flag is the deployment's default, and a weakened default is written into every session it opens, for each confinable mode, so a stored log says what it accepted: a line, or full by its absence. Confinement governs file effects only, and by path: a confined command writes through a hard link that already sits in the workspace, like the file fence (measured under bubblewrap and Seatbelt); neither backend lets it create one across the boundary. A confined command still reaches the network. Its environment is scrubbed of the credential names this deployment declared and of MINIDSH_*, and of nothing else, but that is defence in depth, not a boundary: reads are never fenced, so ~/.minidsh/credentials.json stays readable, and any secret the deployment did not declare passes through.
  • A background job runs under the authority its start resolved — the session's mode and recorded acceptance, never an escalation — and a durable switch of either ends it. It is stopped when its session ends; a job process that outlives a killed MiniDSH process (on macOS, and unconfined on Linux, where bubblewrap does not take it down) runs unsupervised, and the next resume reports it lost without stopping it. On Windows each PowerShell the harness starts holds what it starts in a kill-on-close Job of its own, so a job's processes die with it and with a killed host; a process created through WMI or the Task Scheduler, or one that asks to break away from the Job, escapes, and so does everything where Constrained Language Mode refuses the containment (a warning says so).
  • An approval says what the runtime will do. The line a person answers carries a subject the harness builds from the validated call arguments — the command, and the sandbox mode and enforcement it would run under — plus the whole call as the log recorded it. The model's own justification appears beside that, never in place of it. A yes may be extended to that exact action for the rest of that run of the session; such a consent is a durable event, it is offered by the host rather than invented by a surface, it dies the moment any authority knob moves or the run ends (a resume or a fork asks again), and it can be revoked.
  • A network tool (the opt-in web_search) asks before every query leaves the machine, on a line naming the query, where it goes and the credential it goes as; that consent is the only authority over egress — nothing confines or records the request — and a delegated child is not offered the tool.
  • The browser tools (on by default where Chrome, Edge or Chromium is installed) drive a headless browser the harness starts over a pipe, with a fresh profile and downloads refused. Inside the browser, a proxy the harness owns and resolver rules confine it to localhost, the workspace's own files and any allowHosts the row names, and a canary proves that fence each time it starts. That fence is the browser's own, not the shell's, and it is drawn around the internet, not around this machine: the browser reaches every service listening on localhost, under every sandbox mode and from a delegated child, without asking and without a record, and such a service may act on its own authority. It asks no consent by design, so a verifier child can use it.
  • Skills are data, never authority. A repository's .agents/skills or .claude/skills is listed to the model like any file it might read: a skill's text can steer the model, its scripts run as ordinary shell commands under the session's authority, and it never replaces a skill of yours or a bundled one. On Linux and macOS a .git above the working directory that another user owns ends the search for repository skills and AGENTS.md at the working directory.
  • A delegated child runs under a ceiling it cannot widen; the ceiling is recorded at its creation and enforced before dispatch.
  • Provider keys stay out of what the harness writes: a key is redacted from any provider or proxy error before that error reaches the log, the model or stderr, and a base URL carrying a username or password is refused.
  • minidsh web binds loopback by default and exchanges a one-shot launch token for a signed, HttpOnly, SameSite=Strict cookie behind a Host/Origin fence, and refuses to be framed by another page. That is reachability, not identity: whoever can open the URL is the one principal. Binding to a non-loopback address is the operator's decision.
  • composition.json and any module-loaded plugin are code-equivalent trust. The harness warns about authority-sensitive rows a layer changed; it does not sandbox them.
  • Model-written text that reaches a terminal is sanitized where it is recorded (approval reasons, at the approval seam) and where it is rendered (src/app/present.ts).

Things that would be vulnerabilities, and that we would like to hear about: a write that lands outside the workspace on a confined host without an approval, other than through a hard link that already existed in the workspace, or a file left outside by another process swapping a directory for a link mid-write (only ever empty on Linux, best effort elsewhere; both documented, ARCHITECTURE §13); a browser that reaches a non-loopback host its row did not allow; a provider key in a log, an error or a tool result; an approval whose recorded subject or reason differs from what the person was shown, or from what the call actually runs; two different actions that share one standing consent; a query that leaves the machine without the consent its line showed, or with characters the line did not show; a standing consent that survives a narrowing of the session's authority; a way to reach the web surface without the cookie; a session log that records less authority than was exercised; a way for a delegated child to widen its ceiling; a way for repository content (an AGENTS.md, a file the model reads) to change what the harness is allowed to do rather than what the model is told.

There aren't any published security advisories