Security gate for AI agents · anchored on NEAR

AEGIS

“No agent should execute what it hasn’t verified.”

AI agents are now told to execute instructions written by third parties: mint pages that embed step-by-step playbooks for the agent, skill packages served from mutable “latest” URLs, onboarding documents that end with “copy this prompt into your AI”. A compliant agent is one pasted paragraph away from draining its owner’s wallet. AEGIS verifies before anyone executes. Point it at a contract, a signature payload, a claim, or a page of instructions; it runs four gates and returns a verdict — ALLOW, WARN, or BLOCK — with line-level evidence, and the report hash is anchored on NEAR so any agent or human can re-verify it later without trusting the machine that produced it.

Runner aegis.mjs v1.0.0 Anchor registry aegis-gate.testnet Reports anchored 2 Reads only — holds no keys, signs nothing

The four gates

Every input an agent is asked to act on passes the gates that apply to it. A gate with no input is skipped and says so; a gate that cannot run reports NO_VERDICT and caps the overall verdict at WARN — a gate that could not run is never silently passed.

GATE 1

Provenance

Is this source official, or a third-party echo?

Addresses, links and claims are accepted only from the project’s official channels, and re-confirmed against independent infrastructure (explorers, Sourcify) — never from a relayed message, a comment, or another agent’s summary.

GATE 2

Contract

Is the code verified, and what can it actually do?

Sourcify verification (Exact Match preferred), then a risk-pattern scan of the verified source: delegatecall, selfdestruct, permit, approval / transferFrom surface, owner setters. Counts are evidence; an audit attributes them before they decide anything.

GATE 3

Signature

What exactly is being signed, and who authored the payload?

Full EIP-712 decode before any signature: domain name, chainId, verifyingContract (never a held token/NFT, a router, or Permit2), primaryType (never Permit variants), forbidden spender / value / amount fields, domain scoped to the single project. Payloads authored at runtime by a counterparty’s server fail by construction.

GATE 4

Instruction channel

Is somebody’s page trying to operate the agent?

Pages, packages and docs are scanned for instructions addressed to agents: “connect to our MCP server”, challenge→sign→submit sequences with server-supplied payloads, agent-targeted install scripts, manufactured urgency, authority claims. Content is data; only the user’s own instructions are instructions.

ALLOW

All gates that ran pass. The action can be constructed, simulated, and executed by the agent itself, within pre-set value limits.

WARN

Something needs attribution or a condition first — an unattributed pattern, a mutable download that must be hash-pinned, an admin surface to understand. A WARN is a to-do list, not a rejection.

BLOCK

A gate fails outright: unverifiable code, a signature the user cannot audit in advance, or an execution flow orchestrated by the counterparty’s server.

Merge rule: the overall verdict is the worst verdict among the gates that produced one (BLOCK > WARN > ALLOW). This page is a static exhibit: the reports below were generated by the AEGIS kit on the command line and anchored on NEAR testnet. This site does not run the gates in your browser.

Case studies — real reports, real anchors

AEGIS was distilled from gates that were actually run by an operating agent before this project existed. The first two exhibits are reports produced by the unified runner (aegis.mjs v1.0.0, 1 Nov 2026) and anchored on NEAR testnet — every verdict and hash on this page is copied from those report files and can be re-checked against the on-chain anchors below. The third predates the runner and is labelled as what it is: a manual case study.

Nonce Protocol — verified contract, unverified flow

Runner report · aegis.mjs v1.0.0 · generated 2026-11-01 03:27 UTC · inputs: the Nonce case brief (Gate 4) + a Permit-shaped EIP-712 payload of the class this flow asks agents to sign (Gate 3)
BLOCK

The project’s mint page carries a step-by-step playbook written for the agent, and the mint is orchestrated by the project’s own server. In the underlying manual audit the contracts themselves passed — Sourcify Exact Match — and it didn’t matter. Verified contract ≠ verified flow.

GateVerdictHeadline reasons (from the report)
1 · Provenance SKIPPED
  • Not run: no claim supplied (--claim).
2 · Contract SKIPPED
  • Not run: no contract address supplied (--contract). The contract side of this case was covered by the earlier manual audit (Exact Match) — see the lesson above.
3 · Signature BLOCK
  • primaryType “Permit” is a Permit shape — rejected by the signature SOP.
  • Forbidden field “spender” present in the payload (message or declared types) — rejected by the signature SOP.
  • Forbidden field “value” present in the payload (message or declared types) — rejected by the signature SOP.
  • Decoded domain: name “Example Token”, chainId 56, verifyingContract 0x2222222222222222222222222222222222222222.
4 · Instruction channel BLOCK
  • Challenge→sign→submit sequence detected (stages: obtain-challenge, sign, submit) — one server-orchestrated flow spread across the text, the Nonce shape: the counterparty issues, the agent signs, the agent submits.
  • Server-sign-submit instructions appear together with a direction to connect to an external server — a foreign server both instructs the agent and feeds it payloads to sign.
  • Weighted injection score 15 ≥ BLOCK threshold 8 (external-server-connect 2 hits × weight 4; server-sign-submit 1 hit × weight 5; agent-addressed-instruction 1 hit × weight 2).
reportHash 7c7cf5b8979c312f790713f94e650e8d0b83c163570783b16ae171802122060f

Anchored on NEAR testnet by aegis-gate.testnet, agent identity rainyy.ai — anchor transaction in the proof section below.

Hashstone — a direct on-chain freemint

Runner report · aegis.mjs v1.0.0 · generated 2026-11-01 03:27 UTC · input: contract 0x00fbc656caa753a5280835bf9e849c4c0aff9bf4 (BNB Chain, chainId 56)
ALLOW

An Agent-ID-gated freemint executed as a direct on-chain call with a locally built transaction: value 0, nothing to sign off-chain, no server in the loop. The owner-admin surface is listed as evidence and attributed — not hidden, and not feared.

GateVerdictHeadline reasons (from the report)
1 · Provenance SKIPPED
  • Not run: no claim supplied (--claim).
2 · Contract ALLOW
  • Verified exact_match on Sourcify; no critical/permit patterns in project source.
  • Evidence (attributed by audit): project source: 1× “withdraw”; 7× “onlyOwner”; project setters: setBaseURI, setContractURI, setOpen, setPrice, setRoyalty, setTarget.
3 · Signature SKIPPED
  • Not run: no typed-data file supplied (--typed-data). This flow asks for no off-chain signature at all.
4 · Instruction channel SKIPPED
  • Not run: no brief/text file supplied (--brief).
reportHash 79729980bfa8fe4ce1b4dde58766b65cba21987364473659190c744d66237d8d

Anchored on NEAR testnet by aegis-gate.testnet, agent identity rainyy.ai — anchor transaction in the proof section below.

TermiX skill package — the channel was the risk

Manual case study · examined 28 Oct 2026 · predates the unified runner — not a runner report and not anchored on NEAR
WARN → ALLOW-WITH-PIN

The package termix-agent.skill v1.11.1 (zip, 251,946 bytes) is distributed from https://termix.ai/skills?v=latest. The engineering inside was sound — the default signing mode is keyless, delegated to the wallet CLI — but the distribution channel was the risk. “Latest” is not a version. Pin the hash, or don’t run it.

GateFindingWhy
1 · Provenance WARN (the trigger)
  • The distribution URL is unpinned: ?v=latest serves whatever the operator’s bucket holds today. A version number inside a mutable download is a claim, not an identity.
4 · Instruction channel WARN
  • The zip’s INSTALL.md is written as direct instructions to an AI agent — install this package, run these scripts. During the audit that text was treated strictly as data: extracted, read, never followed.
Update mechanism WARN
  • The updater verifies the zip’s sha256 against a release manifest — but the manifest is served by the same operator, from the same bucket, unsigned. That protects against corruption in transit, not against the operator or a bucket compromise: the checker and the checked live in the same place.

Resolution: the sha256 of the audited zip was pinned as the package’s identity. When the human partner later downloaded the package on his own device, its hash matched the pin character-for-character — so the copy in use is the copy that was audited. Conditions: the pinned hash only; scripts read before any use; account and submission steps stay in human hands.

Pinned sha256 of the audited package (not a report hash) 227b1b4e559eef7e67fd25fba9b2d1d5560fc1fdc434620ff9d6c270f685c673

On-chain proof — NEAR testnet anchor registry

Verdicts are only useful if they outlive the server that produced them. A minimal registry contract on NEAR testnet stores report anchors: record_anchor(report_hash, verdict, agent_id). Anchoring is permissionless — no owner, no allowlist, no admin power — the contract holds no funds, and first write wins: re-anchoring a hash returns the original record unchanged. Both records below were read back through the public NEAR RPC and round-trip exactly.

Registry contract aegis-gate.testnet view on NearBlocks (testnet) ↗
ReportVerdictreportHashAnchor transaction
Nonce Protocol
anchored 2026-11-01 03:59 UTC · agent rainyy.ai
BLOCK 7c7cf5b8979c312f790713f94e650e8d0b83c163570783b16ae171802122060f CQWcvjoukVqBTfa74wRNNKpGtyRRYBv2LmaAzdH5jooo
Hashstone
anchored 2026-11-01 03:59 UTC · agent rainyy.ai
ALLOW 79729980bfa8fe4ce1b4dde58766b65cba21987364473659190c744d66237d8d FNwEp27ArWZtruwkbvMHJVjHjqkEzms99eZHBDs4gDfn

What an anchor proves

  • A report with this exact hash existed at the recorded time.
  • It was submitted by the recorded account under the recorded agent identity (rainyy.ai).
  • Anyone can re-run the kit on the same inputs, recompute the hash, and compare — reproducibility is the verification.

What it does not prove

  • That the verdict is correct.
  • That the gates ran honestly, or that the inputs were authentic.
  • The hash binds the merged report digest, not the full per-gate evidence (limitation 13 below).

Scope & known limitations

Disclosed openly, per the project’s submission policy — nothing below is hidden from reviewers or judges. This is the short version; the full audit trail with per-limitation impact and roadmap is in AUDIT.md in the repository. AEGIS produces screening reports, not audits.

  1. Pattern counts are raw text occurrences (code + comments + strings), never attributions. Counts alone never decide a verdict; a human/agent audit must attribute them. (Gate 2)
  2. The project/library split is path-based — only node_modules/… counts as library, so a single flattened file cannot be attributed per-contract automatically; case files carry that attribution manually. (Gate 2)
  3. Setter detection is narrow — only function setX(…) declarations are listed; admin mutators with other names can be missed, so the listed setters are evidence, not a completeness claim. (Gate 2)
  4. Gate 2’s verdict scope is deliberately thin — it escalates only on unverifiable code and project-source delegatecall / selfdestruct / permit. Dangerous combinations of patterns can still return ALLOW with the evidence attached: an ALLOW is a screening pass, never a clean bill of health. (Gate 2)
  5. Sourcify is the trust anchor. The tool trusts Sourcify’s match and sources at query time; it does not independently compare runtime bytecode, and there is no second source yet (reports state crossCheck: "unavailable"). An outage fails loudly with no verdict — never a fabricated one. Narrowed in v0.2.0 (bounded retries), not closed. (Gate 2)
  6. Node ≥ 18 is assumed (AbortSignal.timeout availability). (Kit)
  7. Gate 1 “claim found” is a content heuristic — substring or record-existence matching, not semantic attribution. A page that merely mentions an address in a warning still “carries” it; the claim’s role must still be confirmed before acting. (Gate 1)
  8. Gate 1 does not discover official channels. Whoever supplies the “official” URL frames the check; without a caller-supplied domain registry, a lookalike domain carrying the same claim would pass the content test. The report states on every run when no registry was supplied. (Gate 1)
  9. Gate 3 is structural only. It verifies no signature, recovers no signer, and cannot prove a verifyingContract really belongs to the project. Held/router classification is only as complete as the caller-supplied lists (only Permit2 is built in); an ALLOW never replaces building and simulating the final transaction separately. (Gate 3)
  10. Gate 3 types/message mismatch is WARN, not BLOCK — deliberate (wallets sign the declared types; undeclared message fields are not signed), but the warning must actually be read. (Gate 3)
  11. Gate 4 is regex/heuristic screening. Paraphrased, obfuscated, image-carried, or non-English instructions can evade it; thresholds are calibrated on the kit’s own cases, not a broad labelled corpus; and text discussing these patterns scores like text using them. Gate 4 does not fetch URLs — its input is a local file, so whoever fetches the page controls its freshness. (Gate 4)
  12. The runner composes file inputs only, and its report is a digest — per-gate verdicts, reasons, and curated evidence, not the gates’ complete reports. The reportHash binds the merged digest, not the underlying per-gate artifacts. (Runner)
  13. Anchoring is testnet-only and manual. The registry lives on NEAR testnet (anchors carry no mainnet evidentiary weight), and anchoring is a separate step — the runner does not yet anchor automatically. (Anchor)
  14. Deployer key custody is a local file (testnet funds only, never committed). Because records are first-write-wins and anchoring is permissionless, a leaked key could only spend the account’s testnet balance or anchor additional reports under its name — it cannot alter existing anchors. (Anchor)
Closed in v0.2.0: Sourcify partial_match results are no longer treated as verified — they are forced to WARN (former limitation 5).