What verification does not prove

The questions a passing verification deliberately leaves open.

A Pentr evidence locker answers one question: has this evidence changed since it was signed. Most of what an auditor weighs is outside that question. This page sets out which parts.

An unpinned check says nothing about who signed

Run without --key, the verifier falls back to the public key packaged inside the locker. Every structural check still runs, but the key being checked against arrived with the evidence. Anyone who can modify a locker can re-sign it with a key of their own, bundle that key, and produce a locker that verifies unpinned.

An unpinned PASS means internally consistent. A pinned PASS means internally consistent and signed by the key you pinned. Only the second is a statement about provenance, and it is only as good as the channel you obtained the key through. See verifying a locker for how the pinning works.

The verifier is published separately from the locker

pentr-verify is open source under the MIT licence, at github.com/PentrAI/pentr-verify. You can obtain it from the repository instead of from the organization whose evidence you are checking, and read its source before you run it.

The checks it performs are standard — SHA-256 digests and an Ed25519 signature over a canonical JSON block — so they can also be reimplemented by anyone unwilling to take any supplied code on trust. Whichever implementation you use, a PASS shows that the evidence is unchanged since signing, not who signed it.

A PASS is about the bytes, not about the test

Verification recomputes hashes and checks a signature. Nothing in that procedure inspects whether the test was well conceived, thorough, or finished.

A run that stopped early seals the same way a run that finished does. The run is carried out by an automated agent, and that agent stops after five consecutive failed steps; it also stops at 500 steps whether or not the work is done. In both cases the run loop exits normally, the agent process emits its result payload and exits cleanly, and a clean exit with a payload is the entire condition for sealing.

An extreme case is reachable: if the declared in-scope and out-of-scope lists overlap, the rules-of-engagement step raises before the run advances out of its first phase, and it raises again on every retry until the five-failure abort fires. The agent then exits cleanly with an empty result, and the run is sealed over an evidence set containing nothing. The resulting locker is structurally sound and verifies, pinned, as a PASS.

The presence of a valid seal is therefore not evidence that the test ran to completion, or that it ran at all. It is evidence that whatever the run produced has not been altered since it was signed.

Judging whether a test was competently and sufficiently conducted means reading the evidence: the commands issued, the responses received, the phases they came from. The locker preserves all of that faithfully. It does not assess it.

What the locker lets you check for yourself

Three things in a verified locker carry information the verdict does not.

entry_count in the manifest — reported as artifacts in the verifier's summary — is the number of sealed artifacts. Zero is the empty case above: a locker that verifies and seals no evidence.

The chain head of a run with no entries is the chain's starting value, and you can compute that yourself. It is the SHA-256 of the ASCII string pentr-evidence-locker-v1| followed by the run id. If the chain head in the output equals that value, the locker seals nothing.

Each manifest entry records the phase that produced it. Evidence comes from the agent's six working phases — intelligence, threat_modeling, vuln_analysis, exploitation, post_exploitation and reporting — so the spread of phases across the entries tells you which parts of the run produced evidence and which produced none. The run block also records started_at and ended_at, which gives you the wall-clock span.

What no part of a locker records is why the run ended. The manifest carries a version, an algorithm, the chain's starting value, the run block, the entries, the chain head and the entry count. The unsigned seal header adds the hashes, the algorithms, the key identifier and the seal, signature and export times. Neither carries a termination reason, a step count, or any statement that a phase completed. A run cut off at a ceiling and a run that finished its work produce lockers of the same shape.

Scope is bound as a digest of a declaration

The manifest carries a scope_digest: the SHA-256 of the declared in-scope list, the declared out-of-scope list and the rules of engagement, in a canonical serialisation. Sealing that digest fixes what was declared at the time of the run, so the declaration cannot be rewritten afterwards without breaking the signature.

It does not establish that the declaration was honest, or that it described the systems you care about. A scope that omits the sensitive half of an estate produces a digest as sound as one that does not.

Two practical consequences follow for anyone holding only a locker. The scope text is not in the archive — only its digest is — so the locker will not tell you what was in scope. And recomputing the digest from a scope statement you have been given separately requires the exact lists and rules dictionary in the exact canonical form, not a prose description of them.

The absence of a locker means nothing in particular

A run is sealed only after the agent exits cleanly and returns a result payload. A run that fails, crashes, or produces no payload is never sealed, and an unsealed run has no locker to export or share.

Sealing can also fail on its own terms. If the platform's key-encryption key is missing or any signing step fails, the run is deliberately left unsealed rather than half-sealed, and no path in the shipped system retries it. That run has no locker either.

A locker is therefore evidence about the run it covers. Its absence rules out one thing only — that this run was sealed successfully — and that is consistent with a run that failed, a run that finished and then failed to seal, and a run that never happened. It supports no inference between them.

The evidence set is not necessarily everything that happened

Artifacts are capped at 10 MiB each. An artifact over the cap is rejected at the moment it is produced, before it is emitted, so it is dropped whole rather than truncated — nothing partial enters the record and no placeholder marks the gap. The same limit applies again on the storage side.

The step that produced the oversize artifact then fails and is abandoned, and it counts toward the five-consecutive-failure abort. Nothing in a verified locker indicates that this happened.

In scope is enforced on a best-effort parse

Before a command runs, a guard splits it, pulls out well-formed IPv4 addresses and hostnames, and checks them against the declared lists. When it finds no target it can identify, it allows the command — the case it is written for is the local command with no target at all, such as whoami.

That default is permissive by design, and it means "every command was in scope" is not a claim the system can make. What it can say is that every command whose target the parser recognised was checked against the declared scope, and that recognition is textual. A command whose target the parser cannot see is a command the guard does not constrain.

The guard is also the whole of the enforcement. The agent runs in a container that the scheduler starts without any network argument, so it gets Docker's default bridge and unrestricted outbound access; there is no network-level check sitting behind the guard's decision. That describes where the boundary is drawn. It is not a claim that runs leave their declared scope — the guard blocks what it recognises, and it recognises a good deal. It means that when the parse recognises nothing, nothing further catches it.

The agent is not given credentials

The interface for creating an engagement accepts a credentials field, and no part of the product uses it. It is not written to the engagement record, it is not placed on the job the scheduler consumes, and the container environment variable that would carry it is never set. The agent's configuration therefore always holds an empty credentials list, and the rules-of-engagement document it renders shows none.

For reading a locker this means the run started with no credentials supplied. Authenticated access visible in the evidence is access the agent obtained during the run — a default password, a credential it recovered, a session it captured — not access it was handed. Surface reachable only with credentials the agent could not obtain for itself was not tested, so a locker covering such a system is evidence about its unauthenticated surface.

Control identifiers are pointers, not verdicts

A control identifier beside a finding says the finding carried an ATT&CK or WSTG id, or in a report matched a keyword pattern, that the framework's tables map to that control; Frameworks explains what each case tells you. It is not a judgement that the control is met, failed, or adequately covered. In Pentr's own reporting a control is marked tested or not_tested according to whether any finding mapped to it. There is no passed and no failed.

The mapping is small and mechanical. Each of the six frameworks declares a table of ten ATT&CK techniques and twelve WSTG identifiers, and the control identifiers sealed into a locker's manifest come from those two tables alone. A finding carrying identifiers that are not in its framework's tables, or carrying none at all, contributes no control identifiers to the manifest. An empty identifier list is an ordinary, accepted case, not a malfunction.

Pentr's generated reports additionally fall back to matching a finding's text against keyword categories when the direct tables produce nothing. That fallback is a normal path there, and it is a regex over prose. It does not feed the locker.

Treat a control crosswalk as a starting point for your own control review.

Key custody is software custody, and the timestamp is self-asserted

The signing key is an Ed25519 key belonging to the organization, held by the Pentr platform and encrypted at rest. The only signer implementation that ships signs with that key in process. The platform is therefore technically capable of signing on the organization's behalf. A hardware- or KMS-backed signer is an anticipated upgrade and is not implemented. The locker's own README says so.

signed_at is the platform's clock at the moment of signing, recorded inside the signed bytes. It is not countersigned by a timestamping authority. A signature proves the key signed those bytes; it does not independently establish when.

The other two timestamps in seal.json are weaker still. sealed_at and exported_at sit in that unsigned header and are checked against nothing during verification. Rewrite either one and the locker still verifies, pinned, as a PASS. Neither is evidence of when the run was sealed or when the archive was produced; both are the platform's unverified assertion.

Not every file in the locker is covered by the signature

The signature covers signature_block.json, which in turn binds the manifest's hash and the chain head. seal.json is an unsigned header. Nine of its eleven fields are cross-checked against something the header does not control — eight against the signed material, and format against the version string compiled into the verifier — so a lie in any of the nine is reported rather than believed.

README.txt is neither signed nor cross-checked. It is explanatory text that travels with the archive. Read it as a convenience, and take nothing from it that you would not take from an unsigned note.

The locker holds evidence, not findings

An exported locker contains the manifest, the signature material, the public key, the seal header, the README and the artifact blobs. The written findings, their severities and their remediation advice live in the report, which is a separate document and is not in the archive. Manifest entries reference findings only by bare identifier.

Verifying a locker tells you that the captured evidence is intact. Matching that evidence against the claims in a report is a step you take yourself.

Nothing here is an attestation

Pentr produces evidence and reports. Auditors produce attestations. No output of this system is a SOC 2 report, an ISO 27001 certificate or an ENS conformity declaration, and none should be presented as one. A verified locker is supporting material for your work, like any other authenticated system export.

Last updated 2026-10-21