netverify 1.2.0

read-only MCP verifier for ISP backbone output · commit 603fc60 · every value on this page is measured output, not a claim
ALL SCENARIOS PASS · 6/6

Automated gates — this build

unittest suite370 tests ✓ evals46/46 ✓ stdio wire gateextensions answered ✓ smoke test7 tools ✓ upstream paritybyte-identical ✓ zero-dependencystdlib only ✓ ruffclean ✓ bandit -llclean ✓ mutation21/21 caught ✓

Wrap-up demo — the operator story demo/transcript.txt · exits 0

A healthy interface is reported healthy.
[exit 0]
$ -m netverify.cli verify --command srl_interface_brief --interface ethernet-1/1 --file C:\work\hardened-mcp-evals\demo\inputs\interface-up.txt
ok interface ethernet-1/1 is admin-enabled and oper-up [pass]
A disabled interface is a fault, with the reason named.
[exit 1]
$ -m netverify.cli verify --command srl_interface_brief --interface ethernet-1/1 --file C:\work\hardened-mcp-evals\demo\inputs\interface-down.txt
FAIL interface ethernet-1/1 is admin-enabled and oper-up [fail]
interface ethernet-1/1 is not admin-enabled and up
Total ping loss is a fault, not an unreadable capture.
[exit 1]
$ -m netverify.cli verify --command ping --file C:\work\hardened-mcp-evals\demo\inputs\ping-total-loss.txt
FAIL ping reached the far end with no total loss [fail]
no successful reply was recorded; total or partial loss
Credential-shaped text is masked and the finding is named.
[exit 0]
$ -m netverify.cli verify --command ping --scan --file C:\work\hardened-mcp-evals\demo\inputs\hostile-secret.txt
ok ping reached the far end with no total loss [pass]
untrusted content found in the supplied output:
high credential - a credential assignment was present and has been masked
The installation reports on itself before answering for a network.
[exit 0]
$ -m netverify.cli self-check
netverify 1.2.0 (MCP 2026-07-28)
commands 6
secret patterns 5
injection patterns 9
max output 65536 bytes
vendored parser 71d3398 (8796 bytes)
tracing off
guards:
ok known_secret_masked
ok mutating_command_refused
ok raw_cli_string_refused
The MCP surface a host would use: tools, resources, refusal, prompt.
[exit 0]
$ scripts/smoke_test.py
SMOKE TEST OK
7 tools, all read-only with output schemas
4 resources, contract on protocol 2026-07-28
valid call verified, out-of-scope refused, injection neutralised

Stress demo — the tool under attack demo/stress_results.json · exit 0

Hostile capture: injection + exfiltration + a real secret
● PASS
verdict stays pass, all three attacks are named, the secret never appears in the verdict or the emitted text
instruction_override · critical exfiltration · critical credential · high
pasted  password: Sup3rSecret!
emitted password: ************
outcome pass  ·  secret leaked: no
measured 0.0 ms · demo/stress_demo.py
Mutating command id is refused, and the metric label stays bounded
● PASS
ScopeError with reason=not_in_allowlist; command label is None so a caller cannot mint a metric series per request
reason=not_in_allowlist metric label: None (bounded)
command 'configure terminal' is not in the allowlist. Allowed ids: ['frr_bgp_sum…
raised instead of answered: yes
measured 0.0 ms · demo/stress_demo.py
A plausible typo (`intf`) is refused instead of answered from
● PASS
ScopeError with reason=unknown_argument - a near-miss name must never silently fall back to a default
reason=unknown_argument
command 'srl_interface_brief' does not accept argument(s) ['intf']. Accepted: ['…
raised instead of answered: yes
measured 0.0 ms · demo/stress_demo.py
Token bucket drains exactly, refuses, then recovers
● PASS
4 of 5 spends allowed, the 5th refused with reason=rate_limited, and spend succeeds again after refill
✓✓✓✓✗
5th spend refused with reason=rate_limited
recovered after refill: yes
measured 1109.0 ms · demo/stress_demo.py
Oversize capture is truncated at the byte budget, secret dropped
● PASS
truncated=True, emitted text within max_bytes, and the secret at the tail does not survive the cut
13,023 B in → budget 4,096 B → 4,096 B out
truncated: yes  ·  tail secret in emitted text: no
measured 16.0 ms · demo/stress_demo.py
'No entries found' vs 'no route row at all' are different verdicts
● PASS
the device answering NO is a real fault (fail); text with no route table is an unusable capture (input_error) - conflating them either hides a fault or pages someone about a bad paste
"No entries found for prefix"
fail
the device answered NO — a real fault
no route row in the text at all
input_error
an unusable capture — not a fault
measured 0.0 ms · demo/stress_demo.py
Reproduce: python demo/wrap_up_demo.py · python demo/stress_demo.py · python demo/build_dashboard.py — all read-only, offline, zero dependencies beyond the optional PNG render.