Methodology 2.9.1 in effectIndependent examination of the packages AI agents install

Before an AI agent installs a package, we read it. Every grade comes with the evidence.

When we can’t read a package, we say so — no guess, no grade.

Attestari reads the file your package manager downloads — not the GitHub repo §3 — and runs the same fixed checks §5 on every MCP server and agent tool on the public registries §1. Every finding shows the file, the line, and the exact text we found §7. A package we could not examine gets no grade, and the reason is published instead §9.

Every verdict public · No publisher pays §17

Sample decision row

Illustrative · invented package

mailship-mcp

npm · 2.4.1

Grade

highest confirmed severity: medium

C

  • model_directed_textmediumconfirmed

    description_reassigned_at_runtime · python:@mcp.tool

    mailship/server.py:88send_message.__doc__ = _PROMPT_V2

    blob 06975638e17a…

  • publisher_provenanceinfonoted

    no_provenance_attestation

    registry record"attestations":null

    blob de4dd43e3767…

Counts
high 0 · medium 1 · low 0 · info 1
Methodology
2.0.0
Sample decision row for an invented package, shown to illustrate the shape of a result.

The problem

Verified once. Trusted forever.

What a registry checked when a package was listed is not what you install today.

A registry checks a package once, when it is listed.

The version that was checked is not the version that installs next month. A rating here is always of one version, never of a project §2. A result describes the artifact we fetched, identified by hash, on the date stated. It says nothing about what the same version number serves tomorrow §15. That is why every version is examined, not only the first.

The description rug pull

research proof of concept · April 2025

A tool description is read by the agent, not by the user §5. In April 2025 Invariant Labs showed a description that quietly told the agent to read the client’s configuration and an SSH key and pass them along in an unrelated parameter: tool poisoning.

Six days later they showed the rug pull. A server whose one tool, get_fact_of_the_day, carried a harmless description on first launch. On the second launch the module reassigned that description to a block telling the agent to redirect every WhatsApp message to a proxy number, attach the previous chat history, and not notify the user.

What the artifact shows, we report: the poisoned description as a finding of severity high, and the reassignment as a finding of its own, description_reassigned_at_runtime §5 §15. What the description becomes at run time is not in the artifact, and we say so rather than guess.

Sources: Invariant Labs, “MCP Security Notification: Tool Poisoning Attacks” (1 April 2025) · Invariant Labs, “WhatsApp MCP Exploited: Exfiltrating your message history via MCP” (7 April 2025). Both are among the publicly disclosed cases behind the description check §18.

A hidden BCC added to handler code, as in the postmark-mcp package on npm in September 2025, is a change in what the code does rather than in what it says; no check here reads behaviour, and the methodology says so §15.

The grade

One ladder · fails closed

Four grades, and one outcome that is not a grade.

The grade is read off the highest confirmed severity §11. There is no score, no average and no partial credit: one confirmed finding at high is an F whatever else the package does well. And a package is graded only when every guard of the gate holds §9; otherwise it is not gradeable, with every reason recorded.

  1. Grade A: No confirmed finding.

    Only noted context, or nothing. Under 2.9.1 that means every tool, prompt and resource description (and every prompt's message text) was readable and the one measured check, the description analysis, found nothing in it. Findings from checks whose precision is not yet measured are noted at info and cannot move the grade (decision D5).

  2. Grade B: Highest confirmed severity is low.

    A property of the publisher record that leaves the operator with no one to hold to account, without by itself changing what the package does.

  3. Grade C: Highest confirmed severity is medium.

    A property that materially weakens the operator's ability to know what they are running, or that concentrates control in a way worth knowing about before installing.

  4. Grade F: Any confirmed finding of severity high.

    An operator running this subject is exposed to something they would not reasonably expect from its stated purpose: code they cannot inspect, credentials reaching a destination they did not choose, or instructions to the agent that work against the operator.

  5. Not gradeable. A guard failed, or could not be evaluated.

    The failure we care most about avoiding is not a wrong grade. It is a passing grade on something nobody looked at. A package we couldn’t examine is published as “not gradeable” with the reasons why. It never looks the same as a package we examined and found clean §9. “We cannot tell you what this does” is a legitimate published outcome, and for a meaningful share of popular packages it is the accurate one.

We don’t run the code; what the code does when called isn’t graded yet.

Under 2.9.1 no measured check assigns low, so no package currently receives B, and C currently comes from one finding only: a tool description replaced at run time (description_reassigned_at_runtime). §11

The description check was admitted on 39 attack spans and 456 clean descriptions, against a published bar of 200 labelled spans and 1,000 clean descriptions. The phase-1 corpus reached that bar on 2026-09-17 and the figures pass the test on it; its labels are two independent model passes with adjudication, and no person has labelled it yet. Until a human pass confirms the labels, “measured” carries this note. §18

Measured on 2026-10-07 over 1,907 labelled descriptions (1,949 spans), under methodology 2.9.1, claude-opus-5, prompt 2026-09-28.10-b64-fewshot-counts-nothink, every proposal read once more by model:claude-opus-4-8@labeller-2026-09-18.1: 87 of 89 real descriptions found (population and research; 245 of 247 synthetic descriptions apart), recall on real descriptions 97.8%, Wilson 95 % 92.2% to 99.4%; precision 98.5% on the found descriptions (332 of 337, Wilson 95 % 96.6% to 99.4%); 0 false positives across 1408 clean descriptions (0.0%, Wilson 95 % 0.0% to 0.3%) and 0 across 93 hard negatives (0.0%, Wilson 95 % 0.0% to 4.0%); 19 description(s) not analysed and excluded from every rate. Before the independent reading, the check alone proposed a finding on 88 of 89 real descriptions (98.9%, Wilson 95 % 93.9% to 99.8%), with precision 94.6% on the descriptions it proposed on (333 of 352, Wilson 95 % 91.7% to 96.5%), 4 false positives across 1408 clean descriptions (0.3%, Wilson 95 % 0.1% to 0.7%) and 2 across 93 hard negatives (2.2%, Wilson 95 % 0.6% to 7.5%); the reading decided the rest. Labelled by two independent model passes (model:claude-opus-5@labeller-2026-09-18.1, model:claude-sonnet-5@labeller-2026-09-18.1; kappa 0.977 on 1949 records); disagreements and byte-identical inconsistencies adjudicated by a third model pass (model:claude-opus-4-8@adjudicator-2026-09-18.1, model:claude-opus-4-8@adjudicator-2026-09-18.2, model:claude-opus-4-8@adjudicator-2026-09-27.3) citing the guide, and by attestari-engineers@2026-09-18 (12 records), and by claude-adjudicator (8 records); a third labeller (model:claude-opus-4-7@labeller-2026-09-18.1) agreed with the gold set on 99 of 100 (99.0%, Wilson 95% 94.6%–99.8%) of a seeded sample (40 positive, 20 hard negative, 40 clean). No person has labelled the corpus. §18

Reason codes, from the gate map the engine exports §9 §7

advertised_tools_not_parsedG3
The README or a bundle manifest advertises tools the extractor did not parse; the mismatch is itself the finding.
archive_unreadableG3
The artifact is not a readable tar or zip.
check_erroredG5
A fetch or a check crashed on this package (a failure with a traceback, or in the fetch or check stage) and the run recorded it.
check_missingG5
A configured check has no result row for this package; a check that did not run cannot have found nothing.
declaration_not_parsedG3
A registration site exists in readable source and the extractor could not read it: our gap, recorded as such.
evidence_unpublishablepublish
A finding's evidence cannot be published without personal data; the finding is withheld, not redacted, and the subject is published as not gradeable.
excerpt_unverifiedG6
A finding's quoted excerpt is not byte for byte inside the cited evidence blob, within the cited lines when lines are cited.
extraction_budget_exceededG3
Reading the package's source passed the per-package budget of wall time or resident memory, so nothing past that point was read; the budget is measured, the measured figures are recorded on the row and published with it, and the grade is refused.
gate_blockedG2
The gate row recorded by the run says the package is not gradeable.
gate_file_missingG1
The run has no gradeable.json, so nothing in it may be read as gradeable; every package in the run is refused.
gate_not_recordedG1
gradeable.json has no row for this package: the gate was never recorded for it.
gate_recomputed_blockedG3
The gate, recomputed at decision time from the run files, refuses the package; its own reason codes are listed with it.
minified_onlyG3
Registration text exists only in a minified or obfuscated bundle: a medium finding, and no grade.
model_check_skippedG3
The description analysis did not run on a package that has parsed tool descriptions, including when the analysis service was unavailable and the retries ran out: an examination incomplete on our side, not a finding about the package.
no_registration_siteG3
Neither a registration site nor a consumption pattern was found in the artifact.
no_surface_examinedG3
No tool, prompt, resource or resource template was parsed from the published source; the package's advertised behaviour was never visible.
not_a_serverD4
The package consumes or defines the tool API rather than serving tools: out of scope, neither graded nor refused, counted apart from the population.
not_analyzedG4
A tool was never analysed: the model provider declined or never finished the call, a replay had no record, or the description is declared but not readable as a literal.
partial_descriptionG3
A tool's description was read with holes and nothing was found in its readable segments; that does not establish a clean tool. Not applied when a high finding already places the package at F.
reading_missingG8
A model_directed_text finding above info has no independent reading recorded (readings.json absent, the finding not in it, or its call not completed), so it can be neither confirmed nor noted and the package is refused.
runtime_generatedG3
The file that registers tools builds them from external data at run time; the tool list exists only when the server runs.
surface_not_recordedG3
surfaces.json has no row for this package, so the gate cannot be recomputed at decision time.
unknownG3
The surface row was written before the surface taxonomy existed and records no outcome; the grade is refused rather than guessed.
verifier_failedG7
A model-derived finding's excerpt is not inside a description span of a tool declared at the cited path, by the same test the verifier applied when the proposal was made.

A package that is not a server at all, a client, an SDK, a CLI, is out_of_scope: neither graded nor refused, and counted apart from the population §10.

Every version

A rating is of a version, never of a project

Verified once is not verified. So every version is examined again.

The scheduled pass runs with no human input: it examines the candidates, decides every finding under the methodology in force, and diffs the result against the previous decided run §14. Nothing in a run is decided by a person §13. When a publisher fixes what a finding describes, the new version is re-examined free and promptly, and the updated result is published §16.

diff.json · packages.changed §14

Illustrative · invented package

npm:mailship-mcp

previous

2.3.0

A

current

2.4.1

C
One row of the diff the scheduled pass writes. The grade moved because the new version carries a confirmed finding the previous one did not; the finding, with its file, line and evidence hash, is in the decision row §12.

Eight checks are declared. Four run against every subject; four are declared with their inputs fixed and do not run. None of the eight executes the subject’s code §5 §15

  • undetermined_tool_surface

    runs on every subject · decides scope and refusal, never a severity

    Can the tool surface be determined at all?

    Reads: Source files in the artifact, for the sites where MCP tools are registered.

    Concludes: A parsed set of tools with their descriptions, or an outcome that refuses the grade under its own code.

  • model_directed_text

    runs on every subject · the one check in MEASURED_CHECKS

    Does a tool description contain instructions aimed at the model?

    Reads: The literal text of each parsed description, passed to a model as delimited evidence, never as instruction.

    Concludes: Quoted spans that direct the agent to exfiltrate, to override the user or to conceal, each re-verified byte for byte. Severity high. An exfiltration finding also carries three context tags read from the package bytes: whether the destination is on the publisher's own domain, whether the README discloses the transmission, and whether an opt-out exists.

  • remote_code_fetch

    runs on every subject · decided at info; its findings are the capability inventory

    Does the package fetch and execute code it did not ship?

    Reads: Source files and manifests, for download-then-execute, dynamic evaluation, remote imports and install scripts.

    Concludes: Quoted spans of code obtained at run time rather than published, recorded as capability inventory: declared, not measured, never graded.

  • publisher_provenance

    runs on every subject · decided at info pending measured precision

    What links this artifact to a person or organisation?

    Reads: Registry metadata, attestations, maintainer records, repository ownership, commit signatures.

    Concludes: What is present and what is absent in that chain. Informational until its precision is measured.

  • undeclared_network_calls

    declared, does not run

    Which hosts does the code contact that neither the README nor the tool descriptions mention?

    Reads: Network call sites in the stored source, the README, and the tool surface.

    Concludes: Nothing yet. A report lists it as not assessed, which means exactly that.

  • dependency_risk

    declared, does not run

    Do the direct dependencies carry known advisories, unpinned versions or install-time scripts?

    Reads: The deps.dev record and the package manifest.

    Concludes: Nothing yet. A report lists it as not assessed, which means exactly that.

  • abandonment

    declared, does not run

    Is anyone still maintaining this?

    Reads: Repository activity, archive flag, contributors, release timeline.

    Concludes: Nothing yet. A report lists it as not assessed, which means exactly that.

  • fork_divergence

    declared, does not run

    Was this published from a fork, and how does the shipped code differ from upstream?

    Reads: The repository record, the published tarball, and the upstream package tarball.

    Concludes: Nothing yet. A report lists it as not assessed, which means exactly that.

The description check was admitted on 39 attack spans and 456 clean descriptions, against a published bar of 200 labelled spans and 1,000 clean descriptions. The phase-1 corpus reached that bar on 2026-09-17 and the figures pass the test on it; its labels are two independent model passes with adjudication, and no person has labelled it yet. Until a human pass confirms the labels, “measured” carries this note. §18

Measured on 2026-10-07 over 1,907 labelled descriptions (1,949 spans), under methodology 2.9.1, claude-opus-5, prompt 2026-09-28.10-b64-fewshot-counts-nothink, every proposal read once more by model:claude-opus-4-8@labeller-2026-09-18.1: 87 of 89 real descriptions found (population and research; 245 of 247 synthetic descriptions apart), recall on real descriptions 97.8%, Wilson 95 % 92.2% to 99.4%; precision 98.5% on the found descriptions (332 of 337, Wilson 95 % 96.6% to 99.4%); 0 false positives across 1408 clean descriptions (0.0%, Wilson 95 % 0.0% to 0.3%) and 0 across 93 hard negatives (0.0%, Wilson 95 % 0.0% to 4.0%); 19 description(s) not analysed and excluded from every rate. Before the independent reading, the check alone proposed a finding on 88 of 89 real descriptions (98.9%, Wilson 95 % 93.9% to 99.8%), with precision 94.6% on the descriptions it proposed on (333 of 352, Wilson 95 % 91.7% to 96.5%), 4 false positives across 1408 clean descriptions (0.3%, Wilson 95 % 0.1% to 0.7%) and 2 across 93 hard negatives (2.2%, Wilson 95 % 0.6% to 7.5%); the reading decided the rest. Labelled by two independent model passes (model:claude-opus-5@labeller-2026-09-18.1, model:claude-sonnet-5@labeller-2026-09-18.1; kappa 0.977 on 1949 records); disagreements and byte-identical inconsistencies adjudicated by a third model pass (model:claude-opus-4-8@adjudicator-2026-09-18.1, model:claude-opus-4-8@adjudicator-2026-09-18.2, model:claude-opus-4-8@adjudicator-2026-09-27.3) citing the guide, and by attestari-engineers@2026-09-18 (12 records), and by claude-adjudicator (8 records); a third labeller (model:claude-opus-4-7@labeller-2026-09-18.1) agreed with the gold set on 99 of 100 (99.0%, Wilson 95% 94.6%–99.8%) of a seeded sample (40 positive, 20 hard negative, 40 clean). No person has labelled the corpus. §18

Independence

Paid for by the people deciding whether to install. Never by the publisher being examined.

Publishers never pay.

The examined publisher pays §17

  • Nothing for a rating. Which packages we examine is set by a fixed, published rule — not by request, and not by who paid §4.
  • Nothing for a better rating. There is no paid review, no expedited review, and no arrangement under which a rated party influences its own result.
  • Nothing for removal. An accurate finding is not removed because a publisher objects to it §16.

The verdict is public so anyone can check it. Where Attestari earns revenue, the source is disclosed. Any commercial relationship with a party that is also a subject is disclosed on that subject’s published result.

Who pays instead · pricing

  • See and protect. A device app and a web app that show every agent tool installed on a device with its grade, and block at connect time.
  • Lookup API and MCP server. The verdict, the reasons, and the evidence hashes for any package version, from an API your own tools can call, or from an MCP server your agent asks before it installs anything.
  • Change monitoring. Watch the packages you already depend on for a new version and a changed verdict.
  • Pre-publish check. A linter for publishers, never a rating: its output never affects a public grade §17.

All are paid for by people deciding whether to install something. None are paid for by the publisher of what is examined.

Behind every published finding: the byte-exact excerpt, the file and line it came from, and the hash of the stored evidence it was re-verified against at decision time, with instructions for reproducing the claim from public sources without our software §7.

Day 0, day 30 §19 §16

Every finding goes to the registry and the publisher before it is published. On day 0, to the registry’s security contact and to the publisher at the contact address in their registry record or repository, with the evidence. On day 30, publication, on one clock for everyone. That period is for correcting us.

Appeals, at any time §16

A publisher may dispute any finding, before or after publication. A factual error is checked against the stored evidence and corrected. A dispute of interpretation is considered and may be published alongside the finding. Corrections are published and dated.

Re-examined, free §16

A fixed version is examined like any other. When a publisher fixes what a finding describes, the new version is re-examined free and promptly, and the updated result is published. There is nothing to negotiate, only something to ship.