HushSpec/Docs
HUSHED · MMXXVI
Documentation v1.0
v1.0.0referencestable

CLI Reference#

For executable conformance testing, see the separate hushspec-testkit external reference. Its execution packet, supported platforms and exit semantics are separate from h2h below.

h2h is the reference command-line tool for HushSpec documents. It validates, resolves, lints, formats, diffs, evaluates, signs and scaffolds policies, and most reporting subcommands support machine-readable output through --format json. The exceptions are init, keygen, sign, panic and completions, which take no --format, and hash, whose --format selects digest or canonical. resolve selects YAML or JSON; schema --format affects the listing only, while a schema body is always JSON. The option appendix below is captured from the release binary, including every nested subcommand.

Terminal
h2h --help            # subcommand list
h2h <command> --help  # flags for one command
h2h version           # CLI, build and spec versions

Installation#

Install the CLI through the installation guide. It includes the rootless installer, package managers, prebuilt binaries and Windows instructions. The installed command is h2h.

Exit codes#

CodeMeaning
0Success: the document is valid, the check passed, the action was allowed.
1Policy-level failure: invalid document, lint findings, failing test cases, a denied action, a signature that did not verify, a refused rewrite, or a --fail-on class that was found.
2Input or usage failure: file not found, unreadable input, a document fmt could not parse, an unknown flag or value (clap's own usage errors also exit 2).
4eval / explain only: the action's decision was warn.

These are common conventions, not a universal error taxonomy. init, keygen, sign and panic commands also use exit 1 for some I/O or setup failures; panic status uses 1 for an active or unreadable sentinel. Consult the command-specific contracts below and retain the diagnostic. eval and explain describe decisions; they do not run the requested tool.

Reading from stdin#

validate, lint, fmt and hash accept - in place of a path and read the document from stdin. Diagnostics name it <stdin>.

eval and explain accept a full action from stdin through --action-file -; their policy argument remains a file or builtin reference. Do not assume every path-taking command supports -.

Terminal
cat policy.yaml | h2h validate -
kubectl get cm policy -o jsonpath='{.data.policy\.yaml}' | h2h lint -
h2h fmt - < policy.yaml > formatted.yaml

Two restrictions follow from stdin having no file to write back to:

  • h2h lint - --fix is refused (exit 2). Use --dry-run to see the diff, or write the document to a file first.
  • h2h fmt - writes the formatted document to stdout and prints no status lines, so it can be used as a filter. (With --format json stdout carries the JSON summary instead, not the document.)

For validate --strict, a document read from stdin has no path to resolve relative extends against, so relative references resolve from the working directory. builtin: references work unchanged.


h2h validate#

Validate documents against the HushSpec schema: YAML parse, structural validation, and (with --strict) extends resolution.

Terminal
h2h validate rulesets/default.yaml
h2h validate --strict --format json policy.yaml
cat policy.yaml | h2h validate -
FlagDescription
<FILES>...One or more policy files; - reads stdin.
-f, --format <text|json>Output format (default text).
--strictAlso check that extends references resolve.

Exit: 0 valid · 1 invalid · 2 a file was missing.

Error codes in the output: E000 I/O, E001 YAML parse, E002 unsupported version, E003 duplicate pattern name, E004 other validation error, E005 invalid regex, E010 extends resolution failure, E011 a metadata date that is not an ISO 8601 calendar date (YYYY-MM-DD).

h2h resolve#

Print a policy with its extends chain fully resolved and merged: the exact document an enforcement engine would evaluate. extends is consumed by the merge, so the output has no extends key.

Terminal
h2h resolve policy.yaml                 # canonical YAML on stdout
h2h resolve policy.yaml --format json   # same document as JSON
h2h resolve builtin:strict              # builtins work too
h2h resolve --strict policy.yaml        # warnings become failures
FlagDescription
<POLICY>Policy file, or a builtin reference (builtin:default).
-f, --format <yaml|json>Output format (default yaml).
--strictFail when the resolved document produces validation warnings, such as unknown extension keys (an unknown posture capability or budget key).

The resolved document is validated before it is printed; a document that only becomes invalid after merging (a duplicate pattern name contributed by two layers, say) fails rather than being emitted. Warnings go to stderr, so h2h resolve policy.yaml > resolved.yaml stays clean.

Exit: 0 success · 1 parse, resolve or validation failure (including strict warnings) · 2 the policy file was not found.

h2h hash#

Print a policy's content hash, the portable identity defined by the canonical form specification (spec/hushspec-canonical.md). Two parties holding the same policy get the same digest in every SDK, regardless of which optional keys the author omitted or which language wrote the file.

Terminal
h2h hash policy.yaml                      # sha256:<64 hex>
h2h hash policy.yaml --format canonical   # the RFC 8785 JSON the digest covers
h2h hash builtin:default                  # builtins work too
h2h resolve policy.yaml | h2h hash -      # read an already-resolved document
FlagDescription
<POLICY>Policy file, a builtin reference (builtin:default), or - for stdin.
-f, --format <digest|canonical>Print the sha256: digest (default) or the canonical JSON text it is computed over.
--strictFail when the document produces validation warnings.

The hash covers the resolved document, so a policy that declares extends is resolved through the same chain h2h resolve walks before it is hashed. Changing a base therefore changes the identity of every policy that extends it, even when the child file is untouched. That is the point: the enforced policy changed. merge_strategy is a resolution field and never appears in the canonical form.

The document is validated first: an invalid document has no canonical form, because a digest for something no engine would accept is worse than no digest at all.

Exit: 0 success · 1 parse, resolve, validation or canonicalization failure · 2 the policy file was not found.

h2h lint#

Static analysis for policies: empty rule blocks, overlapping patterns, shadowed exceptions, over-broad allow lists, regex complexity, disabled rules, duplicates, control mappings, credential coverage, permissive defaults and unreachable extension configuration.

Terminal
h2h lint policy.yaml
h2h lint --format json --fail-on-warnings policy.yaml
h2h lint --format sarif --out lint.sarif policy.yaml
h2h lint policy.yaml --fix
h2h lint policy.yaml --dry-run
FlagDescription
<FILES>...One or more policy files; - reads stdin.
-f, --format <text|json|sarif>Output format (default text).
--out <PATH>Write the report to a file instead of stdout (json and sarif only).
--fail-on-warningsExit 1 on warnings, not just errors.
--fixApply decision-neutral auto-fixes in place. Refused for -.
--dry-runShow what --fix would change without writing.

Exit: 0 clean · 1 findings (or a parse error) · 2 a file was missing or unreadable, a write failed, the report could not be serialized, --out was combined with --format text, or --fix was pointed at stdin.

--fix rewrites the file through the canonical formatter, which does not preserve comments. Run it on documents whose comments you can afford to lose, or review the --dry-run diff first.

Source positions#

Every finding is located at the key or list entry it is about, not at the file. Text output prints file:line:column followed by the document path:

text
warning[L008]: rules.egress.allow[1]: duplicate pattern "api.example.com"
  --> rulesets/example.yaml:8:9
   | rules.egress.allow[1]

Positions come from a second pass over the same bytes with a real YAML event parser (saphyr-parser), which keeps quoted keys, block scalars, flow sequences and comments between entries correctly aligned; a line scanner does not. Lint reports the resolved document, so a finding about a block a policy inherits names the base that declares it:

text
warning[L004]: rules.egress.allow[0] contains wildcard pattern "*" ...
  --> builtin:permissive:9:9
   | rules.egress.allow[0]

JSON findings carry code, severity, message, location, fixable, the document path, and a span object (file, line, column, end_line, end_column; 1-based, with end_column pointing at the character after the region). Each file also reports the fixed codes that --fix actually applied. A finding whose key could not be located (an inherited default no document writes, or a base fetched over HTTP) omits span and still carries location.

SARIF#

--format sarif emits a SARIF 2.1.0 document: one run, a tool.driver for h2h carrying the full rule catalog below (each with shortDescription, fullDescription and a defaultConfiguration.level), and one result per finding with ruleId, level, message, a physicalLocation region, a logicalLocations entry naming the document path, and, for fixable findings, a fixes entry describing the deletion --fix would perform. Severities map error → error, warning → warning, info → note.

Terminal
h2h lint --format sarif --out lint.sarif rulesets/*.yaml

Upload the file with github/codeql-action/upload-sarif and findings appear as code-scanning annotations on the pull request that introduced them. This repo's own Policy Lint job does exactly that.

Lint rules#

Severity is a function of provability. error means the document contains configuration that can never take effect under the spec's own rules; warning means a construct defeats something else the same document declares; info means the construct is coherent but easy to arrive at by accident. Two codes (L016, L018) report at two severities for exactly that reason; see their rows.

CodeLevelRuleWhy it fires
E000errorfile-unreadableThe path does not exist, or the file is not readable UTF-8. Nothing was linted.
E001errorparse-errorYAML parsing or deserialization failed. HushSpec rejects unknown keys, so a typo in a field name lands here rather than being silently ignored.
E010errorunresolvable-extendsA base could not be loaded, the chain is circular or too deep, or a pinned digest did not match. Lint reports the resolved document, so an unresolvable chain leaves nothing to lint.
L001warningempty-rule-blockThe block is enabled but declares nothing to allow or deny, so it makes no decision. A block that looks like enforcement and is not is worse than an absent one.
L002warningoverlapping-patternsSampled synthetic targets matched two patterns in the same list. Overlap is not itself a defect, but a redundant pair is dead weight.
L003warningshadowed-exceptionA forbidden_paths exception re-permits a path that nothing denies, so it has no effect.
L004warningoverly-broad-allowallow: ["*"] permits every target, which makes the rest of the list decorative.
L006warningregex-complexityNested quantifiers are a ReDoS risk; very long or heavily alternated patterns are hard to review.
L007infodisabled-ruleenabled: false makes a block inert, which permits what it would otherwise govern. Reported for all twelve rule blocks, so a disabled control is never invisible.
L008warningduplicate-patternA byte-identical repeat of an earlier entry contributes nothing. The one finding whose removal is provably decision-neutral, so --fix always applies it.
L009infomissing-secret-patternsWithout secret detection, a file_write carrying a credential is indistinguishable from any other write.
L010warningunreachable-allowBlock takes precedence over allow, so an entry in both can never decide anything.
L011warningunmapped-rule-blockOnce a policy maps controls, an unmapped block is enforcement with no stated reason. Policies that map nothing are silent.
L012errorbroken-control-mappingA mapping claims a control is implemented through a rule path that resolves to nothing: a false compliance claim.
L013warningunregistered-controlThe framework is not in spec/registries/frameworks.yaml, or the control id does not match that framework's pattern. The registry is advisory, so this is an unverifiable claim, not an invalid document.
L014warning / infocredential-paths-uncovered.env, .ssh, .aws, .gnupg, .kube and id_rsa are where agent credentials actually live, and the message names the ones a denylist does not reach. A policy running a path_allowlist is silent (everything outside it is already denied). A policy with neither block reports info: a capability-scoped document meant to be composed onto a base legitimately says nothing about the filesystem.
L015warningunder-graded-credential-patternSeverity drives what an engine does with a match, so a pattern that recognizes an AWS key id (AKIA/ASIA), a GitHub token (gh[opsur]_, github_pat_), a PEM private key header or an OpenAI sk- key and grades it below critical has downgraded a credential leak to a note.
L016warning / infooverbroad-forbidden-patternForbidden patterns are unanchored, so .*, .+, a bare single character, or anything matching the empty string matches every command or diff. Beside other patterns that is a defect: they become dead. The lint reports warning. As the only entry in its list it is a coherent deny-all (the sole way this block can express one) and reports info.
L017warningpermissive-defaultegress.default: allow permits every host outside block, making the allow list decorative; tool_access.default: allow with empty block and require_confirmation permits every tool. Supersedes L005, which reported the same shape as info and only when the allow list was non-empty; L005 is retired and will not be reused.
L018warning / infoempty-capability-allowlistenabled: false makes a block inert, which permits the capability, so enabled: true with an empty allowlist is the spec's only way to deny one outright, reported info (this is what rulesets/panic.yaml does deliberately). Promoted to warning where the document contradicts itself (computer_use.allowed_actions permits input.inject while input_injection.allowed_types is empty) or where the block does nothing at all (computer_use in observe mode with nothing allowed: observe never denies).
L019errorunreachable-extensionA posture state that is neither initial nor the target of any transition is never entered; a transition naming an undefined state never fires; an origin profile with no match object is never a candidate (origins spec §3) and one repeating an earlier profile's match always loses the document-order tie; a literal overlay allow entry the base allowlist does not match can never allow anything (origins spec §4.1, overlay allowlists intersect).
L021warningungranted-capabilityA when.capability naming a capability no posture state grants can never be true while the policy has a posture extension, so the block is permanently inert. Without a posture extension the predicate is unevaluable and the block stays active (core spec 3.13), so nothing is reported.
L022warningempty-list-entryAn empty string in tool_access.allow, block, or require_confirmation, or in an origins overlay list, can never match a tool or host (core spec 3.3, 3.7) and is usually an editing mistake.
L020infoinert-conditionThe engine reads start == end as an always-open 24-hour window and an empty days as every day, so a window written that way reads like a restriction and is not one. Listing all seven days is likewise the default. An all_of/any_of with no members is always true. (There is no "never true" window to report: core spec 3.13 keeps a block active when a window cannot be evaluated.)

h2h fmt#

Format policies canonically: fixed rule-block order, sorted and de-duplicated lists, consistent quoting.

Terminal
h2h fmt policy.yaml
h2h fmt --check policy.yaml        # CI gate, does not write
h2h fmt --diff policy.yaml         # unified diff of what would change
h2h fmt - < policy.yaml            # filter mode
h2h fmt --strip-comments policy.yaml
FlagDescription
<FILES>...One or more policy files; - reads stdin and writes stdout.
--checkReport without writing; exit 1 if anything would be reformatted.
--diffPrint a unified diff instead of writing.
--strip-commentsReformat even though comments will be discarded.
-f, --format <text|json>Output format (default text).

Comments#

Canonical formatting is a structural re-render, so every comment except a leading # yaml-language-server: modeline would be lost. Audited policies carry control-mapping comments (# --- 45 CFR 164.312(a)(1): Access Control ---), so fmt refuses to rewrite a document that has any other comment:

text
error refusing to reformat hipaa-base.yaml: 25 comment line(s) would be
discarded (first at line 2); pass --strip-comments to reformat anyway
  • A refused rewrite exits 1 and leaves the file untouched.
  • --check and --diff report has comments; would not reformat and treat it as a skip, not a failure: a commented policy does not fail a fmt --check CI gate.
  • --strip-comments discards the comments and reformats anyway.
  • The modeline alone never triggers the refusal; it is preserved verbatim.

JSON output carries has_comments, comment_count, first_comment_line and skipped alongside changed and diff.

Exit: 0 formatted or already formatted · 1 --check would reformat, or a rewrite was refused to protect comments · 2 file missing, unreadable, unparseable, or a write failed.

h2h diff#

Compare two policies by effective decision: probes are generated from both documents' rules, evaluated against each, and only decision changes are shown.

Terminal
h2h diff old.yaml new.yaml
h2h diff old.yaml new.yaml --format json
h2h diff main.yaml pr.yaml --fail-on relaxed   # CI guard
FlagDescription
<OLD> <NEW>Base and updated policy files.
-f, --format <text|json>Output format (default text).
--sentinel <PATH>Panic sentinel to consult before evaluating.
--fail-on <relaxed|tightened|any>Exit 1 when a change of that class is present.

Each change is classified from the old and new decisions:

change_typeTransitionClass
relaxeddeny/warn → allowrelaxing
demoteddeny → warnrelaxing
tightenedallow → warn/denytightening
escalatedwarn → denytightening
unchangedsame decisionneither

--fail-on relaxed therefore catches anything that can turn a deny into an allow or a warn, --fail-on tightened catches the reverse, and --fail-on any catches both. Without the flag diff is purely informational and exits 0.

Exit: 0 no matching change · 1 a matching change was found · 2 a policy could not be loaded.

h2h eval / h2h explain#

Evaluate a single action against a policy. explain is eval with the rule-by-rule trace forced on.

Terminal
h2h eval policy.yaml --type egress --target api.example.com
h2h explain builtin:default --type file_read --target /etc/passwd
h2h eval policy.yaml --action-json '{"type":"tool_call","target":"bash"}'
h2h eval policy.yaml --action-file - --format receipt   # action from stdin
FlagDescription
<POLICY>Policy file or builtin reference.
--type <TYPE>file_read, file_write, patch_apply, shell_command, tool_call, egress, computer_use, input_inject, browser_action, code_exec, custom. Any other type is denied fail-closed.
--target <TARGET>Path, domain, tool name, command or channel.
--content <STRING> / --content-file <PATH>Action content.
--args-size <N>Serialized tool-argument size in bytes.
--origin <KEY=VALUE>Origin context field (repeatable).
--url <URL>browser_action: navigation destination.
--networkcode_exec: the call requests network access.
--timeout-ms <MS>code_exec: requested execution time in milliseconds.
--context <JSON|@PATH>Runtime context for when conditions: inline JSON object or @PATH to a YAML/JSON file.
--posture <STATE> / --signal <SIGNAL>Posture state and transition signal.
--action-json <JSON> / --action-file <PATH>Full action document; - reads stdin. Conflicts with every flag above: the document carries the whole action.
--sentinel <PATH>Panic sentinel to consult before evaluating.
--explainRender the trace (implied by h2h explain).
-f, --format <text|json|receipt>Output format (default text); receipt emits a full decision receipt.

Exit: 0 allow · 1 deny · 4 warn · 2 the policy or action could not be loaded.

h2h test#

Run evaluation test suites (*.test.yaml fixtures).

Terminal
h2h test --fixtures fixtures/core/evaluation
h2h test policy.test.yaml --format tap
h2h test --policy policy.yaml --fixtures ./tests
h2h test --fixtures fixtures/library --fail-on-uncovered \
  --format junit --report-file target/library-suites.xml
FlagDescription
<TESTS>...Fixture files or directories.
--fixtures <PATH>Directory (or file) of fixtures to collect.
-p, --policy <PATH>Policy that overrides the one embedded in fixtures.
--sentinel <PATH>Panic sentinel to consult before evaluating.
-f, --format <text|tap|json|junit>Output format (default text).
--report-file <PATH>Write the report in --format here; stdout then carries the readable summary.
--fail-on-uncoveredExit non-zero when a declared rule path was never hit.

Fixtures are validated against hushspec-evaluator-test.v0 before any case runs, and both fixture-format versions are accepted: 0.1.0, and 0.2.0 with its per-case controls / tags and its expect.rule_trace / expect.receipt assertions. A case that declares either assertion is checked against the receipt the document produces under the fixed inputs of fixtures/receipts/expected/README.md.

Rule coverage. Every run compares the rule paths each policy under test declares -- every rule block of the resolved document, plus every named secret pattern -- with the paths a passing case hit, through matched_rule and through each rule_trace entry's rule_path. A failing case credits nothing: coverage says a control was exercised, and a case that failed showed the opposite. A path inside a block credits the block, so rules.egress.allow covers rules.egress and rules.secret_patterns.patterns.ssn covers both the block and that pattern. The table prints after the run; --fail-on-uncovered turns a gap into a non-zero exit, which is how the library suites are gated in CI.

JUnit. --format junit emits one <testsuite> per fixture file and one <testcase> per case, carrying each case's controls and tags as <property name="control"> / <property name="tag"> and each failure as a <failure> with the expected and actual values. A final rule coverage suite reports the declared/covered counts per policy, and fails there too under --fail-on-uncovered.

JSON. --format json prints an object: passed, failed, fixtures[] (per file, with each case's controls and tags), and coverage with the per-policy declared, covered and uncovered paths.

Exit: 0 all cases passed · 1 a case failed, or a declared rule path was never hit under --fail-on-uncovered · 2 an argument named neither a file nor a directory, no fixture files were found, a fixture did not match the schema, or the policy could not be read.

h2h audit#

Show a policy's governance metadata (author, approver, classification, lifecycle, change ticket, effective and expiry dates), run the governance checks, and optionally print the control → rule-path matrix.

Terminal
h2h audit policy.yaml
h2h audit policy.yaml --format json
h2h audit policy.yaml --controls        # control -> rule-path matrix + coverage
h2h audit policy.yaml --strict          # every finding is fatal
FlagDescription
<FILE>Policy file to audit.
-f, --format <text|json>Output format (default text).
--controlsAlso report the control → rule-path matrix and rule-block coverage.
--strictExit non-zero when a check fails, a governance finding is reported, or a control rule path does not resolve (L012).

Every governance check that fires is listed with its code, severity and the document path it concerns: GOV_SOD_VIOLATION (author is also the approver), GOV_UNAPPROVED_STATE, GOV_REVIEW_OVERDUE, GOV_CHANGELOG_ORDER, GOV_EXPIRED, GOV_LIFECYCLE, GOV_MISSING_APPROVAL_DATE, GOV_RESTRICTED_NO_APPROVER (core spec 2.5). Warnings are advisory: without --strict they are reported and the command still exits 0. The one error-severity finding, GOV_SELF_SUPERSEDES, fails the audit either way, because h2h validate rejects that document too.

Exit: 0 report produced · 1 the document did not parse, an error-severity finding fired, or --strict saw a failing check or finding · 2 file not found or unreadable.

h2h init#

Scaffold a .hushspec/ directory with a starter policy and test suite.

Terminal
h2h init --preset strict
h2h init --dir ./service-a --preset default
FlagDescription
--preset <permissive|default|strict>Starter policy (default default).
--dir <PATH>Where to create .hushspec/ (default .).

Exit: 0 created · 1 the directory already exists or could not be written.

h2h schema#

Print a published HushSpec JSON Schema. The schemas are embedded in the binary, so this works offline and from an installed release.

Terminal
h2h schema core > hushspec-core.v1.schema.json
h2h schema hushspec-receipt.v1.schema.json     # full file name also accepted
h2h schema core.v0                              # the frozen 0.x lineage
h2h schema --list
h2h schema --list --format json
FlagDescription
[NAME]A short name for the current .v1. lineage (core, posture, origins, detection, evaluator-test, hash-vector, receipt, log-entry, signature, keyring, bundle, report, error-codes, merge-vector), the same name with a .v0 suffix for the frozen 0.x file (core.v0), or the published file name. Required unless --list.
--listList the available schemas instead of printing one.
-f, --format <text|json>Format for --list (the schema body is always JSON).

Output is byte-identical to the corresponding file in schemas/.

Exit: 0 printed · 2 unknown schema name, or neither a name nor --list.

h2h sign / h2h verify / h2h keygen#

Ed25519 detached signatures, envelope format 0.2 (spec/hushspec-signing.md).

Terminal
h2h keygen --output-dir ~/.hushspec
h2h sign policy.yaml --key ~/.hushspec/h2h.key.pem --expires-in 90d --signer security@example.com
h2h verify policy.yaml --keyring ~/.hushspec/keyring.json --last-seen-version 4
CommandFlags
keygen--output-dir <DIR> (default .), --name <NAME> (default h2h), --convert <OLD_KEY>, --force; writes <NAME>.key.pem (PKCS#8, mode 0600) and <NAME>.pub.pem (SPKI), and prints the key_id.
sign<POLICY>, -k, --key <PATH>, --expires-in <DURATION> (30d, 12h, 90m, 3600s), --policy-version <N>, --signer <IDENTITY>, -o, --out <PATH> (default <POLICY>.sig), --allow-unapproved.
verify<POLICY>, -s, --sig <PATH>, -k, --key <PATH> or --keyring <PATH>, --now <TIMESTAMP>, --max-skew <SECONDS> (default 300), --last-seen-version <N>, -f, --format <text|json>.

What is signed#

The envelope covers the content hash of the resolved policy, not the file's bytes, the same digest h2h hash prints. So reformatting a signed policy keeps its signature valid, and a change to a base policy reached through extends invalidates every signature over the policies that extend it, because the enforced policy changed. sign resolves and validates the chain first and refuses to sign when it cannot.

Keys and trust#

Keys are standard PEM: PKCS#8 private, SubjectPublicKeyInfo public, exactly what openssl genpkey -algorithm ed25519 and openssl pkey -pubout produce. A key is named by sha256: plus the digest of its SPKI DER, and verify recomputes that id from the public key rather than trusting a keyring's claim.

--keyring takes a keyring document listing the trusted keys, each of which may carry not_after (retire a key without invalidating older signatures) or revoked: true (reject everything it signed). --key is the one-key shorthand.

Reason codes#

A failed verify prints the reason code of the first check that failed, the same string a receipt's policy.signature.reason carries:

malformed_envelope, unsupported_format_version, unsupported_algorithm, unknown_key_id, key_revoked, key_retired, signed_at_in_future, expired, signature_mismatch, content_hash_mismatch, policy_version_rollback.

Migrating from 0.1#

HushSpec 0.1 signed raw file bytes with bespoke 32-byte key files. Convert the key with h2h keygen --convert old.key and re-sign: a 0.1 signature attests something 0.2 does not claim, so verify reports unsupported_format_version and says to re-sign rather than failing obscurely.

sign refuses a policy whose metadata.lifecycle_state is not approved or deployed, and a policy that does not parse: a signature is a durable attestation that this exact document was approved, so signing a draft would attest something that never happened. --allow-unapproved overrides the gate for development.

Exit: 0 signed / signature valid · 1 a signing or verification failure (invalid key or keyring, any reason code) · 2 usage (missing policy or signature file, nothing to trust, unparseable --now or --expires-in).

h2h panic#

File-sentinel kill switch. While the sentinel exists, evaluating subcommands (eval, explain, test, diff) deny every action.

Terminal
h2h panic activate --sentinel /tmp/hushspec.panic
h2h panic status --sentinel /tmp/hushspec.panic
h2h panic deactivate --sentinel /tmp/hushspec.panic
SubcommandDescription
activateCreate the sentinel file.
deactivateRemove it.
statusReport whether panic mode is active.

All three take --sentinel <PATH> (default .hushspec_panic in the working directory).

Exit: activate/deactivate return 0 on success and 1 on an I/O failure; status returns 1 when panic mode is active and 0 when it is not, so a script can gate on it directly.

h2h completions#

Generate a shell completion script on stdout.

Terminal
h2h completions bash > /etc/bash_completion.d/h2h
h2h completions zsh  > "${fpath[1]}/_h2h"
h2h completions fish > ~/.config/fish/completions/h2h.fish
h2h completions powershell | Out-String | Invoke-Expression

Supported shells: bash, zsh, fish, powershell, elvish.

Exit: 0 generated · 2 unknown shell.

h2h version#

Print the CLI version alongside build and spec provenance.

Terminal
h2h version
h2h version --format json
h2h --version          # clap's short form, CLI version only
FieldMeaning
versionh2h crate version.
git_shaShort git SHA of the build, or unknown when built without git metadata (a published source tarball, for example).
spec_versionHushSpec version this build writes.
supported_spec_versionsEvery document version this build accepts.
targetRust target triple the binary was built for.

Exit: always 0.

Evidence chain#

Verify-on-load flags (eval, explain, resolve)#

FlagMeaning
--require-signatureEvery non-builtin document in the extends chain must carry a detached signature that verifies, or a matching #sha256: pin. A policy that does not verify is refused: eval emits a deny receipt with matched_rule: __hushspec_policy_unverified__ and policy.signature.verified: false, and exits 1.
--keyring <PATH> / --key <PATH>Trusted keys (keyring JSON, or one SPKI PEM). With --keyring and no --require-signature, verification runs opportunistically and the outcome is recorded in the receipt.
--now, --max-skew, --last-seen-versionVerifier clock, allowed signer skew, rollback floor (signing spec 6).

Digest pins: extends: "builtin:default#sha256:<hex>" is checked always; the pinned value is the base's own content hash, which h2h hash <policy> --own prints. A mismatch is reported as digest_mismatch (exit 2 on eval).

Receipt fields on eval#

FlagMeaning
--agent-id, --session-id, --principalThe actor recorded in the receipt (runtime is always h2h/<version>).
--monitorRecord the disposition in monitor mode (would_block instead of blocked).
--log <PATH>Append a policy_loaded entry and the receipt to a hash-linked log (log spec).
--log-key <PATH>Sign every log entry with this Ed25519 private key.

h2h log verify <files...>#

Checks a hash-linked log: sequence continuity, prev_hash links, entry_hash recomputation, payload/entry-type agreement, receipt schema validity, and entry signatures. Pass rotated files oldest first; each continued file must start with a log_started entry that names the previous file's last hash.

FlagMeaning
--keyring / --keyVerify entry signatures.
--require-signaturesEvery entry must be signed and verify.
--now, --max-skewVerifier clock and skew.
--format jsonMachine-readable report.

Exit 0 when the chain verifies, 1 at the first break (reported as file:line: reason), 2 for unusable inputs.

h2h receipts verify <files...>#

Validates receipts from .jsonl logs, JSON receipt files, or signed receipts ({receipt, signature}). With --policy, every receipt must name that policy's canonical content hash, and receipts whose action can be replayed (no content, not browser_action/code_exec) have their decision re-derived under the posture state the receipt records, and a recorded origin or context the engine cannot read back fails the check rather than being replayed without it. With --keyring/--key, signatures are verified; --require-signatures makes an unsigned receipt a failure.

Exit 0 when every receipt passes, 1 otherwise, 2 for unusable inputs.

h2h hash --own#

Prints the document's own content hash with extends and merge_strategy stripped and no resolution: the value a digest pin names and a receipt records for a chain link.

h2h report <files...>#

Aggregates recorded policy decisions, enforcement outcomes and mapped rule activity over a window. Ordinary mode is exploratory; a rule/control mapping is not an assessment conclusion. Use an explicit evidence profile for authenticated, stream-scoped reporting.

Terminal
h2h report audit.jsonl                                     # the whole log, as tables
h2h report audit.jsonl --since 2026-09-01T00:00:00Z --until 2026-09-30T23:59:59Z
h2h report audit.jsonl --policy library/healthcare/hipaa-base.yaml --by control
h2h report audit.jsonl --format json > report.json         # validates against the report schema
h2h report audit.jsonl --format csv --out ./evidence/      # one CSV per table

Input is a hash-linked log (policy_loaded / policy_swapped events plus receipt entries), a plain receipt JSONL, or signed receipts ({receipt, signature}) -- classified line by line. A file holding any log entry is a log and is chain-verified as a whole, so a plain receipt among log entries is reported as a mixed file and breaks the chain rather than slipping past verification. A line that is neither is refused with its file and line number (exit 2); --lenient skips it instead and records the count as totals.skipped_lines. A receipt whose timestamp is not RFC 3339 counts as malformed: a record that will not place itself in time cannot be placed in a window.

When the input is a log, its chain is verified before anything is counted (the same checks as h2h log verify, the receipt schema pass and the entry signatures included, each file on its own -- checking the link between rotated files is h2h log verify's job, and it takes them oldest first). A chain that does not verify refuses to report (exit 1) unless --unverified is passed, and the report is then stamped chain_verified: false.

FlagMeaning
--since / --until <TIMESTAMP>RFC 3339 bounds on receipts and policy events. Both inclusive. The window narrows what is counted, never what is verified.
--policy <PATH>Join metadata.controls (core spec 2.5.1) against the receipts. Without it, a policy named by a log's extends_chain is resolved when it still resolves from here.
--format text|json|csv|oscalDefault text.
--by control|rule|decision|policyReport on one table only; also picks the table --format csv writes to stdout.
--out <PATH>A directory for --format csv (one CSV per table), a file for every other format.
--lenientSkip unparsable lines instead of refusing.
--unverifiedReport on a log whose chain did not verify.
--keyring <PATH>Trusted keyring JSON for entry signatures. Without one, signed entries are counted but not verified.
--key <PATH>A single trusted public key (PEM), as a one-key keyring. Mutually exclusive with --keyring.
--require-signaturesLegacy mode: log entries only, not standalone receipt envelopes. Every log entry must carry a verifying signature; an unsigned entry or missing keyring breaks the chain. Strict mode always authenticates every declared record.
--max-skew <SECONDS>Allowed signer clock skew while verifying entry signatures (default 300).
--now <TIMESTAMP>Stamp generated_at with this instead of the wall clock (reproducible reports). It is also the verifier's clock for entry signatures, so a report pinned to an instant verifies them as of the moment it describes.
--top-paths <N>How many rule_paths each rule-block row lists (default 5).
--experimental-oscalRequired by --format oscal.
--evidence-profile <PATH>Experimental strict profile. Requires JSON or contextual OSCAL, exactly one key/keyring, --out and --verification-out.
--verification-out <PATH>New verification sidecar file; published last as the completion marker.
--assessment-context <PATH>OSCAL only: local manifest binding AP, SSP and resolved catalog.
--native-report-out <PATH>OSCAL only: new accompanying native JSON report.
--max-evidence-file-bytes <N>Strict-only file cap: default 16 MiB, maximum 1 GiB.
--max-evidence-total-bytes <N>Strict-only shared input budget: default 64 MiB, maximum 1 GiB.
--max-evidence-line-bytes <N>Strict-only JSONL line cap: default 1 MiB, maximum 16 MiB. Require 0 < line <= file <= total.

What it aggregates#

  • Totals by decision (allow/warn/deny), by enforcement mode (enforce/monitor), and by disposition (allowed/confirmed/blocked/would_block).
  • Per rule block: evaluated and skipped trace entries, fired (an evaluated entry whose outcome was not allow, so exactly warn + deny), the deny and warn split, and the most frequent rule_paths.
  • Per action type, per policy content_hash with first and last seen, and the policy_loaded / policy_swapped timeline.
  • Per actor (agent_id / session_id / principal).
  • Signature status as each receipt recorded it at load time, with failures grouped by reason.
  • Detections by detector_id, with the level histogram and how many findings met a threshold.
  • Control evidence, with --policy: for each framework and control, the rule_paths it maps to, the rule blocks those were observed under, and the receipts / evaluations / fired / denied counts with a last-seen timestamp -- plus unmapped_fired_rule_blocks, the blocks that fired with no control behind them (lint L011's static gap, observed dynamically).

A mapping that names a rule block (rules.egress) is evidenced by everything that block recorded. A deeper mapping (rules.egress.block) is only evidenced by an evaluation whose recorded rule_path is at or under it, so a control is never credited with an evaluation that matched the allowlist instead. Only receipts naming the policy's own content hash count toward its controls; the report says how many did (receipts_matching_policy).

Formats#

--format json emits one document validated by schemas/hushspec-report.v1.schema.json (h2h schema report). --format csv with --out <dir> writes totals.csv, rule_blocks.csv, action_types.csv, policies.csv, policy_timeline.csv, actors.csv, signatures.csv, detections.csv, and -- with --policy -- controls.csv and unmapped_rule_blocks.csv; without --out it writes the single table --by names to stdout.

--format oscal --experimental-oscal requires the strict profile, trusted keys, --assessment-context, --native-report-out, --out, and --verification-out. The pinned OSCAL 1.1.2 exporter emits receipt-derived EXAMINE observations, not findings, risks or objective satisfaction. It validates both the local AP/SSP/catalog context and generated result. Older experimental commands without context now fail; --unverified is prohibited.

Strict mode authenticates captured source bytes before window filtering, verifies ordered rotations per stream, checks policy transitions and optionally matches independently obtained inventory. Completeness remains not-established without that inventory. Supplied optional policy signatures must still verify. The sidecar binds native report, profile, trust-input and source byte digests; an unsigned sidecar is not itself an attestation. Native signatures retains runtime-reported policy-signature semantics. Multi-policy controls are separate interval summaries in the sidecar.

Strict outputs must be distinct new files in one existing operator-controlled directory; no input aliases or overwrites. Files must match the profile's flattened source order. Strict mode rejects --lenient, --unverified, --by, stdout, negative clock skew and mismatched explicit windows. --policy may only select an already-declared resolved artifact. See Evidence Verification for runnable examples, bounds, trust assumptions and publication/crash limits.

Exit 0 when the report was produced, 1 for a broken chain without --unverified, 2 for unusable inputs or flags.

Strict mode also uses exit 1 for digest, signature, signer-role, duplicate receipt, policy-binding and required-boundary failures; context/output/limit errors use exit 2. Refused strict inputs publish no success packet.

Vectors: fixtures/report/ -- a synthetic 24-hour log and the exact report it must produce.

h2h bundle#

Policy bundle attestation (bundle spec). A bundle is a DSSE envelope whose payload is an in-toto Statement v1: the subject is the canonical form of the resolved policy, and the predicate carries that document, every extends hop with its own hash and signature status, and the resolver that produced them. It is signed with the same Ed25519 keys as policies and receipts, so h2h keygen output works unchanged.

h2h bundle create <policy>#

Resolves the policy (builtin references included), validates the merged document, builds the statement, and signs it.

FlagMeaning
--key <PATH>PEM PKCS#8 Ed25519 private key that signs the bundle. Without it the bundle is unsigned: it still carries the evidence, attests nothing, and h2h bundle verify rejects it -- so create warns.
--keyring <PATH>Trusted keys used to verify the policy's own signature while loading it. The outcome is recorded in predicate.signature_verification and per chain link. Omitted keyring means no verification was attempted, and those members are absent rather than false.
--require-signatureRefuse to bundle unless every non-builtin hop carries a verifying signature or a matching #sha256: pin.
--max-skew <SECONDS>Allowed signer clock skew while verifying on load (default 300).
--created-at <TIMESTAMP>Pin predicate.created_at instead of reading the clock. With it, the same policy, key and resolver produce a byte-identical bundle: JCS payload plus deterministic Ed25519.
--subject-name <NAME>Override the subject label (defaults to the policy's name, then the leaf file name).
--out <PATH>Output path (defaults to <policy file name>.bundle.json in the working directory).
--format jsonMachine-readable summary.

Filesystem chain sources are recorded relative to the working directory when they lie beneath it, so a bundle built in CI carries no runner workspace path.

Exit 0 on success, 1 when the policy will not resolve, will not validate, or is refused by --require-signature, 2 for unusable inputs.

h2h bundle verify <bundle.json>#

Runs the four ordered checks of bundle spec 5.2 and stops at the first failure.

FlagMeaning
--keyring <PATH> / --key <PATH>Trusted keys (keyring JSON, or one SPKI PEM taken as a one-key keyring). One of the two is required.
--policy <PATH>Re-resolve this policy and assert the bundle attests it (check 4): the canonical forms must match and every chain hop's hash must match, in order. Chain source labels are not compared -- the same policy resolved on another host is the same policy.
--now <TIMESTAMP>Verifier clock. A bundle carries no expiry, so this only stamps verified_at in the report.
--format jsonMachine-readable report carrying valid and, on failure, reason and detail.
Reason codeCheck
malformed_bundle1: not a well-formed envelope, statement, or predicate; an unknown predicateType or bundle_version lands here.
unknown_key_id2: no signature names a key in the keyring.
key_revoked2: the only keys that signed are revoked in the keyring.
key_retired2: the only keys that signed were retired before predicate.created_at.
dsse_signature_mismatch2: a usable key was found but no signature verifies over the PAE. An unsigned bundle reports this.
subject_digest_mismatch3: predicate.resolved does not hash to the declared subject.
policy_mismatch4: --policy resolves to something else, or does not resolve at all.

Exit 0 on valid, 1 with the reason code otherwise, 2 for unusable inputs.

h2h bundle inspect <bundle.json>#

Prints the predicate summary -- subject, content hash, policy identity, resolver, created_at, every chain hop with its signature status, and the signing key ids -- without verifying anything. --format json prints the whole decoded statement.

Exit 0, or 1 when the bundle cannot be decoded.

Common workflows#

Validating and linting before commit#

Run h2h validate --strict policy.yaml and h2h lint --fail-on-warnings policy.yaml. Use h2h fmt --check policy.yaml to check formatting without changing files. See CI integration for action, hook and container setup.

Running conformance tests#

Use h2h test --fixtures tests/ for your policy's expected decisions. It tests the reference evaluator, not an arbitrary external engine. Engine claims use the separate conformance testkit.

Policy diffing for PR review#

Use h2h diff old.yaml new.yaml --fail-on relaxed to flag a relaxation, and retain both resolved policy hashes with the review. Re-run policy fixtures; a diff is not exhaustive proof over every possible runtime input.

Emergency panic mode#

Use h2h panic status --sentinel /operator/chosen/path to inspect your configured sentinel. Activating creates the file; deactivating removes it. Only processes configured to consult that path will see it, and an already latched SDK needs an explicit application reset. See safe recovery.

Complete v1 option reference#

Captured from h2h 1.0.0. These blocks list every public command and option. Exit notes are checked against the release implementation, not inferred from help.

h2h audit options#

0: report produced; 1: unreadable/invalid policy or strict governance failure. Usage errors: 2.

text
Display governance metadata and run advisory checks

Usage: h2h audit [OPTIONS] <FILE>

Arguments:
  <FILE>  Policy YAML file to audit

Options:
  -f, --format <FORMAT>  Output format [default: text] [possible values: text, json]
      --controls         Report the control -> rule-path matrix and rule-block coverage
      --strict           Exit non-zero when a governance check fails, a governance finding is reported, or a control rule path does not resolve (lint L012)
  -h, --help             Print help
  -V, --version          Print version

h2h bundle options#

Select create, verify or inspect; their exit contracts follow below. Missing/invalid subcommand: 2.

text
Create, verify, and inspect signed policy bundles (DSSE / in-toto)

Usage: h2h bundle <COMMAND>

Commands:
  create   Resolve a policy and attest it as a signed DSSE / in-toto bundle
  verify   Check a bundle's signatures, subject digest, and (optionally) the policy it claims to be about
  inspect  Print a bundle's predicate summary without verifying it
  help     Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

h2h bundle create options#

0: bundle created; 1: policy resolution/validation/trust refusal; 2: unusable input or output.

text
Resolve a policy and attest it as a signed DSSE / in-toto bundle

Usage: h2h bundle create [OPTIONS] <POLICY>

Arguments:
  <POLICY>  Policy file to bundle, or a builtin reference (e.g. "builtin:default")

Options:
  -k, --key <PATH>              PEM PKCS#8 Ed25519 private key that signs the bundle (see `h2h keygen`)
      --keyring <PATH>          Trusted keyring JSON used to verify the policy's own signature on load
      --require-signature       Refuse to bundle unless every non-builtin document in the extends chain carries a verifying signature or a matching #sha256: pin
      --max-skew <SECONDS>      Allowed signer clock skew in seconds when verifying on load [default: 300]
  -o, --out <PATH>              Output path for the bundle (defaults to <POLICY>.bundle.json)
      --created-at <TIMESTAMP>  Pin predicate.created_at instead of using the clock, for reproducible bundles (RFC 3339, e.g. 2026-09-15T12:00:00.000Z)
      --subject-name <NAME>     Override the subject name (defaults to the policy's name)
  -f, --format <FORMAT>         Output format [default: text] [possible values: text, json]
  -h, --help                    Print help
  -V, --version                 Print version

h2h bundle verify options#

0: valid; 1: verification refused with a reason code on stderr; 2: unusable input or trust configuration.

text
Check a bundle's signatures, subject digest, and (optionally) the policy it claims to be about

Usage: h2h bundle verify [OPTIONS] <BUNDLE>

Arguments:
  <BUNDLE>  Bundle file to verify

Options:
  -k, --key <PATH>       PEM SPKI public key, accepted as a one-key keyring
      --keyring <PATH>   Trusted keyring JSON (hushspec-keyring.v1.schema.json)
      --policy <PATH>    Re-resolve this policy and assert the bundle attests it (check 4)
      --now <TIMESTAMP>  Verifier clock, RFC 3339 (defaults to now). A bundle has no expiry; this only stamps the report
  -f, --format <FORMAT>  Output format [default: text] [possible values: text, json]
  -h, --help             Print help
  -V, --version          Print version

h2h bundle inspect options#

0: decoded; 1: bundle cannot be decoded. Inspection does not verify trust. Usage errors: 2.

text
Print a bundle's predicate summary without verifying it

Usage: h2h bundle inspect [OPTIONS] <BUNDLE>

Arguments:
  <BUNDLE>  Bundle file to read

Options:
  -f, --format <FORMAT>  Output format [default: text] [possible values: text, json]
  -h, --help             Print help
  -V, --version          Print version

h2h validate options#

0: valid; 1: invalid; 2: input could not be read or invalid usage.

text
Validate policy files against the HushSpec schema

Usage: h2h validate [OPTIONS] <FILES>...

Arguments:
  <FILES>...  Policy YAML files to validate; "-" reads the document from stdin

Options:
  -f, --format <FORMAT>  Output format [default: text] [possible values: text, json]
      --strict           Also check that extends references resolve
  -h, --help             Print help
  -V, --version          Print version

h2h resolve options#

0: resolved; 1: resolution/validation/trust failure or strict warning; 2: missing input or invalid usage.

text
Print a policy with its extends chain fully resolved and merged

Usage: h2h resolve [OPTIONS] <POLICY>

Arguments:
  <POLICY>  Policy YAML file, or a builtin reference (e.g. "builtin:default")

Options:
  -f, --format <FORMAT>        Output format [default: yaml] [possible values: yaml, json]
      --strict                 Treat validation warnings (unknown extension keys, unreachable posture states, ...) on the resolved document as failures
      --require-signature      Require every non-builtin document in the extends chain to carry a verifying detached signature or a matching #sha256: pin
      --keyring <PATH>         Trusted keyring JSON used to verify policy signatures on load
      --key <PATH>             A single trusted public key (PEM) used to verify policy signatures on load
      --now <TIMESTAMP>        Verifier clock as an RFC 3339 timestamp (defaults to now)
      --max-skew <SECONDS>     Allowed signer clock skew in seconds [default: 300]
      --last-seen-version <N>  The last policy_version accepted for this policy (rollback protection)
  -h, --help                   Print help
  -V, --version                Print version

h2h test options#

0: all cases passed; 1: failed cases or no runnable cases; 2: input/configuration failure.

text
Run evaluation test suites against policies

Usage: h2h test [OPTIONS] [TESTS]...

Arguments:
  [TESTS]...  Test fixture files

Options:
  -p, --policy <POLICY>      Policy file to test against (overrides policy embedded in fixtures)
      --fixtures <FIXTURES>  Directory of test fixture files
      --sentinel <PATH>      Panic sentinel file to consult before evaluating; if it exists the process denies all actions (default: .hushspec_panic)
  -f, --format <FORMAT>      Output format [default: text] [possible values: text, tap, json, junit]
      --report-file <PATH>   Write the report in `--format` here instead of to stdout; stdout then carries the human-readable summary
      --fail-on-uncovered    Exit non-zero when a declared rule path was never hit by any case
  -h, --help                 Print help
  -V, --version              Print version

h2h eval options#

0: allow; 1: deny or policy refusal; 4: warn; 2: input/usage failure. No tool is executed.

text
Evaluate a single action against a policy

Usage: h2h eval [OPTIONS] <POLICY>

Arguments:
  <POLICY>  Policy YAML file, or a builtin reference (e.g. "builtin:default")

Options:
      --type <TYPE>            Action type (file_read, file_write, patch_apply, shell_command, tool_call, egress, computer_use, input_inject, browser_action, code_exec, custom)
      --target <TARGET>        Action target (path, domain, tool name, command, channel)
      --content <STRING>       Action content (file body, patch text)
      --content-file <PATH>    Read action content from a file
      --args-size <N>          Serialized tool-argument size in bytes
      --url <URL>              browser_action: navigation destination URL
      --network                code_exec: the call requests network access
      --timeout-ms <MS>        code_exec: requested execution time in milliseconds
      --context <JSON|@PATH>   Runtime context for `when` conditions: an inline JSON object, or @PATH to read a YAML/JSON file
      --origin <KEY=VALUE>     Origin context field as KEY=VALUE (repeatable). Keys: provider, tenant_id, space_id, space_type, visibility, external_participants, tags, sensitivity, actor_role
      --posture <STATE>        Current posture state (defaults to the policy's posture "initial" state)
      --signal <SIGNAL>        Posture transition signal
      --action-json <JSON>     Full action as an inline JSON object
      --action-file <PATH>     Full action as a YAML or JSON file; "-" reads stdin
      --sentinel <PATH>        Panic sentinel file to consult before evaluating; if it exists the process denies all actions (default: .hushspec_panic)
      --explain                Render the rule-by-rule trace (text output only)
      --require-signature      Require every non-builtin document in the extends chain to carry a verifying detached signature or a matching #sha256: pin
      --keyring <PATH>         Trusted keyring JSON used to verify policy signatures on load
      --key <PATH>             A single trusted public key (PEM) used to verify policy signatures on load
      --now <TIMESTAMP>        Verifier clock as an RFC 3339 timestamp (defaults to now)
      --max-skew <SECONDS>     Allowed signer clock skew in seconds [default: 300]
      --last-seen-version <N>  The last policy_version accepted for this policy (rollback protection)
      --monitor                Record the decision in monitor mode (a warn or deny is recorded as would_block instead of blocked)
      --agent-id <ID>          Actor recorded in the receipt: the agent
      --session-id <ID>        Actor recorded in the receipt: the session, run, or job
      --principal <ID>         Actor recorded in the receipt: the principal the agent acts for
      --log <PATH>             Append the receipt to a hash-linked log (log spec), recording a policy_loaded event first
      --log-key <PATH>         Sign log entries with this Ed25519 private key (PEM)
  -f, --format <FORMAT>        Output format [default: text] [possible values: text, json, receipt]
  -h, --help                   Print help
  -V, --version                Print version

h2h explain options#

0: allow; 1: deny or policy refusal; 4: warn; 2: input/usage failure. No tool is executed.

text
Explain a single-action decision with a rule-by-rule trace

Usage: h2h explain [OPTIONS] <POLICY>

Arguments:
  <POLICY>  Policy YAML file, or a builtin reference (e.g. "builtin:default")

Options:
      --type <TYPE>            Action type (file_read, file_write, patch_apply, shell_command, tool_call, egress, computer_use, input_inject, browser_action, code_exec, custom)
      --target <TARGET>        Action target (path, domain, tool name, command, channel)
      --content <STRING>       Action content (file body, patch text)
      --content-file <PATH>    Read action content from a file
      --args-size <N>          Serialized tool-argument size in bytes
      --url <URL>              browser_action: navigation destination URL
      --network                code_exec: the call requests network access
      --timeout-ms <MS>        code_exec: requested execution time in milliseconds
      --context <JSON|@PATH>   Runtime context for `when` conditions: an inline JSON object, or @PATH to read a YAML/JSON file
      --origin <KEY=VALUE>     Origin context field as KEY=VALUE (repeatable). Keys: provider, tenant_id, space_id, space_type, visibility, external_participants, tags, sensitivity, actor_role
      --posture <STATE>        Current posture state (defaults to the policy's posture "initial" state)
      --signal <SIGNAL>        Posture transition signal
      --action-json <JSON>     Full action as an inline JSON object
      --action-file <PATH>     Full action as a YAML or JSON file; "-" reads stdin
      --sentinel <PATH>        Panic sentinel file to consult before evaluating; if it exists the process denies all actions (default: .hushspec_panic)
      --explain                Render the rule-by-rule trace (text output only)
      --require-signature      Require every non-builtin document in the extends chain to carry a verifying detached signature or a matching #sha256: pin
      --keyring <PATH>         Trusted keyring JSON used to verify policy signatures on load
      --key <PATH>             A single trusted public key (PEM) used to verify policy signatures on load
      --now <TIMESTAMP>        Verifier clock as an RFC 3339 timestamp (defaults to now)
      --max-skew <SECONDS>     Allowed signer clock skew in seconds [default: 300]
      --last-seen-version <N>  The last policy_version accepted for this policy (rollback protection)
      --monitor                Record the decision in monitor mode (a warn or deny is recorded as would_block instead of blocked)
      --agent-id <ID>          Actor recorded in the receipt: the agent
      --session-id <ID>        Actor recorded in the receipt: the session, run, or job
      --principal <ID>         Actor recorded in the receipt: the principal the agent acts for
      --log <PATH>             Append the receipt to a hash-linked log (log spec), recording a policy_loaded event first
      --log-key <PATH>         Sign log entries with this Ed25519 private key (PEM)
  -f, --format <FORMAT>        Output format [default: text] [possible values: text, json, receipt]
  -h, --help                   Print help
  -V, --version                Print version

h2h init options#

0: scaffold created; 1: refusal or filesystem failure; 2: invalid usage.

text
Scaffold a new policy project

Usage: h2h init [OPTIONS]

Options:
      --preset <PRESET>  Security preset [default: default] [possible values: permissive, default, strict]
      --dir <DIR>        Directory to create .hushspec/ in [default: .]
  -h, --help             Print help
  -V, --version          Print version

h2h lint options#

0: no blocking findings; 1: errors or warnings with --fail-on-warnings; 2: input/usage failure.

text
Run static analysis checks on policy files

Usage: h2h lint [OPTIONS] <FILES>...

Arguments:
  <FILES>...
          Policy YAML files to lint; "-" reads the document from stdin

Options:
  -f, --format <FORMAT>
          Output format

          Possible values:
          - text
          - json
          - sarif: SARIF 2.1.0, the format GitHub code scanning ingests

          [default: text]

      --fail-on-warnings
          Exit 1 if any warnings are reported (not just errors)

      --fix
          Apply decision-neutral auto-fixes in place

      --dry-run
          Show what --fix would change without modifying files

      --out <PATH>
          Write the report to this file instead of stdout (json and sarif only)

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

h2h diff options#

0: no selected --fail-on class; 1: selected change class found; 2: input/usage failure.

text
Compare two policies and show effective decision changes

Usage: h2h diff [OPTIONS] <OLD> <NEW>

Arguments:
  <OLD>
          Base policy file (before change)

  <NEW>
          Updated policy file (after change)

Options:
      --sentinel <PATH>
          Panic sentinel file to consult before evaluating; if it exists the process denies all actions (default: .hushspec_panic)

  -f, --format <FORMAT>
          Output format

          [default: text]
          [possible values: text, json]

      --fail-on <CLASS>
          Exit 1 when the diff contains a change of this class: "relaxed" (a deny becomes allow/warn, or a warn becomes allow), "tightened" (the reverse), or "any" (either)

          Possible values:
          - relaxed:   Any change that can turn a deny into allow/warn or a warn into allow
          - tightened: Any change that can turn an allow into warn/deny or a warn into deny
          - any:       Either direction

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

h2h fmt options#

0: formatted or already canonical; 1: --check needs changes or rewrite refused; 2: read/parse/write/usage failure.

text
Format policy files canonically

Usage: h2h fmt [OPTIONS] <FILES>...

Arguments:
  <FILES>...  Policy YAML files to format; "-" reads stdin and writes to stdout

Options:
      --check            Check formatting without modifying files (exit 1 if changes needed)
      --diff             Show what would change without modifying files
      --strip-comments   Reformat even though it discards comments. Without this flag `fmt` refuses to rewrite a document that carries comments beyond a leading yaml-language-server modeline
  -f, --format <FORMAT>  Output format [default: text] [possible values: text, json]
  -h, --help             Print help
  -V, --version          Print version

h2h hash options#

0: digest/canonical output; 1: parse/resolve/validation/canonicalization failure; 2: input/usage failure.

text
Print the content hash of a policy's canonical form

Usage: h2h hash [OPTIONS] <POLICY>

Arguments:
  <POLICY>
          Policy YAML file, a builtin reference (e.g. "builtin:default"), or "-" to read the document from stdin

Options:
  -f, --format <FORMAT>
          Output format

          Possible values:
          - digest:    The `sha256:<hex>` content hash (canonical spec 5)
          - canonical: The RFC 8785 canonical JSON text the hash covers (canonical spec 4)

          [default: digest]

      --strict
          Treat validation warnings on the resolved document as failures

      --own
          Hash the document on its own, with `extends` and `merge_strategy` stripped and no resolution: the value a `#sha256:` digest pin names and a receipt records for a chain link (core spec 2.3)

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

h2h panic options#

Select activate, deactivate or status. This manages a sentinel file, not another process latch. Usage errors: 2.

text
Manage emergency panic mode (deny-all kill switch)

Usage: h2h panic <COMMAND>

Commands:
  activate    Activate panic mode by creating the sentinel file
  deactivate  Deactivate panic mode by removing the sentinel file
  status      Check the current panic mode status
  help        Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

h2h panic activate options#

0: sentinel created; 1: write failed; 2: invalid usage.

text
Activate panic mode by creating the sentinel file

Usage: h2h panic activate [OPTIONS]

Options:
      --sentinel <SENTINEL>  Path to the sentinel file (default: .hushspec_panic in current directory)
  -h, --help                 Print help
  -V, --version              Print version

h2h panic deactivate options#

0: sentinel removed or already absent; 1: removal failed; 2: invalid usage. Running SDK latches require explicit reset.

text
Deactivate panic mode by removing the sentinel file

Usage: h2h panic deactivate [OPTIONS]

Options:
      --sentinel <SENTINEL>  Path to the sentinel file (default: .hushspec_panic in current directory)
  -h, --help                 Print help
  -V, --version              Print version

h2h panic status options#

0: absence proven; 1: present or absence cannot be proven; 2: invalid usage.

text
Check the current panic mode status

Usage: h2h panic status [OPTIONS]

Options:
      --sentinel <SENTINEL>  Path to the sentinel file (default: .hushspec_panic in current directory)
  -h, --help                 Print help
  -V, --version              Print version

h2h sign options#

0: signed; 1: approval/key/policy/signing/output failure; 2: missing policy, invalid duration or usage.

text
Sign a policy file with an Ed25519 key

Usage: h2h sign [OPTIONS] --key <KEY> <POLICY>

Arguments:
  <POLICY>  Policy file to sign

Options:
  -k, --key <KEY>              PEM PKCS#8 Ed25519 private key (see `h2h keygen`)
      --expires-in <DURATION>  Expiry, as a duration from now: 30d, 12h, 90m, 3600s
      --policy-version <N>     Override the policy_version claim (defaults to metadata.policy_version)
      --signer <SIGNER>        Human-readable signer identity (e.g. an email address)
  -o, --out <PATH>             Output path for the .sig file (defaults to <POLICY>.sig)
      --allow-unapproved       Sign a policy whose lifecycle_state is not approved or deployed (development only -- the signature then attests an unreviewed policy)
  -h, --help                   Print help
  -V, --version                Print version

h2h verify options#

0: valid; 1: invalid signature with reason/detail JSON on stderr; 2: unusable inputs or usage.

text
Verify a policy file's detached signature

Usage: h2h verify [OPTIONS] <POLICY>

Arguments:
  <POLICY>  Policy file to verify

Options:
  -s, --sig <PATH>             Detached .sig file (defaults to <POLICY>.sig, then <POLICY stem>.sig)
  -k, --key <PATH>             PEM SPKI public key, accepted as a one-key keyring
      --keyring <PATH>         Trusted keyring JSON (hushspec-keyring.v1.schema.json)
      --now <TIMESTAMP>        Verifier clock, RFC 3339 (defaults to now)
      --max-skew <SECONDS>     Allowed clock skew for signed_at, in seconds [default: 300]
      --last-seen-version <N>  The last policy_version accepted for this policy name (rollback protection)
  -f, --format <FORMAT>        Output format [default: text] [possible values: text, json]
  -h, --help                   Print help
  -V, --version                Print version

h2h keygen options#

0: keys written; 1: key conversion, existing-file refusal or I/O failure; 2: invalid usage.

text
Generate a new Ed25519 keypair for policy signing

Usage: h2h keygen [OPTIONS]

Options:
      --output-dir <OUTPUT_DIR>  Directory to write key files to (defaults to the current directory) [default: .]
      --name <NAME>              Base name for the key files: writes <NAME>.key.pem and <NAME>.pub.pem [default: h2h]
      --convert <OLD_KEY>        Convert a HushSpec 0.1 key file to PEM instead of generating a new key
      --force                    Overwrite existing key files
  -h, --help                     Print help
  -V, --version                  Print version

h2h schema options#

0: schema/list printed; 2: unknown name or input/serialization/usage failure.

text
Print a published HushSpec JSON Schema

Usage: h2h schema [OPTIONS] [NAME]

Arguments:
  [NAME]  Schema to print: a short name (core, posture, ...) or a published file name (hushspec-core.v1.schema.json)

Options:
      --list             List the available schema names instead of printing one
  -f, --format <FORMAT>  Output format for --list (the schema body itself is always JSON) [default: text] [possible values: text, json]
  -h, --help             Print help
  -V, --version          Print version

h2h completions options#

0: completion script printed; 2: invalid shell or usage.

text
Generate a shell completion script

Usage: h2h completions <SHELL>

Arguments:
  <SHELL>  Shell to generate a completion script for [possible values: bash, elvish, fish, powershell, zsh]

Options:
  -h, --help     Print help
  -V, --version  Print version

h2h version options#

0: version printed; 1: serialization failure; 2: invalid usage.

text
Print CLI, build, and spec version information

Usage: h2h version [OPTIONS]

Options:
  -f, --format <FORMAT>  Output format [default: text] [possible values: text, json]
  -h, --help             Print help
  -V, --version          Print version

h2h log options#

Select verify; missing/invalid subcommand: 2.

text
Verify a hash-linked receipt log

Usage: h2h log <COMMAND>

Commands:
  verify  Check sequence continuity, hash links, receipt validity, and entry signatures; report the first break by file and line
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

h2h log verify options#

0: supplied chain verifies; 1: first break reported by file/line; 2: unusable trust configuration or usage. A prefix is not completeness.

text
Check sequence continuity, hash links, receipt validity, and entry signatures; report the first break by file and line

Usage: h2h log verify [OPTIONS] <FILES>...

Arguments:
  <FILES>...  Log files in rotation order, oldest first

Options:
      --keyring <PATH>      Trusted keyring JSON for entry signatures
      --key <PATH>          A single trusted public key (PEM) for entry signatures
      --require-signatures  Every entry must carry a signature that verifies
      --now <TIMESTAMP>     Verifier clock as an RFC 3339 timestamp (defaults to now)
      --max-skew <SECONDS>  Allowed signer clock skew in seconds [default: 300]
  -f, --format <FORMAT>     Output format [default: text] [possible values: text, json]
  -h, --help                Print help
  -V, --version             Print version

h2h receipts options#

Select verify; missing/invalid subcommand: 2.

text
Verify decision receipts against a policy and a keyring

Usage: h2h receipts <COMMAND>

Commands:
  verify  Validate receipts, check they name the given policy, re-derive the decision where the action can be replayed, and verify signatures
  help    Print this message or the help of the given subcommand(s)

Options:
  -h, --help     Print help
  -V, --version  Print version

h2h receipts verify options#

0: supplied receipts pass requested checks; 1: receipt/policy/signature refusal; 2: input/configuration/usage failure.

text
Validate receipts, check they name the given policy, re-derive the decision where the action can be replayed, and verify signatures

Usage: h2h receipts verify [OPTIONS] <FILES>...

Arguments:
  <FILES>...  Receipt files: .jsonl logs (receipt entries), JSON receipts, or signed receipts ({receipt, signature})

Options:
      --policy <PATH>       The policy every receipt must name (by canonical content hash); when the receipt carries no content, the decision is re-derived against it
      --keyring <PATH>      Trusted keyring JSON for receipt signatures
      --key <PATH>          A single trusted public key (PEM) for receipt signatures
      --require-signatures  Every receipt must carry a signature that verifies
      --now <TIMESTAMP>     Verifier clock as an RFC 3339 timestamp (defaults to now)
      --max-skew <SECONDS>  Allowed signer clock skew in seconds [default: 300]
  -f, --format <FORMAT>     Output format [default: text] [possible values: text, json]
  -h, --help                Print help
  -V, --version             Print version

h2h report options#

0: output produced; 1: chain/integrity/authentication/required-boundary failure; 2: input/configuration/limits/output/usage failure. Strict mode publishes no success packet on refusal.

text
Aggregate receipts into a compliance evidence report

Usage: h2h report [OPTIONS] <FILES>...

Arguments:
  <FILES>...
          Hash-linked logs (`.jsonl`) or plain receipt JSONL files

Options:
      --since <TIMESTAMP>
          Only count records at or after this RFC 3339 timestamp

      --until <TIMESTAMP>
          Only count records at or before this RFC 3339 timestamp

      --policy <PATH>
          Policy whose `metadata.controls` the report joins the receipts against

  -f, --format <FORMAT>
          Output format

          Possible values:
          - text
          - json
          - csv
          - oscal: Contextual OSCAL observations; requires strict verification and assessment context

          [default: text]

      --by <TABLE>
          Report on one table only (and, for CSV on stdout, which one)

          [possible values: control, rule, decision, policy]

      --out <PATH>
          Write here: a directory for `--format csv`, a file for every other format (default: stdout)

      --lenient
          Skip input lines that do not parse instead of refusing to report

      --unverified
          Report even though an input log's hash chain did not verify

      --keyring <PATH>
          Trusted keyring JSON for entry signatures

      --key <PATH>
          A single trusted public key (PEM) for entry signatures

      --require-signatures
          Every log entry must carry a signature that verifies (legacy mode: logs only)

      --max-skew <SECONDS>
          Allowed signer clock skew in seconds

          [default: 300]

      --now <TIMESTAMP>
          Stamp the report with this RFC 3339 time instead of the wall clock

      --experimental-oscal
          Enable the experimental OSCAL exporter (`--format oscal`)

      --top-paths <N>
          How many `rule_path`s each rule-block row lists

          [default: 5]

      --evidence-profile <PATH>
          Experimental strict verification profile (offline local artifacts)

      --verification-out <PATH>
          New verification sidecar file, published last as the completion marker

      --assessment-context <PATH>
          Offline OSCAL assessment context manifest (requires strict OSCAL output)

      --native-report-out <PATH>
          New native JSON report file accompanying strict OSCAL output

      --max-evidence-file-bytes <MAX_EVIDENCE_FILE_BYTES>
          Strict mode only: maximum bytes in any input file (default 16777216)

      --max-evidence-total-bytes <MAX_EVIDENCE_TOTAL_BYTES>
          Strict mode only: maximum total input bytes (default 67108864)

      --max-evidence-line-bytes <MAX_EVIDENCE_LINE_BYTES>
          Strict mode only: maximum JSONL line bytes (default 1048576)

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

Loading documentation index…

↑↓ navigate↵ openesc close