Security

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.

Threat model

Where the real risk is.

RiskWho handles itWhat we do
Documents leaving your machineSharedLocal model by default. offline = true makes any outbound call a hard error, not a silent fallback.
Index file readable by other usersYouCreated 0600. We can't stop you moving it somewhere world-readable, and people do.
quire serve exposed to a networkYouBinds to localhost; --host 0.0.0.0 prints a warning. There is no auth layer, see below.
Prompt injection from indexed contentUs, partiallyRetrieved 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 PyPIYouPlugins run in-process with full privileges. We don't vendor or endorse third-party plugins. Read them.
Compromised release artifactUsSigned, reproducible builds with attestations. Verification steps below.
Your model provider retaining promptsYouTheir terms, not ours. If this matters, stay local. It's the default for a reason.
On authentication

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.

binds 127.0.0.1warns on 0.0.0.0no session state
what it says when you expose it
$ 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]
verify a release
$ 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
Supply chain

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
Limits

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.

Reporting

Mail security@quire.dev.

Never a public issue. PGP key fingerprint is in SECURITY.md and on the keyservers.

  1. Within 24h — acknowledgement from a human, not an autoresponder
  2. Within 72h — an initial assessment and a severity we agree on with you
  3. Within 90 days — a fix and a coordinated disclosure, sooner for anything critical
  4. 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--host bound 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.