The substrate already fails closed on malformed input. But a payload can be perfectly valid JSON and still be hostile by resource: megabytes of data, nested ten thousand deep, millions of keys, an 8 MB string, a number outside the safe-integer range. Those pass “is it well formed?” and land in your canonicalizer and hasher, which is exactly where a cheap denial of service lives.
Substrate Guard (lite) is the front door that stops them. It is a deterministic input-bounds gate that runs before any RFC 8785 JCS or SHA-256 work touches the payload. It either accepts, or rejects with a named code (REJECT_OVER_DEPTH, REJECT_TOO_MANY_KEYS, and so on). It never truncates and never repairs: no silent mangling, no ambiguous half-processing. It is strictly additive over the frozen Layer 1: it changes no hash and adds no cryptographic primitive.
Apache-2.0 open source. Install via pip install algovoi-substrate-guard or npm install @algovoi/substrate-guard. Python and TypeScript reject the same hostile inputs with the same code and compute the same profile_ref, byte for byte. This is the lite tier: a structural validator plus a content-addressed profile. It is not cryptography, and not rate limiting or replay windows. Those live in the runtime verifier layer.
How it works
Two pieces, both using the RFC 8785 JCS canonicalisation and SHA-256 already in the substrate:
profile_ref = "sha256:" + SHA-256(JCS(profile))
guard(value, profile) -> accept, or raise a named code, BEFORE canonicalization
profile_ref content-addresses the limits in force. The same discipline as Policy Binding: a record can carry the profile_ref it was admitted under, so a verifier can prove which bounds were enforced, not just that “some validation happened.” Key order does not matter (JCS sorts), so the reference is invariant to how the profile was constructed.
guard(value, profile) validates in a single pass: structural bounds first (cheap, fail fast), then the canonical-size bound last, on an already-bounded value, so the size check cannot itself blow up.
Default profile guard-receipt-v1
| Limit | Default | Reject code |
|---|
max_bytes (UTF-8 of canonical form) | 65536 | REJECT_OVER_SIZE |
max_depth (nesting, root = 1) | 32 | REJECT_OVER_DEPTH |
max_object_keys (per object) | 256 | REJECT_TOO_MANY_KEYS |
max_array_length | 1024 | REJECT_OVER_ARRAY |
max_string_length (per string and per key) | 8192 | REJECT_OVER_STRING |
max_total_nodes | 4096 | REJECT_OVER_NODES |
number_safety | on | REJECT_UNSAFE_NUMBER (outside the JSON safe-integer range, or non-finite) |
The default profile addresses to sha256:a4791b13c67a16109b85ef67fc65700ea902b6ad40dad44d8556632c3d5524a6. Pin it, or content-address your own profile.
What a verifier can check
| Verifier holds | What they can check |
|---|
A profile_ref + the profile | That the bounds in force are exactly these (profile_ref recomputes) |
A record carrying its admitting profile_ref | Which limits the record was admitted under, provably, after the fact |
| A reject code + the value + the profile | That the named bound is the one exceeded, identically in any implementation |
| The same profile, keys reordered | The profile_ref is unchanged (JCS sorts), so the address is construction-independent |
No issuer call. No registry lookup. No AlgoVoi service. RFC 8785 JCS, SHA-256, and a JSON parser are the entire dependency. Every bound is a pure structural property of the parsed value (depth, count, length), so independent implementations enforce it identically. The guard enables rejection; acting on a mismatch is a runtime decision, not a property of the construction.
Use
from algovoi_substrate_guard import guard, profile_ref, GuardError, Profile
try:
guard(value) # default profile guard-receipt-v1; accept or raise
# ... then your existing action_ref(...) / decision_ref(...) / a receipt
except GuardError as e:
print("rejected:", e.code) # e.g. REJECT_OVER_DEPTH
profile_ref() # "sha256:a4791b13…c3d5524a6"
profile_ref(Profile(max_depth=8, max_object_keys=16)) # your own pinnable profile
import { guard, profileRef, GuardError, DEFAULT_PROFILE } from '@algovoi/substrate-guard';
try {
guard(value);
} catch (e) {
if (e instanceof GuardError) console.log('rejected:', e.code);
}
profileRef(); // "sha256:a4791b13…c3d5524a6"
profileRef({ ...DEFAULT_PROFILE, max_depth: 8 }); // your own pinnable profile
The substrate_guard_v1 vector set (15 vectors) is published in the public corpus,
chopmob-cloud/algovoi-jcs-conformance-vectors,
with Python and Node runners. It covers the two profile_ref vectors, the accept controls (including the safe-integer boundary), one isolated rejection per bound (each value exceeds exactly one limit by one, and must reject with the named code), and the key-order invariance of profile_ref. Python and TypeScript reproduce every value byte for byte.
Honest scope
A deterministic structural validator plus a content-addressed profile. It is not cryptography, and it is not rate limiting or replay windows. Rate limiting and replay protection stay in the runtime verifier layer, and are never claimed as a property of the substrate. This page documents the open, lite tier of the AlgoVoi hardening layer.
Lite vs commercial
| Lite (this package) | Commercial verifier layer |
|---|
| Licence | Apache-2.0, open | Commercial OEM |
| Protection | structural input bounds, fail closed | the same, plus runtime rate limiting and replay windows |
| Scope | per-payload, pre-canonicalization | per-caller / per-window, at the edge |
| Verifier | recompute profile_ref offline | maintained verifier |
| Best for | open integrations, hardening the substrate | regulated production, enterprise terms |
The decision chain
Substrate Guard composes in one line with everything already built on the canonicalisation substrate. Call guard(value) before your existing reference construction, for example before an Agent Passport reference, a Spend Guardrail (lite) decision, or a Compliance Gate (lite) verdict. Because it changes no hash, every reference downstream gets the protection for free, with no format change.
Adopters
If you build on algovoi-substrate-guard, pin ==0.1.0, anchor a canonical profile_ref hash from substrate_guard_v1 (for example a4791b13… for guard-receipt-v1), and keep the NOTICE, you qualify for a free v0 licence key for algovoi-mandate-auditor. The gate is scripts/check_v0_adoption.py (dependency + canonical hash anchor + NOTICE + version pin, all four pass, then ISSUE_V0_KEY). Apply: email chopmob@gmail.com.
Relationship to the open substrate
Substrate Guard (lite) sits directly in front of the open JCS Canonicalisation Substrate and composes with Policy Binding, Compliance Gate (lite), Spend Guardrail (lite), and the Retention Chain. It uses the same RFC 8785 JCS and SHA-256 primitives, with no additional cryptographic dependencies.
Specification
Substrate Guard is the resource-bounds edition of input validation. It anchors to the existing input-validation section of IETF Internet-Draft draft-hopley-x402-retention-chain (§7.5, the same section the adversarial_isolation_v1 set anchors to), where the prior set covers malformed input and Substrate Guard covers well-formed but resource-hostile input. No new draft section is asserted. The normative byte-level artifact for this construction is the published substrate_guard_v1 conformance set. Additive over the frozen canonicalisation substrate, sole AlgoVoi authorship.