Please report security issues privately, not through public GitHub issues.
Use GitHub's private vulnerability reporting (Security → Advisories → Report a vulnerability) so the report stays confidential until a fix is available. Include reproduction steps and the affected version or commit. You can expect an initial response within a few days.
If you cannot use GitHub's reporting flow, email contact@lhansen.dev instead.
Radulf is designed as a single-user, local-first tool. Its threat model assumes the operator trusts the machine it runs on. Two properties matter:
- Loop agents execute arbitrary code, bounded by the run sandbox. Cards
run a coding agent against a local git repository inside an isolated
worktree, and that agent runs with
--dangerously-skip-permissions— no interactive approval, no deny-list the model argues with (decision 7). As ofspecs/14-sandboxing.md, that permission posture is enforced by the kernel, not just by prompt text and the worktree cwd: an OS sandbox (Seatbelt on macOS, bubblewrap + seccomp on Linux) restricts agent bash's filesystem access, network egress, and Unix sockets, and in-process path guards constrain the file tools to the same boundary. This narrows the blast radius; it does not eliminate the trust requirement. Operator still trusts the machine: the sandbox is not a security boundary against a hostile operator, a compromised host, or a malicious model provider — see spec 14's threat model for the full list of trusted/untrusted parties, andSANDBOXING.mdfor how the containment is implemented. Only register repositories and run tasks you would run yourself. Accepted residual: everything the agent reads reaches the model provider by construction (tool output enters the context window), and a merged diff — once a human approves it in In Review — is trusted like any other commit; the sandbox bounds what a run can do, not what an approved diff can do. One optional, opt-in residual (default off):sandboxWeakerIsolationForGoTlsallows the macOStrustdservice so Go-family toolchains (go, gh, gcloud, terraform, kubectl) can verify TLS — without it their HTTPS fetches fail under the sandbox.trustdruns outside the sandbox and its certificate-revocation lookups bypass the egress allowlist, a low-bandwidth exfil channel; enable it only for repos whose toolchain needs it. - Auth is optional and gates the whole app. With no auth configured the
make dev/make starttargets bind127.0.0.1and Radulf is meant for localhost only. To expose it beyond localhost you must setRADULF_AUTH_PASSWORD_HASH(a bcrypt hash); this puts every page, API route, and SSE stream behind a single-password login, and flips the bind to0.0.0.0. The bind address is derived from that one variable so the insecure combination — listening on the LAN with the login gate off — is not reachable by accident. Note that Next's own default is0.0.0.0: if you invokenext dev/next startdirectly instead of through the Makefile, pass-H 127.0.0.1yourself. See README → Authentication andspecs/08-hosting-auth.mdfor the full model, including CSRF/origin checks (RADULF_ALLOWED_ORIGIN) and session rotation.
The cross-origin (CSRF) check on mutating requests applies whether or not
auth is enabled. Radulf drives coding agents with
--dangerously-skip-permissions, so a page the operator merely visits must not
be able to blind-POST to their localhost instance — a cross-origin JSON fetch
is stopped by preflight, but a text/plain body is a CORS simple request that
would otherwise reach the handler unpreflighted. Requests carrying no Origin
header at all (curl, scripts, the app itself) are unaffected.
Provider API keys (OpenRouter, oMLX, Brave) are write-only over HTTP:
GET /api/settings renders any key that is set as ••••••••, and sending that
marker back leaves the stored value untouched. They are still stored in
cleartext in the SQLite DB under data/ — that file is as sensitive as the keys
in it, and the sandbox denies agent access to data/ wholesale.
Session cookies are HMAC-signed with the secret in data/auth-secret. There is
no server-side session store, so the kill switch for a leaked cookie is rotating
that secret: delete data/auth-secret and restart — every outstanding session
becomes invalid at once.
Radulf is pre-1.0 and under active development. Only the latest main is
supported; fixes land there.