What we can promise,
and what we can't.
Quire runs on your machine, which removes whole categories of risk and creates others. This page is the threat model, with no compliance badge wall attached. There is no vendor here to be trusted or audited.
No server, no account, no us
There is no Quire service. We hold no keys, store no documents and have no ability to access anything. A breach of our infrastructure exposes a website and a git repo.
No telemetry, verifiably
No analytics dependency and no phone-home path. Not an opt-out you have to trust. grep the repo. Network egress in the default config is zero.
But you're the operator now
Which means index file permissions, who can reach port 7777, and which model endpoint you configured are all yours to get right. Most of this page is about that.
Where the real risk is.
| Risk | Who handles it | What we do |
|---|---|---|
| Documents leaving your machine | Shared | Local model by default. offline = true makes any outbound call a hard error, not a silent fallback. |
| Index file readable by other users | You | Created 0600. We can't stop you moving it somewhere world-readable, and people do. |
quire serve exposed to a network | You | Binds to localhost; --host 0.0.0.0 prints a warning. There is no auth layer, see below. |
| Prompt injection from indexed content | Us, partially | Retrieved spans are passed as data, never as instructions, and the verifier rejects claims unsupported by the span. Not a solved problem; see the limits section. |
| Malicious plugin from PyPI | You | Plugins run in-process with full privileges. We don't vendor or endorse third-party plugins. Read them. |
| Compromised release artifact | Us | Signed, reproducible builds with attestations. Verification steps below. |
| Your model provider retaining prompts | You | Their terms, not ours. If this matters, stay local. It's the default for a reason. |
There isn't any, and that's deliberate.
quire serve has no login, no tokens and no user model. Half-built auth in a self-hosted tool is worse than none, because it invites people to expose the port believing they're protected.
If you need to expose it, put it behind something that does this properly: a reverse proxy with OIDC, a VPN, an SSH tunnel. The docs show all three. We would rather be visibly missing a feature than quietly bad at a security-critical one.
$ quire serve --host 0.0.0.0
warning: binding to 0.0.0.0 with no authentication.
Anyone who can reach this port can read your entire
index. Put a reverse proxy in front of it.
see: docs/deploying.md#behind-a-proxy
continue? [y/N]
$ gh attestation verify \
--owner getquire quire-3.0.1.whl
✓ subject quire-3.0.1.whl
✓ built from getquire/quire@a1f22c9
✓ workflow release.yml
✓ reproducible hash matches rebuild
# or check the SBOM shipped with every release
$ quire --sbom | jq '.components | length'
31
Thirty-one dependencies, and we argue about each one.
The most likely way this project hurts you is a compromised dependency, not a bug in our code. So the dependency count is a number we actively defend.
- Reproducible builds — anyone can rebuild a release and compare hashes
- Build provenance attestations on every artifact, via GitHub OIDC
- SBOM published with each release and available from the CLI
- Dependencies pinned by hash; updates reviewed by a human, not a bot merge
- Adding a dependency requires an argument in the PR, and most fail
- Annual third-party review, funded by the grant — reports published in full
What we haven't solved.
Prompt injection isn't fully solved
If an attacker can put a document into your corpus, they can attempt to influence an answer. Spans are passed as data and verification rejects unsupported claims, which blocks the crude version. It does not block a document that is itself a plausible-looking lie. Nothing does, short of you trusting your sources.
Plugins are unsandboxed
An entry-point plugin runs with your privileges. We considered sandboxing and concluded a bad sandbox would be security theatre. Treat installing a Quire plugin exactly like installing any Python package, because it is one.
The index is not encrypted at rest
It's a SQLite file with 0600 permissions. If your threat model includes someone with your filesystem, use full-disk encryption. We'd add nothing by implementing a weaker layer on top.
We are five people
One annual external review, no dedicated security engineer, no 24/7 rotation. Critical reports get same-day attention in practice, but there's no contract behind that. Plan accordingly.
Mail security@quire.dev.
Never a public issue. PGP key fingerprint is in SECURITY.md and on the keyservers.
- Within 24h — acknowledgement from a human, not an autoresponder
- Within 72h — an initial assessment and a severity we agree on with you
- Within 90 days — a fix and a coordinated disclosure, sooner for anything critical
- Always — credit in the advisory unless you'd rather not have it
There is no bug bounty. We're funded by sponsors and can't responsibly promise money we may not have. We'd rather say that than run a programme that pays late.
Safe harbour
Test against your own installation as much as you like. We will not pursue anyone acting in good faith against their own instance. Don't test against someone else's deployment. That's their machine, not ours to authorise.
Past advisories
- GHSA-2026-0004 — path traversal in the archive parser via a crafted zip. Fixed in 2.9.4. Reported externally, credited.
- GHSA-2026-0002 —
--hostbound to all interfaces without warning. Fixed in 2.8.1 by adding the prompt shown above. - GHSA-2025-0011 — API key logged at debug level in one code path. Fixed in 2.4.2, keys rotated advisory issued.
All three published in full, including the timeline and what we got wrong. Advisories with no detail teach nobody anything.
Found something?
Acknowledged within a day, by a person. We don't threaten researchers, and we've never asked anyone to stay quiet after a fix shipped.