oxagenDocs
Security

Export and verify a run

Check a run export offline and read the limits of its record.

An auditor needs to check what an agent did and what the record leaves out. Export a finished run, then check the file offline with oxagen verify. Only an organization Owner can export. The auditor needs no Oxagen account.

Oxagen locks the export with its private key. Locked means any change after that point shows. Anyone with the matching public key can check the lock. Compare the key ID with one you obtain from the operator through a separate channel. A key shipped inside a file does not establish who made the file.

Export

  1. Open the Run page and choose Export, or run oxagen run export <run-id>. The run must have ended and its record must be locked.
  2. Read the status with oxagen run export-status <export-id>. Wait for ready. A failed export reports its error.
  3. Run oxagen run download <export-id>. The CLI writes a ZIP and checks its SHA-256 hash against the recorded hash. A hash is a short code made from the content. Any change gives a different code.
  4. Give the ZIP to the auditor. A download link also works without sign-in for 15 minutes. Reading the status again creates a new link.

Verification

Install the CLI:

sh
curl -fsSL https://docs.oxagen.sh/install.sh | sh

The installer downloads an executable when one is available. Its npm fallback needs Node.js 22.15 or newer. Node.js 23 needs version 23.8 or newer. You can also install the npm package directly:

sh
npm install -g @oxagen/cli

Check the ZIP or the directory you extracted it into:

sh
oxagen verify export.zip
oxagen verify ./export
oxagen verify export.zip --json

The check reads local files and makes no network call. It sends no usage telemetry and writes nothing to your home directory.

Read the output before accepting the record:

  • Frames: each recorded event has a hash check and a link check. The link places it in its chain. A frame without its original event reports hash not carried. Its links and the export's locked bytes can still be checked.
  • Checks: frame counts, Merkle roots (hashes of the frame hashes), exported content, locks, and key IDs must hold. A manifest check fails when a coverage value in manifest.json differs from the locked copy or is missing.
  • Record coverage: each record coverage line prints the enforcement tier, completeness gaps, and replay grade from the lock. The tier names the recorded enforcement path. The gaps name known missing evidence. The replay grade states what replay the record supports. not recorded means the locked grade is null.
  • Redactions: the summary lists removed credentials by kind and count, and content the export withholds. It never prints a credential value.

HELD means the checks passed, and the command exits 0. BROKEN names a failed check and exits 1. JSON output carries the same checks and coverage values. An empty gaps list means no gaps were declared. It does not prove complete recording.

Lock timing

For a run wrapped on a person's computer, Oxagen makes its lock when it builds the export. The run export carries no independent timestamp from an outside authority. Its exported_at field records the export time but is not covered by the lock.

A passing check detects changes after the lock. It does not prove when the agent acted, that every action was recorded, or that the work was correct. Read the record coverage statement for the recording paths and the actions they cannot see. Observe mode records actions without enforcing rules on them.

Files

FileContents
manifest.jsonFormat, run ID, export time, key ID, and a list under attempts with each chain's figures and coverage values.
frames.ndjsonOne recorded event per line, with hashes and chain links.
attestation.jsonOxagen's locks, their public key, and the locked figures.
redactions.jsonCounts of removed credentials and withheld content.
verify.mjsA standalone Node.js verifier.

The lock covers run_id, attempt_id, frame_count, merkle_root, archive_segment_digest, enforcement_tier, completeness_gaps, and replay_grade. It uses Ed25519 over JSON in one standard form (RFC 8785). The verifier compares the manifest's coverage values with these locked values.

The export uses oxagen.run-export/3, or oxagen.run-export/4 when a frame records content Oxagen removed at ingest. The CLI also reads earlier formats. Update the CLI if it reports unknown export format.

Content checks

The export names stored bodies by hash and does not carry those bodies. If you have a body, compare its SHA-256 hash with content.digest. When Oxagen removed a credential at ingest, compare it with content.server_redactions.digest instead. That field names the stored bytes after the removal, while content.digest names what the agent's computer sent.

To test a changed copy, extract the ZIP and edit a frame's event.body in frames.ndjson. Run oxagen verify ./export again. The frame and exported content checks fail. Keep the original ZIP for comparison.

Run node verify.mjs <directory> only on an export you made yourself. A program in a file someone else supplied runs with your rights before it checks anything. Use your installed oxagen verify for exports from others.

Claim exports

A claim export covers an agent across a time window and uses a separate format. It has its own timestamp, inclusion, gap, access, and recording coverage checks. Those checks do not add an outside timestamp to a run export.

For claim exports, obtain the record keeper's key ID, the Transparency Service's key ID, and the timestamp authority's root fingerprint through separate channels. Pass them with --issuer-key, --ts-key, and --tsa-root. Without all required pins, the verdict is UNPINNED and the command exits 3, even if the other checks hold.

On this page