Skip to content

Latest commit

 

History

History
166 lines (130 loc) · 8.93 KB

IRP.md

File metadata and controls

166 lines (130 loc) · 8.93 KB

Incident Response Plan

How the maintainers of modernc.org/sqlite respond when something goes wrong: a confirmed vulnerability, a bad release, or a compromised account.

SECURITY.md is the public-facing document -- how to report, what to expect, what is in scope. This one is the procedure we follow afterwards. It is published for the same reason Bootstrap publishes theirs: a plan nobody can read is hard to trust. Operational detail that would help an attacker -- the credential inventory, where each token lives, how to revoke it -- is kept in the maintainers' private notes, not here.

What counts as an incident

  • A confirmed vulnerability in code we ship: the hand-written driver, the transpiled SQLite in lib/, sqlite-vec in vec/, or the VFS bridge in vfs/.
  • A vulnerability in SQLite itself, which reaches users through this module rather than through their system packages.
  • A released version that is broken, wrong, or malicious.
  • Compromise of a maintainer account, an access token, or the push mirror.
  • A secret committed to the repository or exposed in logs.
  • A builder result that cannot be trusted: a target reported green that did not build or pass. The builder dashboard is what a release is tagged on, so a false green is a release decision made on bad data. Every tag made on it is suspect until the affected targets are re-run; if one then fails, Phase 5 applies.

An ordinary bug, however severe, is not an incident. It goes through the normal issue and release process.

Who runs it

There are two maintainers, roughly six hours apart (Central European and UTC+8). Whoever picks the report up first says so explicitly in the thread and runs it until they hand over. That one sentence prevents the common failure of a two-person team: both people investigating, or each assuming the other has.

Phase 1 -- Triage

  1. Acknowledge the reporter. The commitment in SECURITY.md is seven days; usually it is much sooner.
  2. Reproduce it. If there is no proof of concept, write one. If it cannot be reproduced, say so to the reporter and ask for specifics rather than closing.
  3. Decide whether it is a vulnerability at all, and whether it is ours. See the scope section of SECURITY.md.
  4. Assess severity: what an attacker gets, and what they need to already control -- hostile SQL, a hostile database file, or only hostile data in a table.
  5. Open a draft GitHub Security Advisory to hold the work. Private vulnerability reporting is enabled, so a report that arrives that way already has one.

Phase 2 -- Scope it

Answer these before writing any fix; they determine who fixes what, and where.

  • Which layer? Hand-written Go, generated Go, or SQLite's own C. This decides the entire remediation path, and the layers are not equally ours to fix.
  • Which targets? The module supports 20 GOOS/GOARCH combinations and the generated code differs per target. A fault may exist on some and not others.
  • Which versions? When was it introduced, and is the current release affected. For generated code, git show vX.Y.Z:vendor.json names the libsqlite3 and libsqlite_vec commits a release was vendored from.
  • Does the fix move modernc.org/libc? If so it cannot ship from this repository alone; see issue #177.

Phase 3 -- Fix it

The path depends on the layer, and only the first is quick:

Layer Path
Hand-written Go (driver, vtab/, pcache/, vfs/ Go side) Fix on GitLab, release.
Generated Go in lib/, vec/ The fix belongs in modernc.org/libsqlite3 (and modernc.org/libsqlite_vec for vec/), which own the transpilation and the patch set. Re-transpile there, make vendor here, make build_all_targets, release.
Generated Go in vfs/ The C is vfs/c/vfs.c, in this repository. Fix the C and re-transpile it here; each generated file's first line records the ccgo command that produced it.
SQLite's own C Report upstream at sqlite.org. Upstream owns the fix; we own shipping it. If the wait is unacceptable, a local patch can go into the libsqlite3 patch set and be dropped when upstream lands theirs -- there is precedent in the v1.56.0 journal-rollback fix.

When the fix moves modernc.org/libc, it is four releases in one incident, in this order, and none can be skipped or reordered, because each pins the one before:

  1. modernc.org/libc, released with the fix.
  2. modernc.org/libsqlite3, its go.mod bumped to that libc, re-transpiled, tagged.
  3. modernc.org/libsqlite_vec, bumped to both, re-transpiled, tagged.
  4. Here: both sibling checkouts at those tags, go.mod on the new libc, make vendor, the builders green on all 20 targets, then the tag. make vendor refuses a dirty checkout, two checkouts on different libc versions, and a libsqlite_vec that requires another libsqlite3 commit than ../libsqlite3 is at, and records what it used in vendor.json. The test suite fails when the libc in go.mod differs from the one in vendor.json, so the go.mod bump goes in the same push as make vendor, or every builder is red until it does.

lib/ and vec/ must come from the same libc. Do not re-vendor one while the other stays on the old version.

Four rules that do not bend under time pressure:

  • Never merge a pull request on the GitHub mirror. GitHub master would gain a commit GitLab does not have, and because the mirror keeps divergent refs and force-push is disabled, every later mirror push fails. Cross-merge into GitLab by hand, as HACKING.md describes.
  • Do not bump modernc.org/libc on its own. The generated code is pinned to the exact version go.mod names.
  • Tag manually, and only when the builders are green. builder.json sets "autotag": "<none>" deliberately. A fix that breaks three platforms is a second incident.
  • Run make sbom after any dependency change. CI fails on stale documents, and an SBOM that misstates what shipped is worse than none during an incident.

Phase 4 -- Disclose

As SECURITY.md states: a GitHub Security Advisory, then an entry in the Go vulnerability database so govulncheck reports it, then the release note. The middle step is the one that actually reaches Go developers and is part of shipping the fix, not an afterthought.

Credit the reporter by name unless they prefer otherwise.

Phase 5 -- When a released version is the problem

A published Go module version cannot be recalled. This is the fact to have internalised before the day it matters. From the module mirror's own FAQ:

Whenever possible, the mirror aims to cache content in order to avoid breaking builds for people that depend on your package, so this bad release may still be available in the mirror even if it is not available at the origin. The same situation applies if you delete your entire repository.

Deleting the tag does nothing. Deleting the repository does nothing. The checksum database has it permanently.

What actually works:

  1. Release a fixed version.
  2. Add a retract directive to go.mod naming the bad version or range, with a comment saying why, and release that. Retraction is advisory -- the version stays downloadable -- but go get stops selecting it and go list -m -versions stops offering it.
  3. Say so in CHANGELOG.md.

The retract block in go.mod already carries seven entries. It is load-bearing history, not clutter: never remove entries while editing it.

Phase 6 -- Account or token compromise

Assume the worst ordering: revoke first, investigate second.

  1. Revoke before diagnosing. Sessions, then tokens, then keys. A token that might be compromised is compromised. The mirror push credential is a case of its own. It can rewrite what GitHub users see while GitLab looks fine, so nothing on the canonical side reveals its misuse. Revoke it with the rest, and compare the mirror's branches and tags against GitLab's rather than assuming they match.
  2. Check what was done with it, not only what it could do: recent commits on both hosts, tags created, releases published, mirror settings, workflow changes, and whether any release artifact changed after it was tagged.
  3. Re-establish the account: authentication factors, recovery codes, and confirm no additional factor or key was added by someone else.
  4. Assume any release made during the window is suspect and treat it under Phase 5, which is the only reason Phase 5 exists in this document.

The specific credential inventory and revocation URLs live in the maintainers' private notes, deliberately not here.

Phase 7 -- Afterwards

Write down what happened and what was slow, while it is still annoying. If the response revealed a gap, fix the document that should have prevented it -- SECURITY.md, CONTRIBUTING.md, HACKING.md, or this plan. A post-incident note that changes no document has not finished.