▸ methodology
THE METHOD BEHIND EVERY FIGURE ON THIS SITE

How we rate an attack surface.

No numbers from thin air. This is the exact method behind every figure on this site: how we define an attack surface, how the engine finds one, how we prove it is reachable, and where we deliberately under-claim.

ASTSTATIC ANALYSIS, NOT GUESSWORK
TAINTREACHABILITY, PROVED NOT ASSERTED
OWASPRISK RATING · NIST · ISO
0PROVEN-LIVE OVERCLAIMS
▸ 01 · definition

What we mean by an attack surface

An attack surface is not a vague weakness. It is a specific point in code where an AI agent can trigger a dangerous capability: a place the agent can actually do something to the outside world.

Every surface is classified against a fixed capability vocabulary, so the same thing is always counted the same way. We group them into three families:

Code & system

code_exec · subprocess_exec
deserialize · ssti
file_delete · file_perms

Money & data out

payment · blockchain_tx
secret_exfil · cloud_write
external_write

Agent actions

tool_invoke · email_send · dm
post · telegram_send
browser_click · computer_use · queue_mutation

In scope

Command and code execution, deserialisation RCE, agent tool invocation, outbound sends, data mutation, payments and crypto, network egress and SSRF, computer and browser use, and dynamic config or module loading.

COUNTED

Out of scope

Pure reads, object constructors, logging, and deprecated no-ops. If it cannot change the world or leak from it, it is not counted. This is written down as a rubric, not decided case by case.

NOT COUNTED

A worked example

A bundled tool exposes subprocess.run(cmd) where cmd is built from text the agent read off a web page. That single call is one surface, capability subprocess_exec.

ONE CALL · ONE SURFACE
▸ 02 · discovery

How the engine finds them

Deterministic static analysis on the code's own syntax tree. Not an LLM guessing, and not a keyword grep. The engine builds one whole-repo call graph and reads real call and write nodes, so imports, comments and strings are excluded by construction.

Structural families

Instead of a brittle allowlist of method names, the engine matches a module family plus a verb family, so every sibling in a dangerous library fires. New names cannot slip through a gap in a list.

Danger-library roots

Sometimes the danger is the library, not the verb. stripe→payment, web3→blockchain_tx, boto3→cloud_write, langchain→tool_invoke.

Focused detectors

High-precision passes for the sharpest cases: eval/exec run on the model's own output (prompts becoming shells), secret exfiltration, and server-side template injection.

It compounds

When the optional advisory tier confirms a genuinely novel sink, its distinctive name is absorbed as a permanent static rule. The engine gets stricter over time without inflating false positives.

0 Inherited attack surfaces mapped ACROSS 12 AUDITED FRAMEWORKS · 361K+ COMBINED GITHUB STARS
▸ motion_pending · higgsfield
▸ it_compounds

The engine gets stricter over time — without inflating false positives.

▸ 03 · reachability

Why only 474 of the 520 count as live

Finding a dangerous sink is not enough. The second stage asks a harder question: can untrusted input actually reach it, and is anything standing in the way? This is where most tools stop and we do not.

Entrypoint-grounded taint

We seed untrusted data only at real entry points: HTTP route handlers, webhooks, genuine bot commands. Not at any parameter that merely looks suspicious. Then we follow that data through the call graph to see if it reaches a sink. A suggestive variable name proves nothing here; a traced path does.

The guard quadrant

Each reachable sink is scored on two axes: is it reachable from untrusted input, and is a real guard on the path. Only reachable and unguarded earns the worst verdict. Two rules keep us honest: severity is downgrade-only, so a missing guard never lowers a score, and anything unresolved is treated as the worst case, never given the benefit of the doubt.

520

Install-liability

The dangerous capability is present but not reached from this project's own entry points. Inert where it sits, and live the moment a downloader wires untrusted input to it. This is the honest headline for an installable framework.

INERT HERE · LIVE ON INSTALL
474

Reachable in repo

Untrusted input already reaches the sink, unguarded, by static taint. This is a real path in real code, not a proof-of-concept and not a claim that an exploit runs today.

STATIC TAINT · UNGUARDED

What neither number is

Neither figure claims a working exploit. Reachability is a property of the code, established statically. A claim that something is exploitable in the wild is a much higher bar, and we hold it separately.

NOT AN EXPLOIT CLAIM
▸ motion_pending · higgsfield
▸ traced_not_asserted

A suggestive variable name proves nothing here. A traced path does.

▸ 04 · rating

How a surface is qualified and rated

Every surface is placed in a tier, so a big raw count can never be passed off as a big threat count. The tiers are strict and they narrow hard.

TOTAL

Every action surface found. The full inventory.

▸▸

VULNERABLE

Surfaces in the high-consequence classes: remote-code and action capabilities.

▸▸▸

REACHABLE & UNGUARDED

The 474-style live threat: untrusted input reaches it and no guard stands in the way.

INSTALL-LIABILITY

The 520-style number: inert here, live on install.

PROVEN-LIVE

The only tier we will ever put a severity badge on. It needs a human trace and a harmless proof-of-concept. Across all 12 frameworks that tier is currently empty, and we say so.

CURRENTLY EMPTY · SAID OUT LOUD

CANDIDATE

High-risk but not yet traced. Counted for context, never badged.

The severity itself is not a vibe. It uses the OWASP Risk Rating Methodology (severity times likelihood), cross-referenced to NIST SP 800-30 and ISO/IEC 27005, and anchored to OWASP LLM06, Excessive Agency. Surfaces are de-duplicated by symbol and capability and carry a stable identity, so the same finding is not counted twice and can be tracked across scans.

▸ 05 · the fix

From finding to fix

▸ RUNTIME ENFORCEMENT
the action gate
▸⚙

A concrete plan, enforced at runtime

For every unguarded surface, Hermes Shield generates a concrete fix plan: the exact control required (for example a kill-switch guard, a final action gate and an approved-hash check), the file to change, and a test to prove the surface is closed. The action gate then enforces it at runtime.

To be plain about scope: today the repairer plans the fix and the runtime gate enforces it. It is not a black-box that silently rewrites your code. A human stays in the loop on the change, which is exactly where we think a security tool should sit.

HUMAN IN THE LOOP
▸ 06 · where we under-claim

The method is only worth trusting if we tell you its edges.

0

0 proven-live

Across all 12 frameworks we claim zero proven-live exploits, because none has cleared the human-trace-plus-proof bar. A security company that overclaims is part of the problem.

Reachable is not exploitable

474 is static reachability in real code. It is a strong signal and a floor, not a demonstration that an attack succeeds on your systems.

We contain, not prevent

We do not stop prompt injection. We stop the action it tries to cause. Our anchor is OWASP LLM06, Excessive Agency, the last line of defence.

Python-first, and honest about it

The guard and reachability model is strongest in Python. Other languages are reported as sinks only, and never dressed up as fully rated.

Corrections only remove

When we corrected our own counts, every change could only remove a false positive, never add a finding. The number went down, on purpose.

Residual risk is documented

Security is probabilistic, not absolute. We write down what we observed and what we did not, rather than implying invulnerability.

▸ questions

Straight answers

The questions a security lead asks before trusting a number.

What counts as an attack surface?
A specific point in code where an AI agent can trigger a dangerous capability: running code, deleting files, sending money, calling an API, invoking a tool or sending a message. Each is classified against a fixed capability list, with a written in-scope and out-of-scope rubric. Pure reads, logging and constructors are excluded.
How does the engine find them? Is it an LLM guessing?
No. It is deterministic static analysis on the code’s own syntax tree, over one whole-repo call graph. Sinks are read from real call and write nodes, so imports, comments and strings are excluded by construction. An optional advisory tier only confirms genuinely novel sinks.
What is the difference between the 520 and 474 numbers?
520 is install-liability: a dangerous capability is present but not reached from the project’s own entry points, so it is inert where it sits and live the moment untrusted input is wired to it. 474 is reachable-in-repo: untrusted input already reaches the sink, unguarded, by static taint. Neither number claims a working exploit.
Does “reachable” mean “exploitable”?
No. Reachability is a static property of the code, established by analysis. A claim that something is exploitable in the wild is a much higher bar that we hold separately. Across all 12 frameworks we have audited, 0 findings are proven-live, and we say so.
Does Hermes Shield stop prompt injection?
No, it contains the consequence. We do not prevent the model from being fooled; we block the dangerous action it tries to cause, before it executes. Our anchor is OWASP LLM06, Excessive Agency, the action-side last line of defence.
Is the scan safe to run on my code?
Yes. It is read-only static analysis that runs where your agents run. It never writes to the code it scans, never reads secrets, and your code never leaves your machine.
▸ apply_the_method

Now you know exactly how the numbers are made.

Every figure on this site traces back to this method — defined, discovered, traced, tiered, and under-claimed on purpose. See it run against your own agent.

Methodology — How Hermes Shield Rates an Attack Surface