Table of Contents generated with DocToc

Security model preparation

The three security-model-* skills cover one lifecycle: getting a project's security model written, findable, and current. This page is the rationale behind them — the decisions that are the same for every adopter, kept in one place so the skills do not each re-argue them.

StageSkillQuestion it answers
Producesecurity-model-prepareThe project has no written model. What does one say, and how does it land without burning maintainer goodwill?
Verifysecurity-model-verifyCan an agent find the model, and does it say enough to route a finding?
Updatesecurity-model-updateSix months of triage decisions have happened. What does the model now say that it did not?

Why a project wants one at all

A security model is the implicit contract between a project and its downstream users: the assumptions it makes about its environment, callers, and inputs; the properties it upholds; the properties it explicitly does not; and the misuses that are syntactically possible but outside the intended use.

It has two readers, and they need different things from the same document:

  • The downstream integrator, deciding which threats the project has taken on and which are left to them.
  • The triager — maintainer, security team, or automated pipeline — who has to classify an inbound report, a scanner finding, or an AI-generated analysis as valid, out of model, or disclaimed by design, and cite the section that justifies the call.

The second reader is why this matters more every year. Automated analysis produces findings at a rate no maintainer can absorb by hand, and the only thing that makes that volume survivable is a document the triage step can consult before a human does. A project without one does not get fewer findings; it gets the same findings with nobody able to close them cheaply.

The rubric is external, and stays external

The specification of what a model contains is maintained publicly by Alpha-Omega:

https://github.com/alpha-omega-security/threat-model

That repository ships an orchestrator plus specialists — recon, surface, interview, authoring, backtest, sidecar, triage — with the section structure (§1.1–§1.19), the provenance tag set, and the closed disposition set with its precedence order in skills/threat-model/references/.

Magpie references it by URL and does not vendor, mirror, or fork it. Two reasons:

  1. A second copy of a specification is a second specification. It drifts, and the drift shows up as a maintainer being told their model is incomplete against a rubric nobody upstream recognises.
  2. The rubric is the part most likely to improve. Pinning a snapshot of it inside Magpie would freeze the least stable and most valuable input.

What Magpie owns is everything around the rubric: the consent conversation, the discoverability chain, the PR mechanics, the confidentiality scrubbing, and the feedback loop from triage history back into the document. Those are governance and workflow concerns, which is Magpie's half of the problem.

The skills therefore cite section numbers (§1.15 known non-findings, §1.17 dispositions) as coordinates into the upstream document. When upstream renumbers, the fix is to update the citing tables — not to start keeping a local copy.

Discoverability: the one hard gate

An agent finds a project's model by following a fixed chain:

AGENTS.md  ->  SECURITY.md  ->  <the model>

Any of four terminations is fine: the model embedded in SECURITY.md, an in-repo file, a project-site URL, or an umbrella model in another repository (the normal shape for build tooling, language ports, and other satellites).

This is the only check that is allowed to block. The reasoning is narrow: a model an agent cannot reach is not a worse model, it is an absent one — the scanner has to treat every component as in scope and every property as unclaimed, and the output is the hundred-finding review the model existed to prevent. Completeness, by contrast, is graded: gaps are proposals the maintainer decides on. Nothing in the completeness rubric is a precondition for anything.

The chain is also cheap to fix, which is why it gets a PR rather than a conversation. Adding one section to AGENTS.md needs no maintainer input, and the diff is small enough to review in the time it takes to read the title.

Why mail, and not a public issue

Everything substantive — model gaps, missing sections, a broken link the project owns — goes to the project's private security list, never to a public tracker. Three independent reasons, any one of which would be sufficient:

  1. Many projects have no usable public tracker. Issues are disabled on the forge, or the project tracks work in a different system entirely. The issue cannot be filed.
  2. A public list of a project's threat-model gaps is an inventory. “The maintainers acknowledge they do not check X” is exactly what a hostile researcher would mine, and publishing it is a favour to them. The private list keeps the same content among people the project has already vetted.
  3. Maintainers who read mail may never see a tracker notification. The conversation almost always starts as mail; continuing it there reaches everyone, including the members who never look at the forge.

PRs are the deliberate exception. They need a repository write anyway, the diff is the artefact being ratified, and “add one link line” carries no inventory worth withholding.

Public-surface discipline

PR titles, PR bodies, commit messages, and branch names on a target repository are public forever. Two rules apply to all of them:

  • No scan-programme, vendor, or engagement identity. The public-facing rationale is “improving the discoverability of the project's security model for automated scanners”. Who is running the review and under what arrangement is information the security team controls the disclosure of; putting it in a commit message forecloses later choices and hands anyone a single string to grep for. On the private list it is fine — that surface is already inside the trust boundary.
  • No tracker contents. A tracker URL or #NNN reference is a stable identifier and is public-safe; the page behind it stays access-gated. Its contents — issue bodies, comment text, team debate, labels, assignees — are not. See the confidentiality rules in AGENTS.md.

This bites hardest in security-model-update, whose entire input is private tracker material and whose entire output is a public document. Its scrub step is not a formality.

Draft-first, and the provenance tags that make it safe

A blank-page request — “could you write up your threat model?” — is an unbounded ask, and it is why most of these efforts produce nothing. A tagged draft is a bounded one: the maintainer reads claims someone else wrote and says yes, no, or not quite per line. Reacting is far cheaper than composing, and the corrections are where the real model surfaces.

That inverts the usual risk, though. A draft written by someone who is not the maintainer, in the maintainer‘s voice, is a document that can quietly launder a guess into the project’s official position. The provenance tags are what prevent it:

TagMeansLicenses closing a report?
documentedLifted from a project artefact, citedYes
maintainerStated by a maintainer, datedYes
assumptionA working premise with an open questionOnly under a declared relaxed policy, low blast radius, never security-critical
inferredThe drafter's guess, with an open questionNo — it escalates, never closes

No hedge variants. Every assumption and inferred claim resolves to a numbered open question. A first draft with no inferred tags is overclaiming.

The asymmetry that governs every judgement call

It appears in the backtest, in the update loop's regression check, and in the rules for what may become a known non-finding. It is one sentence:

Wrongly escalating a non-finding wastes a maintainer's afternoon. Wrongly closing a real vulnerability hands a reporter “not a bug” on a live issue.

So: when the evidence is ambiguous, prefer the wording that leaves reports escalating. Narrow a claim to resolve a conflict; never widen one. A model that closes an item the project historically fixed does not ship — that is the single blocking outcome in both the initial backtest and every later update.

Disclaiming is the cheapest way to make an awkward routing disappear, which makes it the easiest way to pass a quality gate while making the model worse. A disclaimer added because an item routed badly is reverse-engineered from the answer; it still has to be true of the project as it is, cite a real source, and stay inside the scope that source covers.

The update loop, and why §1.15 is fenced

Known non-findings (§1.15) are the recurring-false-positive list, and the section an automated triager is fed verbatim as a negative prompt. They are also first in the disposition precedence order, which means an entry there pre-empts every scope, configuration, dependency, adversary, and property check below it.

A loose entry therefore does not mis-classify one report — it suppresses a class of them, ahead of every other safeguard the model has. Hence the fence:

  • Only two routes feed §1.15: a recurring BY-DESIGN: property-disclaimed close, and an already-established known non-finding recurring again.
  • Nothing under OUT-OF-MODEL:* is ever promoted. Those routes sit below §1.15 in the precedence order; relabelling one lifts it above the checks that decided it, and the class widens without anyone deciding to widen it.
  • Match conditions describe the behaviour of the code, never the quality of the report. No reproducer is not a disposition — an unreproduced report stays open pending a reproducer.
  • The discharging claim must exist in the model and must cover the entry's component.
  • Two independent occurrences minimum. One close is a decision; two with the same discharging claim is a pattern.

The other half of the update loop is the gap list, and the uncomfortable entries are the valuable ones. A model that routes an item the opposite way to how the team actually resolved it, or a rejection a reporter successfully argued the team out of, is the model failing in the expensive direction under real conditions with an independent reviewer. Two reversals against the same claim mean that claim is wrong.

A canned response in <project-config>/canned-responses.md that cites no model section is a gap by definition — the project is stating a position in reporter mail that its own model does not hold. That is precisely what the drafting rule in <project-config>/security-model.md warns about, and this loop is what closes it.

Adopter configuration

The three skills read <project-config>/security-model.md: the authoritative model URL, the repositories in scope and their shape (in-repo model or pointer to an umbrella), the private list to route substantive conversation to, and the branch and license-header conventions the PR helper needs. Everything else comes from project.md.

Cross-references

  • README.md — the security skill family index.
  • process.md — the report-handling lifecycle whose dispositions feed the update loop.
  • confidentiality.md — what may cross to a public surface.
  • poc-handling-policy.md — what an agent may do with reporter-supplied proof-of-concept code encountered while mining the corpus.