norcaster-verify
Independent verification tooling for Norcaster evidence. It verifies without a Norcaster account, without API access and without trusting Norcaster. Everything below runs offline.
Two uses:
- Verify an audit bundle: the evidence package Norcaster exports for SOC 2 examinations and
EU AI Act readiness reviews (Python,
norcaster-verifyon PyPI). - Verify a Shared Record exchange or dispute export: what two companies' agents exchanged, signed by both and logged as hashes (Python or Node; the protocol is a draft, see below).
The examples run from a clone of this repository, against files committed in it:
git clone https://github.com/norcaster/verify.git && cd verify
1. Verify an audit bundle
pip install norcaster-verify
norcaster-verify examples/audit-bundle.json --offline
examples/audit-bundle.json is a small synthetic bundle. Expected result: PASS, with the
content digest, governing configuration and approval provenance checks passing, and the trace
chain snapshot and public Rekor anchors reported as SKIP because the example carries none. A
SKIP is never a PASS.
Change one recorded decision and verify again:
python -c "import json; b = json.load(open('examples/audit-bundle.json')); b['sections']['enforcement_records'][2]['outcome'] = 'allowed'; json.dump(b, open('tampered-bundle.json', 'w'))"
norcaster-verify tampered-bundle.json --offline
Expected result: FAIL on the bundle content digest, exit code 1.
For a real bundle, run norcaster-verify bundle.json (online, it looks up the Rekor anchors),
--offline (it uses the Rekor snapshots embedded in the bundle) or --json (a machine-readable
report). Exit code 0 means no check failed.
What it checks:
- the manifest's content digest;
- the internal integrity references;
- the public Sigstore Rekor anchors;
- governing-configuration consistency: every enforcement decision's
deployment_policy_version/policy_content_hashresolves to a policy snapshot in the bundle whose hash recomputes. Org-scoped decisions, which have no deployment, resolve theirorg_policy_versionagainstorg_policy_version_snapshots; - approval provenance: policy versions produced by a config proposal name an independent
approver and appear in
change_history.
For anchors written by a signing-enabled chain it also checks that the Rekor entry embeds the
tenant's public key named in the manifest and a valid ES256 signature over the segment root. That
needs pip install "norcaster-verify[signatures]"; without cryptography that part reports
SKIP, not PASS.
The same two files (verify_audit_bundle.py, audit_bundle_verify_lib.py) ship inside every
Norcaster audit-bundle zip. This package and the bundled copies are interchangeable, and a test
enforces that they are identical byte for byte.
2. Verify a Shared Record exchange or dispute export
A Shared Record export is one JSON evidence file: the signed receipts, acknowledgements and missing-acknowledgement records for the exchanges in question, their log proofs, a witnessed checkpoint, both organisations' key documents and, for exchanges a party chose to reveal, the content and salt. You verify it against your own trust file: the root fingerprints you pinned for each organisation and the log and witness keys you trust.
Each test vector in spec/vectors/ holds both, plus the expected result. Split one into the two
files the verifier takes:
python -c "import json; v = json.load(open('spec/vectors/valid-acknowledgement.json')); json.dump(v['evidence'], open('evidence.json', 'w')); json.dump(v['trust'], open('trust.json', 'w'))"
Python:
pip install "norcaster-verify[signatures]"
python -m norcaster_verify.shared_record verify evidence.json --trust trust.json
Node (18 or later), the same checks:
npx --yes --package @norcaster/verify norcaster-verify-record evidence.json --trust trust.json
Expected result: the exchange state=acknowledged, Result: no check failed and exit code 0.
Every check passes except two that do not apply here and say so as SKIP: there is no
missing-acknowledgement record, and the evidence holds one checkpoint, so there is no earlier one
to prove consistency with. The receipt's content commitment is checked against the revealed
content and salt (content_commitment: revealed content and salt match the commitment).
Now a copy whose receipt was changed after it was signed:
python -c "import json; v = json.load(open('spec/vectors/tampered-content-hash.json')); json.dump(v['evidence'], open('evidence.json', 'w')); json.dump(v['trust'], open('trust.json', 'w'))"
python -m norcaster_verify.shared_record verify evidence.json --trust trust.json
Expected result: FAIL on the receipt signature, the content commitment and the receipt's log
inclusion, and exit code 1. Add --json to either command for a machine-readable report. Without
the signatures extra the Python verifier reports signature checks as SKIP, never PASS; Node
always checks them. Python and Node give the same report, byte for byte, on every input.
Profiles. The default, --profile basic, reports what the evidence shows. For a dispute, add
--profile dispute: every log check (receipts, acknowledgements, missing-acknowledgement records,
key-document versions and root rotations) must then PASS, and a SKIP counts as a FAIL. Either way
the report says, per organisation (evidence_complete_for), that the result assumes the export
holds the organisation's complete key history, which v0 evidence cannot show:
python -m norcaster_verify.shared_record verify evidence.json --trust trust.json --profile dispute
Input. Both verifiers read the evidence and trust files as text under a strict JSON profile
(spec §4.1): no number with a fraction or an exponent, no NaN, no duplicate member names. A file
outside it is unusable input: the report's input section says why, and the exit code is 2. From
code, use verify_evidence_text(evidence_text, trust_text, profile="basic") in Python or
verifyEvidenceText(evidenceText, trustText, { profile }) in Node; verify_evidence and
verifyEvidence take parsed JSON, which can no longer show those mistakes.
The 25 vectors and what each must give are listed in spec/vectors/, and 52 negative vectors
(malformed input, as text) in spec/vectors/negative/. An export from a Norcaster organisation
(POST /orgs/{org}/shared-record/dispute-exports) or from the self-hosted counterparty component
(norcaster-counterparty export) verifies the same way.
Shared Record status
In development · pilot pairs by application. A pilot version is built and in testing. It is not in production or available to customers.
spec/shared-record-v0.md is a draft protocol, in development, published for review. It is
designed on the IETF SCITT architecture (RFC 9943), using COSE hash envelopes (RFC 9995), COSE
Receipts (RFC 9942) and the RFC 9162 Merkle tree. It may change. The Python module
norcaster_verify.shared_record is its reference implementation and lib/shared-record/ its
Node verifier. spec/REVIEW.md is the brief for an outside review of the protocol. The record
shows what each party signed and what the log recorded; it does not decide who is right.
3. Verify a trace integrity proof (Node)
Verifies a per-run trace integrity proof bundle (norcaster-proof-<run_id>.json): the local
hash-chain proof, the Merkle path, and one Rekor lookup to confirm the segment root against the
public transparency log. This one is online.
npx --yes --package @norcaster/verify norcaster-verify-proof --proof norcaster-proof-<run_id>.json
What it checks: the recorded chain-proof result carried in the bundle, the Merkle inclusion path
when one is present, the tenant-key (ES256) signature over the entry and, where the Merkle path
allows, over the segment root, and that the segment root and public key in the Rekor entry match
what the bundle carries. It does not recompute the full hash chain, and the Rekor URL comes from
the bundle itself. See docs/algorithm.md for exactly what each step proves. Unsigned
(version 1) bundles are reported as unsigned, never as verified. The npm package installs two
commands, norcaster-verify-proof and norcaster-verify-record, distinct from the Python
norcaster-verify command.
What verification does not prove
Completeness of an AI estate, compliance with any framework, or authenticity of the export source. Norcaster produces evidence; only a licensed CPA firm can attest under SOC 2, and EU AI Act conformity determinations require qualified counsel. A Shared Record verification shows what was signed and logged, not which party is right about a dispute.
Source and releases
This repository is a published snapshot of packages/norcaster-verify in Norcaster's main
repository, which is the source of truth. The Python files here are byte-for-byte the ones that
ship inside every audit-bundle zip, and a test in the main repository enforces that. Open issues
here.
Releases are cut from version tags (v0.4.0). The release.yml workflow builds the Python sdist
and wheel and publishes them to PyPI, and publishes the npm package, using each registry's
trusted-publishing (OIDC) flow, with no long-lived tokens. The versions in pyproject.toml and
package.json and the top entry of CHANGELOG.md must all equal the tag. CI runs the Node and
Python tests on every push.
Security reports: see SECURITY.md.
Apache-2.0 · security@norcaster.com
Metadata
Release files for norcaster-verify 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| norcaster_verify-0.4.0.tar.gz | 292.7 kB | With provenance | Details for norcaster_verify-0.4.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| norcaster_verify-0.4.0-py3-none-any.whl | Python 3 | none | any | Details for norcaster_verify-0.4.0-py3-none-any.whl |
Total release size: 344.6 kB
Release files / norcaster_verify-0.4.0.tar.gz
| Download URL | norcaster_verify-0.4.0.tar.gz |
|---|---|
| Size | 292.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
698124b0a6dc1654aa96b4a3d70709b5a25ca0108c7b3e69fdc3faa75aeb5d85
|
|
BLAKE2b-256 checksum How to use checksums |
b8cf5ef3397ae3ba4ad723ee61cc02ec3dc58038c866c348eb90c000e610639a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logRelease files / norcaster_verify-0.4.0-py3-none-any.whl
| Download URL | norcaster_verify-0.4.0-py3-none-any.whl |
|---|---|
| Size | 51.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
d1481d0b9375d0dc828dc97053b0ec096988a31fc10a6492943c7cd201140b67
|
|
BLAKE2b-256 checksum How to use checksums |
17bca37ce24d42588e9ab5d7c7452f0640bef46f80339cb19acf5c02e7813ffd
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logSecurity for norcaster-verify 0.4.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for norcaster_verify-0.4.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.