Aevrin

Concepts

OWASP MCP Top 10, scoring, incomplete scans, target types, and quota.

OWASP MCP Top 10

Every finding, from every tool, is mapped onto one of ten categories — so a Semgrep result and a Gitleaks result read the same way, and the same categories appear identically in the CLI, the hook, and the dashboard.

CodeCategory
MCP01Token Mismanagement & Secret Exposure
MCP02Tool Poisoning (Hidden Instructions)
MCP03Cross-Origin Escalation / Tool Shadowing
MCP04Rug Pull (Tool Drift After Install)
MCP05Command Injection, Path Traversal, SSRF, File Access
MCP06Missing/Weak Authentication
MCP07Supply Chain / Malicious or Typosquatted Dependencies
MCP08Prompt Injection via Live Tool Responses
MCP09Excessive Agency / Overprivileged Scope
MCP10Weak/Missing Audit Logging

MCP08 is never tested by a static scan. It requires dynamic, adversarial testing against a server's actual runtime tool responses — out of scope for this version. Every report includes an explicit note saying so, rather than silently omitting it: a documented gap, not a clean result.

Scoring

Every scan starts at 100 and loses points per finding, by severity:

SeverityDeduction per findingCap on total deduction for this tier
Critical−40none
High−20−30
Medium−8−16
Low−3−8
Info00

The cap matters: without it, a large monorepo with many low-severity findings (routine lint-tier issues, multiplied across sub-packages) would floor to 0 — "critical risk, do not use" — for the same actual risk profile a single-package repo would score as "moderate." Critical stays uncapped on purpose: a scan with several genuinely critical findings (multiple live, verified secrets, for example) should still be able to floor the score. That's a different failure mode than volume alone.

ScoreVerdict
90–100Clean — no significant issues found
70–89Minor issues — review recommended
40–69Significant risk — do not deploy as-is
0–39Critical risk — do not use this server

Incomplete scans

A scan category (static analysis, secrets, dependencies) is marked unreliable when every tool in it fails to execute — not when tools run and find nothing, but when nothing ran at all. Common causes: Docker isn't running, a scanner binary is missing, or the network is unreachable. The CLI pins its scanner images and runs them sequentially with a 2 GB per-tool ceiling; Docker Desktop should have at least 4 GB available. On Windows it must be using Linux containers and permit bind mounts from the system temporary directory.

Runtime MCP inspection accepts only public HTTPS endpoints. Submitted stdio commands are never executed by Aevrin, and private/loopback/link-local/metadata targets are rejected. When no safe remote endpoint exists, tool-description coverage is shown as skipped rather than failed or silently treated as clean.

If any category is unreliable, the whole scan is flagged incomplete and that status follows it everywhere:

  • The CLI prints a red warning above the score and exits with code 3, regardless of --fail-on.
  • The hook blocks the install with a block_incomplete decision instead of silently allowing — an empty findings list from a category that never ran is not evidence of safety.
  • The dashboard shows a destructive-styled banner naming the specific categories that couldn't run, and the verdict reads "not a reliable result" instead of a score-based verdict.

This is a deliberate, security-first design choice: a scanner that can be made to say "clean" just by breaking its own tools isn't trustworthy. An incomplete result is always treated as inconclusive, never as a pass.

Self-reported results

The CLI runs every scanner on your own machine and reports the result to Aevrin's backend — Aevrin doesn't re-run the scan server-side (that's not feasible for a local filesystem path, and would be redundant for a public repo). This has one real security implication worth being explicit about: the score is independently verified, the findings list is not.

  • Score: recomputed server-side from the findings you submit, using the exact same formula the CLI used. A hand-crafted upload can't claim a better score than its own findings justify — the client-submitted score is logged but never trusted for storage.
  • Findings: not independently re-verified. A valid API key can, in principle, submit fabricated or incomplete findings. This is an inherent limitation of any tool that runs locally and reports centrally — the same shape as, for example, a CI badge driven by a self-reported test result. It's also why every scan on the dashboard is labeled "self-reported by the scanning client, not independently re-verified by Aevrin" rather than presented as an unconditional guarantee.

Every scan result carries this framing consistently: a real signal worth trusting for its stated scope, not a cryptographic attestation.

Target types

TypeWhat it meansWhat runs
GitHub repoA github.com URLFull pipeline: clone, static analysis, secrets, dependencies, tool description check
Local pathA directory on your machine (CLI only)Same as above, minus cloning
Live server URLA running MCP server's URLTool description check only — nothing to clone or scan statically
Pasted configA {"mcpServers": {...}} JSON blobSame as a live server URL, per declared server

For repo and local-path targets, Aevrin also checks whether the target actually depends on an MCP SDK (@modelcontextprotocol/sdk, fastmcp, the mcp PyPI package, etc.) before treating findings as an MCP-specific risk assessment. If it doesn't, the report says so explicitly — the findings are still real code-security findings, just not MCP-specific ones.

Quota and tiers

Quota resets on a rolling monthly window from your signup date, not the calendar month, and is tracked independently across three buckets — CLI, hook, and dashboard scans each have their own limit:

TierCLI scans/moHook scans/moDashboard scans/moHistory retentionPDF export
Free5257 daysNo
Hobby50205090 daysYes
TeamUnlimitedUnlimitedUnlimitedUnlimitedYes

Exceeding a bucket's limit returns a two-part message everywhere it can happen (CLI, hook, dashboard): what happened, when it resets, and where to upgrade — never a bare refusal.