Skip to content

Evidence package and auditor access

An evidence package is one ZIP file with everything an auditor asks for about one environment, for one framework and one period. It contains the Statement of Applicability, control status and daily history, the AI risk register, findings and their corrective actions, internal audits, management reviews, an AI inventory summary, audit-log counts and the policy settings in force, with a readable report as HTML and as a PDF to file. A manifest lists the SHA-256 of every file and is signed with Ed25519. An auditor can check the package without trusting Shield360.

Auditor access lets an external auditor sign in and read the same evidence for one environment, read-only, until a date you choose.

  • To generate a package, you need the Generate evidence packages permission (compliance.evidence.export). Owners, administrators, the Compliance officer role and Auditors have it.
  • To invite an Auditor, you need the Manage members permission (organisation.members).
  • To sign packages, the server needs an evidence signing key. See Configure the signing key. Without one, packages are still produced, but they are unsigned.
Open Govern → Compliance program → Framework controls

Choose the environment in the header. The package is always for the current environment; a request cannot name another one.

Choose the framework and period

The package covers the framework shown on the page: ISO/IEC 42001, the EU AI Act, NIST AI RMF or OWASP Agentic (ASI). Choose From and To under Evidence package. The period is a range of UTC days, at most 366 days long, ending no later than yesterday and within the control history retention. The default is the last 90 days.

Select Generate package

The ZIP downloads. The panel says whether packages are signed, and with which key id, and lists the packages generated before for this environment.

To get only the report as a PDF, select Download PDF report instead. See Download the PDF report alone.

Every package is recorded with its id, environment, framework, period, manifest hash, signing key id, who generated it and when. Its generation is written to the audit log. The contents are never stored.

The API returns the same ZIP:

Terminal window
curl -H "Cookie: <session>" -o evidence.zip \
"https://<your-shield360>/api/governance/evidence-package?framework=iso_42001&from=2026-07-01&to=2026-09-30"

A period that is not two real days, is longer than 366 days, ends today or later, or starts before the history kept returns 400, and so does a format other than zip or pdf. A package that would be larger than 64 MB returns 413. If gathering the evidence takes longer than 60 seconds, the request returns 503 with a message instead of timing out. Choose a shorter period and try again.

FileContents
README.txtWhat the package is, the “not a certification” statement, whether it is signed, and how to verify it.
report.htmlA readable summary of everything below. A single file that opens offline.
report.pdfThe same report as report.html, with the same sections in the same order, as a PDF to file. See The PDF report.
manifest.jsonEvery other file with its SHA-256 and size; the package id, environment, framework, period, when it was generated, the user id of the person who generated it, and whether and how it is signed.
manifest.sigThe Ed25519 signature over manifest.json, 64 raw bytes. Only in signed packages.
signature.jsonThe same signature as base64, with its key id and the manifest’s SHA-256. Only in signed packages.
public-key.pemThe signing key’s public half, for convenience. Compare it with the published key before you rely on it.
SHA256SUMSEvery listed file’s SHA-256, for sha256sum -c.
statement-of-applicability.jsonEach control’s applicability, justification, owner, evidence reference and review, with the framework’s coverage (controls with a status, observed or attested only, decided, not applicable, partly met) and, for the NIST AI RMF, its source (NIST AI 100-1, AI RMF 1.0). Empty for frameworks without a Statement of Applicability (ISO/IEC 42001 and the NIST AI RMF have one).
eu-ai-act-statement.jsonEU AI Act packages only: every Article with its status (met, attested, partial, gap, no evidence, not applicable), its checks, its attestation and whether it applies (recorded or derived), the coverage summary, your recorded roles, the serious incidents (category, dates, deadline and status; no free text), the AI literacy coverage, and the source of the Regulation’s text.
control-status.json, control-status.csvEach control’s status and checks when the package was generated, measured over the last 7, 30 or 90 days (the shortest that covers the period).
control-history.jsonEach control’s recorded status for every day of the period. A day without a record is not recorded, never met.
risk-register.json, risk-register.csvEvery AI risk with its score, owner, treatment and evidence summary, as the risk register export has them.
findings.json, findings.csvEach finding raised before the end of the period and not closed before it began: its status at the end of the period, what was recorded at each step (containment, root cause, corrective action, effectiveness, acceptance) and who took each step and when. With counts of findings raised, closed, open, overdue and accepted as risks over the period. report.html has the same summary and a table.
internal-audits.json, internal-audits.csvEach internal audit planned or reported in the period: scope, criteria, lead auditor and independence, status, results, and the conclusion on each control in scope with the finding raised from it. The CSV has one row per control in scope. report.html has a summary table.
management-reviews.json, management-reviews.csvEach management review that met in the period: attendees, status, notes, sign-off, decisions and actions, and the input pack kept at sign-off. The CSV has one row per decision or action. report.html has a summary table.
ai-requests.json, ai-requests.csvEach AI request submitted by the end of the period and not already final before it began: what was asked for and why, its status at the end of the period, the decision and what it changed, and its steps up to the end. With counts of requests submitted, approved, rejected and withdrawn in the period, and of those open and overdue at its end. report.html and report.pdf have an AI requests and approvals section with the same summary and a table.
impact-assessments.json, impact-assessments.csvEvery version of each AI system’s impact assessment live during the period, with its facts (counts, never names), judgements and steps, and today’s coverage of the systems in scope. The CSV has one row per version, with the choices but not the text. report.html has a summary table.
red-team.json, red-team.csvEach agent test red-team run finished in the period: its target, suite, strategies and seed, and its results counted by attack category and strategy, with failures by severity; each target’s last red-team run; and, as of the period’s end, how many targets were red-teamed in the last 30 days and how many fail a critical test. The CSV has one row per run, category and strategy. Counts and the names people gave targets and suites only: no prompt, answer or transcript. Only where the environment has agent test targets.
data-lineage-summary.jsonThe period’s data lineage through the data gateway, the MCP gateway and the guards: flows and events counted by classification, outcome and origin, how many sources, consumers and destinations, and the sensitive classifications seen without an owner. No source, consumer, destination, person or value is named. Left out when lineage could not be read.
dataset-register.jsonThe dataset registry as it stood when the package was built: each dataset’s purpose, status and approval (and whether a second person approved it), which governed items are recorded (owner, provenance, license, quality and bias examination, preparation, retention, location), its data classes, sensitive-data kinds, legal basis, collection period, version, SHA-256 and linked AI systems, dictionary tables and lineage sources, and what it lacks. Free text is left out. Left out when the registry could not be read.
ai-inventory-summary.json, ai-inventory.csvEach AI asset with its owner, decision and risk rating, and how many people and machines use it. Not their names, and no billed amounts.
audit-log-summary.jsonAudit-log record counts by action over the period, for the organisation and for the records that name this environment. A package an Auditor generates has the environment’s counts only. No record contents, people or addresses.
policy-settings.jsonThe enforcement policy and data retention settings in force when the package was generated. Settings only, never a key.

The package carries no telemetry, prompts, responses or credentials. People can be named in it in two ways:

  • By user id: who generated the package, who took each finding step, who led, started and reported each internal audit, who attended, held and signed off each management review. These are Shield360’s own users, named by id only.
  • In text people typed, which may name anyone:
    • risk-register.json, risk-register.csv, and the risk table in report.html and report.pdf (title and owner only): each risk’s title, description, owner, treatment plan and acceptance rationale.
    • findings.json, findings.csv and the findings table in report.html and report.pdf: each finding’s title, description, owner, approver and the text recorded at each step.
    • internal-audits.json, internal-audits.csv and the audit table in report.html and report.pdf: each audit’s title, scope, criteria, external lead auditor’s name, independence reason, results and conclusion notes.
    • management-reviews.json, management-reviews.csv and the review table in report.html and report.pdf: each review’s title, external attendees’ names and reviewers’ notes, and each action’s description and owner.
    • ai-requests.json, ai-requests.csv and the AI requests table in report.html and report.pdf: each request’s purpose, use case, users, justification, question and answer, owner, conditions and rejection reason. The requester, reviewer and each step’s actor are user ids.
    • impact-assessments.json: each assessment’s purpose, context and notes, harm notes included.
    • statement-of-applicability.json, ai-inventory-summary.json, ai-inventory.csv: owners, justifications, reviewers and other recorded fields, as their exports have them. Never the names of the people or machines using an AI asset, only how many.

The cover of report.html and report.pdf also names the organisation, project and environment.

A package an Auditor generates follows the same rules and holds the same files for their one environment; only the audit-log counts are narrowed to that environment. Erasing a person clears their name from these records, so packages generated afterwards no longer carry it; packages already downloaded are outside Shield360.

report.pdf is drawn from the same content as report.html, so the two always agree. It has:

  • A cover with the organisation, project and environment, the framework and its version, the period, when the package was generated and by which user id, the package id, whether the package is signed and with which key id, and the “not a certification” statement.
  • A table of contents with the page each section starts on. Each entry links to its section, and the sections are also bookmarks.
  • The sections of report.html, in its order: controls, the AI risk register, findings, internal audits, management reviews, AI requests and approvals, the AI inventory, audit-log counts and the files in the package.
  • A footer on every page with the package id and “Page N of M”.

Tables wrap long text and repeat their header row on every page they run onto. A status is always written as a word, such as Met or Gap; the coloured square beside it only repeats it. Tables stop at the same row limits as report.html: 200 risks, 500 findings, 500 internal audits, 200 management reviews, 500 AI requests and 100 audit-log actions. When rows are left out, the report says so and names the CSV or JSON file that has them all. A single cell shows at most 2,000 characters.

The PDF is set in Noto Sans, embedded in the file, which covers Latin-1 and Latin Extended-A. A letter with an accent the font lacks is shown without the accent, and any other character it cannot show, such as Chinese, Hebrew or an emoji, is shown as ?. The JSON and CSV files keep the exact text.

The PDF has no scripts, form fields, attachments or links to the internet, and loads nothing when it opens. Text people typed is drawn as text. report.pdf is listed in manifest.json and SHA256SUMS like every other file, so the signature covers it.

Download PDF report on the Evidence package panel returns only report.pdf, for the same environment, framework and period. It needs the same permission as a package, and an Auditor’s PDF follows the same rules as their package. To fill in the PDF, Shield360 generates and records a complete package, so the package id in the PDF’s footer is a recorded package. The download is written to the audit log as evidence.report.download, with the PDF’s SHA-256.

The API takes format=pdf:

Terminal window
curl -H "Cookie: <session>" -o evidence-report.pdf \
"https://<your-shield360>/api/governance/evidence-package?framework=iso_42001&from=2026-07-01&to=2026-09-30&format=pdf"

The response’s X-Shield360-Evidence-Package header names the package, and X-Shield360-Evidence-Report-Sha256 gives the SHA-256 that package’s manifest lists for report.pdf. A PDF downloaded alone cannot be checked against the signature, because the manifest is not included. To hand an auditor something they can verify, download the package.

Verification needs only OpenSSL 3 (or Node.js) and the public key. It does not need an account or the Shield360 verify page.

Get the public key

Open https://<your-shield360>/api/evidence/keys. It lists every evidence signing key with its key id: the current key and any earlier ones kept after a rotation. Save the key whose kid matches signature.keyId in manifest.json as key.pem:

Terminal window
KID=$(jq -r .signature.keyId manifest.json)
curl -s "https://<your-shield360>/api/evidence/keys" \
| jq -r --arg kid "$KID" '.data.keys[] | select(.kid == $kid) | .publicKey' > key.pem

Do not rely only on public-key.pem inside the package: anyone who could change the package could change that file too. Compare it with the published key, or get the key from your contact at the organisation.

Check the signature
Terminal window
unzip evidence.zip -d evidence && cd evidence
openssl pkeyutl -verify -pubin -inkey key.pem -rawin \
-in manifest.json -sigfile manifest.sig

OpenSSL prints Signature Verified Successfully. Any change to manifest.json, even one space, makes the check fail.

Check every file
Terminal window
sha256sum -c SHA256SUMS
sha256sum SHA256SUMS

Each file prints OK. The hash of SHA256SUMS itself must match its entry in manifest.json. On macOS, use shasum -a 256 in place of sha256sum.

This Node.js script does all of it in one step:

// node verify-evidence.mjs <unzipped package folder> <key.pem>
import { createHash, createPublicKey, verify } from "node:crypto";
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
const [dir, keyFile] = process.argv.slice(2);
const manifestBytes = readFileSync(join(dir, "manifest.json"));
const manifest = JSON.parse(manifestBytes.toString("utf8"));
let ok = true;
if (manifest.signature.signed) {
const signature = readFileSync(join(dir, "manifest.sig"));
const valid = verify(null, manifestBytes, createPublicKey(readFileSync(keyFile)), signature);
console.log(`signature (${manifest.signature.keyId}): ${valid ? "valid" : "INVALID"}`);
ok &&= valid;
} else {
console.log("signature: the package is unsigned");
}
const listed = new Set(manifest.files.map((f) => f.path));
for (const file of manifest.files) {
const data = readFileSync(join(dir, file.path));
const match = createHash("sha256").update(data).digest("hex") === file.sha256 && data.length === file.bytes;
if (!match) console.log(`CHANGED: ${file.path}`);
ok &&= match;
}
const own = ["manifest.json", "manifest.sig", "signature.json", "public-key.pem"];
for (const name of readdirSync(dir)) {
if (!listed.has(name) && !own.includes(name)) {
console.log(`NOT LISTED: ${name}`);
ok = false;
}
}
console.log(ok ? "The package checks out." : "The package does NOT check out.");
process.exit(ok ? 0 : 1);

https://<your-shield360>/evidence/verify checks a package without signing in. Upload manifest.json and manifest.sig (or signature.json). The page reports:

  • whether the signature is valid under the key the manifest names;
  • whether this server generated a package with that id and exactly that manifest (known), has no record of it (unknown), or generated one with that id but a different manifest (changed).

File contents are not uploaded. Check them with sha256sum -c as above. Nothing you upload is stored.

The page calls a public API:

Terminal window
curl -X POST -H "Content-Type: application/json" \
-d "{\"manifest\": $(jq -Rs . < manifest.json), \"signature\": \"$(base64 < manifest.sig | tr -d '\n')\"}" \
"https://<your-shield360>/api/evidence/verify"

Malformed JSON, a body without a manifest, or a manifest that is not an evidence package manifest returns 400. A body over 2 MB returns 413, however it is sent.

When no signing key is configured, packages are still produced with the SHA-256 of every file. manifest.json has "signed": false. The README, the report and the panel on Govern → Compliance program → Framework controls all say the package is unsigned. An unsigned package can be checked only against this server’s record of its manifest hash, so configure a signing key before you hand packages to an auditor.

The evidence signing key is its own Ed25519 key. It is not the licence signing key, which never belongs on a server.

  1. Generate the key pair on a trusted machine:

    Terminal window
    openssl genpkey -algorithm ed25519 -out evidence-signing-key.pem
    openssl pkey -in evidence-signing-key.pem -pubout -out evidence-public-key.pem
  2. Store the private key in your secret store, for example a Kubernetes secret, and set these environment variables on the Shield360 server:

    VariableValue
    SHIELD360_EVIDENCE_SIGNING_KEYThe private key: the PKCS#8 PEM, or that PEM base64-encoded onto one line (base64 < evidence-signing-key.pem | tr -d '\n').
    SHIELD360_EVIDENCE_SIGNING_KEY_IDA key id of your choosing, such as 2026-10. Letters, digits, ., _ and -.
    SHIELD360_EVIDENCE_PUBLIC_KEYSOptional. Earlier keys’ public halves, as JSON: [{"kid":"2026-09","publicKey":"-----BEGIN PUBLIC KEY-----\n…"}].
  3. Restart the server. The panel on Govern → Compliance program → Framework controls shows the key id, and /api/evidence/keys publishes the public key.

A key that cannot be read, is not Ed25519, or has no valid key id is treated as no key. The server logs the variable’s name, never its value.

  1. Generate a new key pair.
  2. Add the old key’s public half to SHIELD360_EVIDENCE_PUBLIC_KEYS with its old key id.
  3. Set SHIELD360_EVIDENCE_SIGNING_KEY and SHIELD360_EVIDENCE_SIGNING_KEY_ID to the new key and a new key id.

New packages are signed with the new key. Packages signed with the old key still verify, because its public key is still published under its key id.

An Auditor is an external person who can read one environment’s compliance evidence, and nothing else, until a date you choose.

Can readCannot see or do
Govern → Compliance program → Framework controls, including the Statement of Applicability and daily historyCosts, budgets and pricing
The AI risk registerTelemetry, requests, prompts and responses
Findings and corrective actions, with each finding’s timelineRaise, change or close a finding, or see suggested findings
AI impact assessments, with their facts and history, and their PDF or HTMLStart, change, submit or approve an assessment
Internal audits and management reviews, with each review’s input packPlan, conclude or report an audit; create, hold or sign off a review; change their settings
AI requests, with each request’s timeline and risk view, by user idSubmit, change, review or decide an AI request
The AI inventory list, with counts of people and machinesAn asset’s page or the inventory export, which name people and machines and carry spend; usage figures
The audit log, narrowed to records about their environmentAPI keys, the vault and install keys; billing and the subscription
Evidence packages: generate and downloadMembers, roles and organisation settings
Change anything, including the Statement of Applicability
Open Organisation → Members

The Auditors section is below the members list.

Enter the auditor's email, environment and end date

The end date is required and can be at most 180 days away. Access ends at the end of that day (UTC).

Select Invite auditor

Someone who already has an account is added at once. Anyone else is invited and becomes an Auditor when they sign up or accept the invitation. An invitation accepted after its end date gives no access.

The invitation is written to the audit log with the environment and end date. The auditor’s email address is not. An organisation can have up to 10 Auditors with access at a time, invitations included; the limit holds even for invitations sent at the same moment. An existing member cannot also be an Auditor, and an identity-provider group mapping never gives the Auditor role.

An Auditor who signs in lands on Govern → Compliance program → Framework controls.

The API takes the same fields:

Terminal window
curl -X POST -H "Cookie: <session>" -H "Content-Type: application/json" \
-d '{"email":"auditor@firm.example","databaseConfigId":"<environment id>","expiresOn":"2027-01-31"}' \
"https://<your-shield360>/api/organisation/auditors"

Malformed JSON, a missing or invalid email, or an end date that is in the past or more than 180 days away returns 400. An environment of another organisation returns 404. An existing member, an existing invitation, or more than 10 active Auditors returns 409.

The server enforces access on every request, not only in the interface:

  • An Auditor holds exactly the Auditor role’s read-only permissions, whatever role or project role their membership carries. Their role cannot be changed, and the Auditor role cannot be given to a member.
  • Routes that check no permission treat an Auditor as signed out unless the route is one of the evidence routes above.
  • Every request reads the Auditor’s own environment, whatever environment the request names.
  • When the end date passes, the Auditor’s permissions are removed on their next request. They can still sign out and switch to another organisation they belong to. The members list shows them as expired. Their first request after the end date writes the expiry to the audit log. Each day an Auditor reads evidence, their first access that day is written to the audit log.

To end access early, remove the Auditor from the members list. To extend it, remove them and invite them again with a new end date.

Auditors do not use a seat. Access is read-only, limited to one environment, at most 180 days long, and at most 10 Auditors at a time. Someone who is both an Auditor in one organisation and a member of another counts once, as a member.