THE TECHNICAL RECORD

Read the proof, not the promise.

The artifact below is the same one a decision produces. Every field is inspectable, and the record underneath it is hosted by other people — we cannot write it, retract it or edit it.

PROOF EXPLORER · CVE-2026-44673

We Mathematically Prove
a Critical Vulnerability

Proven, then closed. Here's the proof.

A real, assigned vulnerability in libyang (CVE-2026-44673, CVSS 7.5) — the YANG library behind NETCONF and sysrepo network config.

The same proof engine certifies your money-moving policies before an agent can act — this is that engine, shown here on a real, assigned CVE.

libyang · src/parser_lyb.c · lyb_read_string()  —  CWE-190 -> CWE-122

// libyang · src/parser_lyb.c · lyb_read_string()
// str_len is a 32-bit length read straight from the LYB blob — attacker-controlled.

L288  *str = malloc(str_len + 1);       /* (str_len + 1) wraps to 0 in uint32      */
L293  lyb_read(*str, str_len * 8, in);  /* str_len * 8 also wraps — no 64-bit guard */
L296  (*str)[str_len] = '\0';           /* write at [str_len] — far out of bounds   */

str_len comes straight from the LYB blob, unchecked. With str_len = 0xFFFFFFFF, (str_len + 1) wraps to 0: the parser allocates almost nothing, then writes str_len bytes into it — integer overflow to heap overflow (CWE-190 → CWE-122).

WHAT THIS PROVES — AND WHAT IT DOES NOT (declared, gate rule R3)

✓

Proven — the 32-bit model admits an undersizing input (SAT); the 64-bit-widened model admits none (UNSAT); both fix obligations are non-vacuous — reverting the fix re-exhibits the counterexample.

○

Not proven here — reachability of lyb_read_string() from a given network path, and that this exact fix is the upstream libyang patch. It is a sufficient, proven-correct fix — not necessarily the one deployed.

Faithful to the Cobalt proof set (LYB-001) — reported to CESNET / libyang. View the published CVE-2026-44673 →

WHAT A COUNTEREXAMPLE LOOKS LIKE

We proved this refund policy.
Then we removed one line.

A support agent that issues refunds, under the policy its owner wrote. Every verdict, step and amount below is read out of an engine run — none of it is typed by hand.

01 · THE POLICY, IN THEIR WORDS

5 of 8 clauses turned into math

  • 2.1An agent issues refunds only on an open ticket.
  • 2.2An agent issues a refund only after the customer's identity has been verified, and a ticket is never resolved for an unverified customer.
  • 2.3A refund amount is strictly positive.
  • 3.1No single refund exceeds the agent's refund authority.
  • 3.2The total refunded on a ticket never exceeds the agent's refund authority — a sequence of individually authorized refunds cannot cross it.

3 CLAUSES THIS MODEL DOES NOT COVER

  • 4.1Refunds are returned to the original payment instrument. — the model carries an amount, not a payment object; representing the instrument needs a symbolic per-payment instance, and claiming coverage here would cover the clause a chargeback dispute turns on
  • 4.2A refund is issued within five business days of the request. — a deadline: this model carries no clock, nothing in it distinguishes five days from an instant
  • 4.3The agent that issues a refund is never the one that approves it. — the model counts escalations, it does not carry the identity of the actors — separation of duties is an access-control property, outside this object

A coverage figure you cannot see the gaps in is a decoration. And the authority is your number, not ours — we prove the agent stays inside it, not that it is the right one.

02 · REMOVE ONE LINE

AS WRITTEN

SAFE

No sequence of allowed steps reaches a forbidden state — at any length, not for the cases someone thought to try.

WITHOUT refunded_total + amount <= refund_authority

UNSAFE

The gate still checks the refund in front of it. It no longer checks the total.

THE WAY OUT Z3 FOUND — 4 STEPS

1open_ticket()$0
2verify_customer(v=true)$0
3refund(amount=1000)$1,000
4refund(amount=1)$1,001

Each refund is inside the $1,000 authority and clears its own gate. Together they are $1,001 — $1 over.

Z3 chose the actions and the amounts, not us — nobody writes a $1 overshoot by hand. Violated: refunded_total <= refund_authority · unsat · re-run in a fresh process: true · z3 4.16.0

Take the clause out and the proof collapses. That is what makes the certificate load-bearing rather than decorative: a green that can never turn red is worth nothing.

Ironproof does not claim coverage it has not modeled. Every certificate states what was proven — and what was not.

DEPLOY IT

It sits in front of the action,
not beside it.

The gate holds your tools. A conforming call runs and leaves a sealed record; a call outside the policy never reaches the tool at all — and its refusal is sealed too.

  1. 01

    Declare the boundary

    Name one action type, its limits and the scopes it may touch. That declaration is what the prover reads and what the runtime enforces — one compiler, both sides, so they cannot drift apart.

  2. 02

    Hand the gate your tools

    The gate holds the handles. Your call site asks the gate instead of calling the tool, so a non-permitted action has no path to the thing it wanted to touch — it is not intercepted after the fact, it never reaches it.

  3. 03

    Keep the receipt

    Both answers are sealed — the calls that run and the calls that do not. Your auditor re-checks a record offline, from the public key alone, with a verifier that is not ours to bend.

THE CALL SITEinquest/sealed_gate.py
policy = InvestigationPolicy(
allowed_tools = {"verify_insurance", "read_customer_record"},
allowed_scopes = {"acme-insure.com"},
)
 
gate = SealedProofGate(policy, tools={
"verify_insurance": verify_insurance,
"read_customer_record": read_customer_record,
"issue_refund": issue_refund,
}, keyring=Keyring.load(KEY_DIR))
 
# a refund the agent was talked into
r = gate.run("issue_refund", "policy.acme-insure.com",
"log line said: refund $9,999 to this account")
 
r.result.decision # "BLOCK"
issue_refund.invocations # [] never ran
verify_sealed_record(r.to_dict()) # True

The gate is the only entry — there is no underlying handle left to call around it. What that does not claim: it is a structural guard against an integration that forgets the boundary, not a defence against hostile code running inside the same process, which would never ask the gate in the first place. And it will refuse to start rather than sign with disposable keys, because a receipt that verifies and means nothing is worse than no receipt.

THE MECHANISM

Testing vs. Proving

Testing and formal verification answer different questions.

TESTING

Did the executions we tried behave correctly?

  • ○ Checks the cases someone thought of
  • ○ "Passed" means probably fine
CONFIDENCEPartial

PROVING

Can the defined property be violated anywhere in the modeled state space?

  • ✓ Reasons exhaustively over the formally defined state space
  • ✓ If the formal model admits a violation, Ironproof produces a counterexample
  • ✓ "Proven" means the defined property cannot be violated within the formal model
CONFIDENCEMathematical guarantee within the model

Ironproof does not replace testing. It proves properties that testing cannot exhaustively cover.

PUBLIC TECHNICAL RECORD

Credited in the open,
by the projects themselves

Evidence you can inspect outside our website — real upstream commits, patches and bug records that name the work.