Files
claim-check/README.md
T
Paul HittandClaude Opus 5.5 489e14c2c4 Publish to the GitLab package registry on version tags
The README's install line failed: the package was never published, and
nothing told npm where the @extant2000 scope lives. A tag like v1.2.3 now
publishes to this project's registry with CI_JOB_TOKEN, and the README
sets the scope's registry before installing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 16:41:45 -04:00

101 lines
4.0 KiB
Markdown

# ClaimCheck
Checks the cryptographic claims a system makes, such as "end-to-end
encrypted", against the keys, ciphers and transport settings it actually
uses.
## What it does and why
A common pattern: a feature is described as encrypted, and it is. AES-256-GCM,
correctly applied, ciphertext at rest. It is still not end-to-end encrypted,
because the server holds the key. Every cryptographic choice is sound, the
claim is false, and no review of the algorithm would catch it.
That asymmetry is what this library is built around. **Cipher strength is the
easy half and rarely the part that is wrong. The part that goes wrong is key
custody: who can read the plaintext.** A claim of "end-to-end" or
"zero-knowledge" is a claim about custody. An operator holding the key
falsifies it no matter how strong the cipher is.
In the case above, ClaimCheck fails the end-to-end claim and names who holds
the key. It passes the at-rest claim, because that one is true. Only the
claim that goes beyond its custody fails.
## What it checks
| Claim | Checked against |
|---|---|
| `end-to-end-encrypted` | Key custody. Fails if `server`, `operator` or `third-party` holds any key. |
| `zero-knowledge` | The same custody rule. |
| `encrypted-at-rest` | An at-rest cipher exists, and it is not broken and not ECB. |
| `encrypted-in-transit` | A pinned minimum TLS version of 1.2 or higher. |
| `passwords-hashed` | A password KDF (bcrypt, scrypt, argon2, PBKDF2), not a fast hash and not reversible encryption. |
| `forward-secrecy` | Every listed cipher suite uses an ephemeral (ECDHE or DHE) key exchange. |
Broken primitives (MD5, SHA-1, DES, 3DES, RC4, any ECB mode) are reported
even when nothing was claimed about them. Not advertising a property is no
reason to leave MD5 in a signature path.
A key held only in an `hsm` does not break a custody claim, because the
operator cannot read it. A `third-party` holder does: "end-to-end with a
vendor in the middle" is the marketing sense of the phrase, not the
technical one.
## Missing evidence is not a pass
A claim with nothing in the inventory to support it is reported as NOT
ASSESSED. "No key custody was described" is not evidence that custody is
safe. It only shows that custody was not described. The same applies to a
TLS setup with no pinned minimum version: the result would depend on a
runtime default that can change, so it is not assessed.
The library is pure. It takes a description of your system and returns
findings, with no filesystem access, network calls or crypto calls. The
output is in the `@extant2000/evidence-record` format, and every conclusion
cites the file or location it rests on.
## Usage
```ts
import { checkClaims, formatClaims, type CryptoInventory } from '@extant2000/claim-check'
const inventory: CryptoInventory = {
claims: ['end-to-end-encrypted', 'encrypted-at-rest', 'passwords-hashed'],
keys: [{
keyId: 'NOTES_KEY',
heldBy: ['server', 'operator'], // list every party that can obtain it
location: 'config/app.env',
protects: 'note bodies',
}],
ciphers: [
{ algorithm: 'AES', mode: 'GCM', keyBits: 256, purpose: 'at-rest', where: 'src/notes/store.ts', line: 88 },
{ algorithm: 'argon2id', purpose: 'password', where: 'src/auth.ts', line: 30 },
],
transport: [{ minTlsVersion: '1.2', where: 'config/tls.yml' }],
}
const report = checkClaims(inventory) // structured report
console.log(formatClaims(inventory)) // the same report as text
```
`heldBy` decides the custody checks, and an optimistic answer defeats the
library. If the operator can read the environment variable that holds the
key, list `operator`.
For this inventory the end-to-end claim fails at critical severity, citing
`config/app.env`, while the at-rest and password claims pass.
## Install
The package is published to the GitLab package registry, so point the
`@extant2000` scope there first:
```
npm config set @extant2000:registry https://gitlab.com/api/v4/packages/npm/
npm install @extant2000/claim-check
```
## License
MIT. See [LICENSE](LICENSE).