Anchoring Runbook
This page is the operator runbook for the audit-log anchoring rail added on top of the SHA-256 hash chain described in the Security Policy and implemented in spotforecast2_safe.manager.logger. It is deliberately separate from that page: anchoring is something an operator of a deployed system does to their own retained logs, not something spotforecast2-safe itself does automatically, ships pre-configured, or can verify on its own authority.
The two make targets
make anchor LOG=~/spotforecast2_safe_models/logs/sf2-safe-logger_20260810_090000.log \
TSA_URL=https://timestamp.digicert.com
make anchor-verify LOG=~/spotforecast2_safe_models/logs/sf2-safe-logger_20260810_090000.logLOG accepts either a single audit log file or a directory; a directory is anchored as one AuditAnchorSet document (audit-anchor-set.json) covering every *.log file found in it, which is what closes the rotation gap left by a per-file anchor (setup_logging starts a fresh file, and therefore a fresh chain, on every process start).
make anchor refuses immediately, before writing anything, if the named chain is not intact (verify_audit_log(...).chain_intact is False), and again if TSA_URL is unset — there is no default timestamping authority; see Choosing a witness below for why and what to pick instead. On success it writes the canonical anchor document next to the log(s), gets an RFC 3161 token for it from TSA_URL via openssl ts -query and a stdlib HTTP POST, and stores the token (.tsr) and the TSA’s certificate chain (.tsa-chain.pem) alongside the document. make anchor-verify never touches the network: it re-verifies the stored token against the stored chain with openssl ts -verify, then hands the document’s AuditAnchor to verify_audit_log and prints AuditChainReport.summary().
Neither target is a prerequisite of make verify, make release, or make ship. Anchoring belongs to the deployer operating spotforecast2-safe in production, on logs that do not exist yet at release time; it is not a step in shipping this package.
Choosing a witness
An anchor is only as good as the place it points to. make anchor never picks one for you — endorsing a specific commercial or free service is not this Makefile’s call to make. The options, roughly cheapest first:
| Witness | What it gives you | Caveat |
|---|---|---|
RFC 3161 timestamp, free TSA (e.g. freetsa.org) |
An independent “this hash existed at time T” claim, verifiable offline forever with the stored token and chain | No legal presumption; free services can (and have) disappeared |
| RFC 3161 timestamp, commercial TSA (DigiCert, Sectigo, GlobalSign, …) | Same as above, backed by an organisation with an SLA | Still no legal presumption unless the TSA is eIDAS-qualified |
| RFC 3161 timestamp, eIDAS-qualified EU Trust Service Provider (browse the EU Trusted List) | Everything a free TSA gives you, plus a legal presumption of the accuracy of the date/time and the integrity of the timestamped data, under eIDAS Regulation (EU) No 910/2014, Article 41(2) | Usually a paid, registered service |
| Any git remote (push the anchor document, or a commit containing it, to a second control domain) | A second party that would have to collude with whoever holds the log to backdate it | Force-pushable; only as strong as who controls that remote |
| PyPI digest immutability | Free, and PyPI never allows re-uploading a version | Only proves something about a released package, not an arbitrary log file — project-side only, not a deployer tool |
| A second organisation’s log (e.g. a monitoring vendor mirrors the anchor document) | Often the cheapest real option for a deployer who already has such a relationship | Only as strong as that organisation’s own retention practice |
| Printed or notarised record | Withstands a purely digital compromise | Manual, does not scale to a daily anchoring cadence |
Rejected: Sigstore/Rekor (the project retired Sigstore in 2026-07 when the hosted CI that provided its OIDC identity went away — see Security Policy) and OpenTimestamps (a Bitcoin-anchored scheme with no maintained, audited Python or CLI tooling this project could adopt without a new dependency). Both were considered and set aside when this rail was designed.
openssl gotchas
LibreSSL vs OpenSSL. macOS ships LibreSSL as
/usr/bin/openssl. Both LibreSSL 3.x and Homebrew’s OpenSSL 3.x acceptts -query -sha256 -certandts -verify -CAfiledespite LibreSSL’s usage banner advertising only md4/md5/sha1/ripemd160 — SHA-256 timestamping is not blocked in practice. What LibreSSL’sts -verifydoes not have is-attime: archival verification of a token after the TSA’s own signing certificate has expired needs that flag, and therefore needs Homebrew OpenSSL (brew install openssl@3), not the systemopenssl. Pointmake anchor/make anchor-verifyat it explicitly:make anchor-verify LOG=... OPENSSL=/opt/homebrew/opt/openssl@3/bin/opensslThe certificate chain must be stored, not just the token.
openssl ts -verifyneeds the TSA’s certificate chain to check the token’s signature; a.tsrfile alone is not self-verifying.make anchorextracts the chain from the TSA’s response and writes it next to the token (<doc>.tsa-chain.pem) precisely because that chain, not just the token, has to survive as long as the log does — potentially far longer than the TSA’s own certificate remains valid or its website stays up. Losing the chain file makes a stored token unverifiable even though nothing about the log or the token itself changed.
What make cannot provide
None of this promotes make anchor from a convenience into a control on its own:
- It cannot be the witness.
make anchorautomates the round trip to an independent service; it is not itself independent of the machine or operator running it. A purely local anchor (noTSA_URL, or a TSA under the same operator’s control) proves nothing beyond what the log file already proves about itself. - It cannot compel cadence. How often an operator anchors is entirely their choice;
verify_audit_log’s prefix matching (AnchorStatus. MATCHES_PREFIX) means an infrequently anchored log is still gracefully handled rather than falsely flagged, but the records appended after the last anchor remain unanchored until the next one runs. - It cannot survive being skipped. Nothing forces an operator to run
make anchorat all; unlikemake verify, it is not wired into any release or CI gate (there is no CI - see Security Policy). What is mitigated: the offline half of this rail — comparing a presentedAuditAnchoragainst a recomputed chain — runs undermake verifyon every commit (via the docstring examplesmake docexecutes, and the testsmake testruns), so that code path cannot silently rot even in a year nobody anchors anything. The decision to anchor a given production log remains entirely the deployer’s. - It cannot establish authorship. Anchoring binds a chain head to an external witness at a point in time; it says nothing about who wrote the records. The chain is, and remains, unkeyed — see
AuditCaveat. AUTHORSHIP_NOT_ESTABLISHED, one of the twoSTANDING_LIMITATIONSeveryAuditChainReportcarries regardless of anchoring status.
See also
- Security Policy — the hash chain itself, and what this package does and does not provide overall.
spotforecast2_safe.manager.logger(API reference: Manager section) —AnchorStatus,AuditAnchor,AuditAnchorSet,AuditCaveat,AuditChainReport,verify_audit_log.scripts/anchor_audit_log.py— the scriptmake anchor/make anchor-verifyinvoke; run it directly for options these two targets do not expose (--ca-file,--timeout, …).