Manage · Audit log (hosted)

Audit log (hosted)

Hosted audit log across every tracked repo in this team. Search by actor / event kind / time range; CSV export for compliance review. Reads the `kind:"audit"` envelope at team scope.

Start here if you want to search or export the audit trail: Audit log.

See it in motion

Where to find it

  • Hosted route: repoops.ai/team/cloud-audit
  • API: /api/audit?team=<teamId> (add format=csv for the download); the signed compliance export is GET /api/team/audit/export
  • Auth: Any team member can read the log; the signed export needs the owner or admin role
  • Plan: Any hosted plan for the log; the signed export is Enterprise

What it does for you

An audit trail at team scope, exportable as evidence.Hosted audit log across every tracked repo in this team. Search by actor / event kind / time range; CSV export for compliance review. Reads the `kind:"audit"` envelope at team scope.

Proving an export is genuine

Every signed export carries a sha256 digest on the response, asx-repoops-export-digest. A digest proves a file is byte-identical to the one this server produced, but only to someone who already holds the digest from a channel the file did not travel on. It cannot prove we produced it, because anyone can recompute a sha256.

When an export signing key is configured, the response also carries an Ed25519 signature over that digest. Your auditor verifies it with the public key, and needs no account here to do so:

  • Signature: x-repoops-export-signature (base64)
  • Public key: GET /api/audit-export/pubkey, unauthenticated
  • Signed or not: x-repoops-export-signed. When it reads false, x-repoops-export-sig-reason says why, so a half-configured install never looks signed.

One limit worth planning around: a browser download drops response headers. When the signature is the point, pull the export withcurl -D headers.txt -o export.csv and keep both files together. The completeness claim (that the file is the whole window, not a silent truncation) rides inside the file itself, so that part survives either way. An export that would exceed 10,000 rows is refused with a 413; narrow it withsince and until.

What the signature is over

This part was missing, and without it the steps above do not produce a verdict. The signature is not over the file bytes and not over the bare hash. It is over a domain-separated message: the context string, a newline, then the digest, where the digest already carries its sha256: prefix.

digest  = "sha256:" + hex(sha256(file bytes))
message = "repoops.audit-export.v1" + "\n" + digest
verify(ed25519, publicKey, message, base64decode(signature))

The context string is what stops a signature being carried into another context unchanged, and the v1 suffix is how the construction can change without invalidating signatures already issued.

Run it. The export route reads your browser sign-in, so pass the session cookie your browser holds for www.repoops.ai; it exports the team you are signed in to. This checks a downloaded export against the headers you saved beside it, on Node 20 or newer, with nothing to install:

curl -D headers.txt -o export.csv \
  -H "Cookie: $BROWSER_COOKIE" \
  "https://www.repoops.ai/api/team/audit/export?since=2026-07-01&until=2026-09-30"

curl -s https://www.repoops.ai/api/audit-export/pubkey   # { alg, publicKey }

node verify-export-signature.mjs \
  --file export.csv --headers headers.txt --pubkey <publicKey>

The script is scripts/verify-export-signature.mjs in the RepoOps repository. It is dependency-free and reads only node:crypto, so you can copy the single file out and run it on a machine that has neither the repo nor npm. It exits 0 when the file verifies, 1 when it does not, 2 on a usage or input problem, and prints which of the two checks failed.

Pass the key you fetched from the pubkey URL over HTTPS, or one you pinned out of band. Do not use the x-repoops-export-key-id header as your key: anyone can forge a file, sign it with a key they generated, and set every header on it, so checking a signature against the key carried beside it proves only that the author owned a key.

Read more

Last updated