Security policy¶
Reporting a vulnerability¶
Use GitHub's private vulnerability reporting
on this repository. If you cannot use that either, contact the maintainer directly through
GitHub: @shivanshjain456. There is no security mailing
list — this is a solo project, so the inbox is one account, and it is this one. Please include:
version (docket --version), platform, the smallest reproduction you have, and whether the
issue lets an agent escape a cap, forge a receipt, or read data Docket promised not to record.
Response targets (best effort, stated so you can hold us to them): acknowledgement within 72 hours, an assessment within 7 days, and a fix or a documented mitigation within 30 days for issues that break a documented guarantee. Credit in the advisory unless you prefer otherwise.
What counts as a security issue here¶
Docket makes narrow, checkable claims. Breaking one of them is a security issue:
| claim | class of bug that breaks it |
|---|---|
| a cap is enforced at the boundary | a call reaches a provider after the cap was crossed; a cap check that a model name, header or request shape can bypass |
| a receipt is tamper-evident | any edit, truncation, reordering or extension of a ledger that docket verify still reports as verified |
| cost is recomputable | a usage path where the recorded cost and the token counts disagree under the embedded price table and verification still passes |
| limits are disclosed truthfully | a receipt that omits, weakens or contradicts limits_disclosure |
| secrets are not recorded | an API key, authorization header, prompt or completion content appearing in a ledger or receipt when it was not explicitly requested |
| the boundary is not a backdoor | the local proxy exposing an endpoint that lets a non-supervised process write arbitrary events into another session's ledger, or read another session's data |
| a restore is confined | docket restore (or a rollback) writing or deleting anything outside the tree it was pointed at, choosing that tree from a forged receipt, following a symlink out of the tree, applying a setuid mode, or overwriting a file that changed after the run without --force |
| a snapshot does not sweep up secrets | a watched symlink whose target is copied into the snapshot store, or a watched root outside the working directory captured under a path a restore could be aimed with |
| Docket's own fetches stay local | a urlopen in the package reaching file://, a cloud metadata address, or any host that is not this machine when the address came from the environment (DOCKET_PROXY_URL, the self-test probes) |
| an agent-side URL is never a file read | an urlopen handed a scheme other than http/https in code that stands in for an agent (demo.py, scriptagent.py) |
Ordinary bugs (wrong output, a crash, an unhelpful message) are normal issues, not advisories.
Not in scope¶
- Escaping the boundary by design. An agent pointed at a provider URL directly, or
superuser, or traffic outside the supervised process tree, is outside what Docket claims —
this is documented in
docs/threat-model.mdand stated in every receipt. - Filesystem writes that violate
deny_write. Detection and reporting is the documented behaviour; prevention is not claimed (a sandbox is the right tool, and the docs say so). - Resource exhaustion of the recording process itself by a supervised agent with the same privileges — an agent that can kill Docket can also edit its files; Docket's answer is verifiability, not tamper-proofness against root.
- A snapshot you asked for containing sensitive content.
--snapshotcopies the contents of the watched tree into.docket/snapshots/, by design: that is what makes a rollback possible. What it excludes — symlinks, roots outside the working directory, anything matched by[files] ignore— is recorded innot_covered. Everything else in the watched tree lands in the store, so watch a directory that does not contain credentials you would not commit, and useignorefor.env, key material and caches. - An attacker racing a restore between its confinement check and its write, on a directory the operator has already handed to an autonomous process. The confinement rules are aimed at hostile documents (see ADR 0004).
- Prompt-injection consequences inside an agent. Docket records what an agent did; it does not judge whether it was wise.
- Denial of service against a consumer of receipts (a 10 GB receipt is a bug report about limits, unless it also bypasses a documented limit check — then it is an advisory).
Supply chain posture¶
- Zero runtime dependencies in the core (
dependencies = []), so the installable surface is the standard library.[sign]addscryptographyand only for Ed25519 signatures. - CI runs lint, the full offline test suite on Python 3.11–3.13, the independent Node verifier against the committed conformance vectors, and a clean-venv install + demo smoke test.
- Releases are built from a tag in CI; the sdist and wheel are attached to the GitHub release
with a signed
SHA256SUMS. The repository is expected to be protected with required reviews and signed commits. docketnever phones home. There is no telemetry, no update check, no analytics, no account. The only addresses Docket itself dials are loopback (its own boundary, its probes) and the provider URLs the user configured — because a supervised agent asked it to.- Every
urllib.request.urlopenin the repository passes its URL through one of two guards before the call, and that is checked rather than promised:tests/test_url_guards.pywalks the package's syntax tree and fails if anyurlopenargument cannot be resolved back to a guard, so a call site added later without one breaks the build. The guards areutil.local_http_url(scheme http/https, a host must be present, and the host must be this machine —127.0.0.0/8,::1,localhost;0.0.0.0is a bind address and is refused as a destination) for Docket's own clients, andutil.http_url(scheme only) for code that stands in for an agent and may legitimately be pointed at a remote provider. The test covers both, including the hostnames that are designed to look local and are not:localhost.evil.example,localhost@evil.example,127.0.0.1.evil.example,2130706433,0x7f000001. - Dependencies are scanned on a schedule (
.github/workflows/security.yml), not only on a pull request:pip-auditaudits the whole development and CI toolchain against the advisory databases, and a failure there means the toolchain has a known vulnerability, which should not appear as an unrelated red build on somebody's change. As of 2026-10-15 it reports no known vulnerabilities. - The claim above about dependencies is checked, not repeated:
tests/test_supply_chain.pyreadsdependencies = []out ofpyproject.toml, pins the three extras by name, and then walks the syntax tree of every shipped module (src/,tools/,spec/,examples/) to assert that the only imports outside the standard library arecryptographyandyaml— two imports, in two files, both optional, both behind a guard so that a clean install can still importdocketand record a capped run. An import that a user's environment does not have is the failure a dependency scanner cannot see, and it is the one this test exists to catch. - Static analysis runs in CI:
bandit -q -c pyproject.toml -r src tools. Suppressions are class-by-class with a written reason in[tool.bandit], never blanket; the URL rule (B310) is not suppressed at the call sites — it is answered by the guards above.
Supported versions¶
Until 1.0, the latest minor release receives security fixes. After 1.0, the latest two minors and the previous major, for a stated window announced at release (the long-term-support policy and is maintained rather than assembled once).