Skip to content

Evidence log and packs

The Evidence page has two tabs:

  • Log: every match, fix, deploy, decision, alert, sign-in change and generated pack, as a record in an append-only, hash-chained log. Browse it, filter it by kind and date, verify the chain, and export it as CSV.
  • Packs: one-click evidence packs for ISO 27001, SOC 2 and Essential Eight over a period, as a JSON file (the record) and a PDF made from it (the copy people read).

Everyone in the organisation can read the log and download packs, Viewers included. With teams, a team member reads the log entries about their teams’ projects and no packs. Owners and admins generate packs.

Records are only ever added. Nothing in the log can be edited or deleted, by you or by MopokeHQ’s application.

Each record holds when it happened, its kind (such as response.not_affected, exposure.verified, scan.completed or package.released), who did it (a person, a CI job or MopokeHQ itself), what it is about, and the details. Each record’s hash covers its own fields and the hash of the record before it, so changing any record breaks every link after it.

Some examples of what lands in the log:

  • an advisory matched, a fix seen on a branch, a fix seen deployed,
  • a P1 alert, an escalation, an acknowledgement,
  • “not affected” and “accepted risk” decisions with their reasons, and every reopen,
  • external scans and their findings, ownership checks,
  • packages quarantined, blocked and released,
  • changes to AI settings, API keys, webhooks and teams,
  • quarterly access reviews,
  • each evidence pack generated, with its hash.

In the app. Evidence, Log, Verify chain walks the whole chain from the first record and reports the first record that fails, if any.

Yourself. Export the log and recompute every hash. You need no MopokeHQ code to do it.

  1. Export the log. Evidence, Log, Export CSV, with no kind or date filter (a filtered export leaves gaps, so its links will not match). The columns are:

    tenant_id, seq, occurred_at, kind, actor, subject, payload, dedupe_key, prev_hash, hash

    Every hashed field is written exactly as it was hashed. prev_hash and hash are hex.

  2. Start from the genesis hash. Record 1’s prev_hash is SHA-256 of the text mopoke-evidence-genesis: followed by the tenant_id.

  3. Recompute each record’s hash. SHA-256 over these fields, in this order, each written as a 4-byte big-endian length followed by its bytes:

    1. prev_hash, as raw bytes (decode the hex first),
    2. tenant_id,
    3. seq, in decimal,
    4. occurred_at, exactly as in the export (UTC, microseconds, such as 2026-10-23T01:02:03.000000Z),
    5. kind,
    6. actor,
    7. subject,
    8. payload,
    9. dedupe_key.
  4. Check the links. Each record’s hash must equal what you computed, and each record’s prev_hash must equal the previous record’s hash.

A short Python script that does all of this:

import csv, hashlib, struct, sys
def hash_record(prev: bytes, row: dict) -> bytes:
h = hashlib.sha256()
fields = [prev] + [row[k].encode() for k in
("tenant_id", "seq", "occurred_at", "kind", "actor", "subject", "payload", "dedupe_key")]
for f in fields:
h.update(struct.pack(">I", len(f)))
h.update(f)
return h.digest()
with open(sys.argv[1], newline="", encoding="utf-8") as f:
rows = list(csv.DictReader(f))
prev = None
for row in rows:
if row["seq"] == "1":
prev = hashlib.sha256(("mopoke-evidence-genesis:" + row["tenant_id"]).encode()).digest()
elif prev is None:
prev = bytes.fromhex(row["prev_hash"]) # an export that starts after record 1
if bytes.fromhex(row["prev_hash"]) != prev:
sys.exit(f"Broken link at record {row['seq']}")
prev = hash_record(prev, row)
if prev.hex() != row["hash"]:
sys.exit(f"Hash mismatch at record {row['seq']}")
print(f"{len(rows)} records verified. Head: {prev.hex() if prev else 'none'}")

Run it as python3 verify.py evidence-log.csv.

The log is also on the public API (GET /evidence), in chain order with each record’s hash and previous hash, for tools that archive it.

On Evidence, Packs, an owner or admin picks:

  • a framework: ISO 27001, SOC 2 or Essential Eight,
  • a period: last month, last quarter, or custom days (up to 400 days, the last day may be today), in your time zone,
  • a client name, optional: the cover then shows the client, with “Prepared by” your organisation.

Each pack is two files:

  • JSON, the record: every number in the pack, the chain head at the end of the period and its hash.
  • PDF, made from the JSON, A4. The header is your organisation’s name, or the client’s. The only MopokeHQ branding is one footer line.

A pack never says a control is met. For each control it says what MopokeHQ evidences, what the records show for the period, and what MopokeHQ does not evidence. Coverage is one of evidenced for reporting repositories, partly evidenced or not evidenced by MopokeHQ, never a pass or fail. The judgement stays with you and your auditor.

MopokeHQ itself holds no ISO 27001 or SOC 2 certification. The packs help you show your own controls; they do not certify anything.

  1. When a pack is generated, the SHA-256 of its JSON file, exactly as downloaded, is written to the log in an evidence_pack.generated record. The packs list shows the hash and the record number.
  2. Hash the JSON you hold (shasum -a 256 pack.json or sha256sum pack.json) and compare.
  3. The pack’s chain.period_end_seq record in the log must have the hash chain.period_end_hash. If the chain verifies, nothing in the period was changed after the pack was made.
Control Coverage Evidence
8.8, obtaining information about technical vulnerabilities Partly Inventory from uploaded SBOMs, advisory feeds and their freshness, daily scan records
8.8, evaluating exposure Partly Priorities, confidence, CISA KEV handling, accepted risks and not-affected decisions
8.8, taking appropriate measures Partly Fix times against deadlines, upgrade merge requests, deploy reports, unsupported runtimes, alerts
5.7 Threat intelligence Partly CISA KEV and FIRST EPSS collected and applied to priority
5.18 Access rights Partly With teams on: members see only their teams’ projects, and the quarterly access reviews completed in the period. Access to systems other than MopokeHQ is not evidenced.

Not included: 8.9 configuration management, 5.9 asset inventory, 8.32 change management, 5.24 to 5.28 incident management.

Deadlines are MopokeHQ’s defaults (P1 48 hours, P2 14 days, P3 30 days), since the standard leaves timeframes to you. Requirement text is paraphrased (ISO text is copyright).

Criterion Coverage
CC7.1, conducts vulnerability scans Partly
CC7.1, monitors infrastructure and software Partly (software only)
CC7.1, detects unknown or unauthorised components Partly (known-malicious packages, not policy)
CC7.1, uses defined configuration standards Not evidenced
CC7.1, implements change-detection mechanisms Not evidenced
CC6.2 and CC6.3, authorising, reviewing and removing access Partly, from teams and the quarterly access reviews

CC7.2 to CC7.5 and CC8.1 are listed as not included, with reasons. Deadlines as for ISO 27001. Criteria text is paraphrased (AICPA text is copyright).

Essential Eight (patch applications, patch operating systems)

Section titled “Essential Eight (patch applications, patch operating systems)”

Follows the Essential Eight Maturity Model (November 2023). Requirement text is quoted under CC BY 4.0.

Requirement Coverage Evidence
Asset discovery (ML1 to 3) Partly Repositories registered from CI uploads, last SBOM for each
Scanner with an up-to-date vulnerability database (ML1 to 3) Evidenced Feed sync state, daily scan records
Daily scanning of online services (ML1 to 3) Partly Daily scan records, repositories under continuous matching, SBOM freshness
Patch online services within 48 hours when critical or exploited (ML1 to 3) Partly Fix times against the 48 hour deadline, known exploited vulnerabilities
Patch online services within two weeks otherwise (ML1 to 3) Partly Fix times against the two week deadline
Remove unsupported online services (ML1 to 3) Partly PHP and Node.js runtimes past end of life
Fortnightly scanning of other applications (ML2, 3) Partly Daily scan records, coverage
Patch other applications within one month (ML2, 3) Partly Fix times against 30 days
Remove unsupported applications (ML3) Partly Runtimes past end of life
Patch operating systems Not evidenced Listed so the gap shows

Deadlines: online services, critical or exploited, 48 hours; online services, other, two weeks; other applications, 30 days. “Critical” is the advisory’s severity; a working exploit is taken to exist when the CVE is in CISA KEV or the package is malicious. Office suites, browsers, workstations, drivers, firmware and the other six strategies are listed as not included.

Every metric is computed for one organisation over the period, from one consistent snapshot.

Metric Shows
Inventory Repositories at the end of the period, new ones, SBOM uploads
Scan coverage Repositories with a stored SBOM, re-matched as advisories land; SBOMs older than 30 days
Daily scans Days in the period with a daily scan record, and the days without one
Feed freshness Each advisory feed’s last sync
Responses Opened, resolved, open at the end, past due, malicious packages
Patch timeliness For each deadline: resolved in the period, inside the window, median time to fix, open past the window
CISA KEV handling Each known-exploited advisory: listed, matched, fixed or open, hours
End of life PHP and Node.js runtimes past end of life on default branches, in deployed environments and in runtime check-ins
Risk decisions Risks accepted and not-affected decisions, with reasons; accepted risks still in force and past their review date
Fixes Upgrade merge requests asked for and their outcome
Deployments Deployments reported, and advisories resolved because the fix was seen deployed
Alerts P1 alerts, escalations, digests, time to acknowledge
Log activity Records added, by kind
Chain Chain verified from record 1; the head at the end of the period and its hash
Access reviews Whether teams limit access, and the quarterly access reviews completed: who, how many people, how many removed, quarters without one

Notes:

  • Fix times start when MopokeHQ matched the advisory to your code, or later when the CVE was added to CISA KEV or the advisory moved up to its current priority. A vendor’s release date can be earlier; every pack says so.
  • An advisory resolves when its fixed version is seen deployed, or when a person confirms a fix that is only on a branch. A dismissed advisory (not affected, or risk accepted) is a decision, not open work and not a fix.
  • A daily scan record is written once per day (UTC) only when every advisory feed synced on time (within 24 hours; 48 hours for EPSS). A missing day means matching ran on stale data that day. Records start from the day the feature shipped, and a pack for an earlier period says so.
  • A release line missing from the end-of-life table is reported as unknown, never as supported.