Auditor
Encryption uses a recognised protocol and vetted primitives; no custom ciphers, custom key schedules or ECB.
1.0.9: the SCOPE rule of 1.0.7 is repeated a second time with named out-of-scope examples (service/session authentication tokens — a chat attachment stays IN scope) and a service-token file is now excluded at the deterministic layer, the same defence in depth already given to KMS/webhook code; a rule against reporting a freshly-generated key or IV as "reused" without caching evidence was added too (1.0.8: SPEED PASS: each file goes to the model as the EXCERPT around the lines the deterministic part marked relevant to THIS requirement (the choice of primitive and mode, key derivation, nonces and randomness, the protocol library, the home-made constructions), 12 lines on each side (the whole file when that is most of it); the omitted spans are marked in place and a finding on a line that was not sent is dropped. A cryptographic file with none of them falls back to its generic crypto lines, so none is dropped. Up to 48 files, most relevant first, instead of 24 whole ones. STABLE PARTS: the files are packed by path with content-defined part boundaries (decided by the path, never by the content) and the repository context lists no files, so an unchanged file lands in a byte-identical part the engine may answer from its verdict cache (per organisation, declared in the trace and in the attestation). The parts are sent in parallel up to what the engine admits (ctx.ai.maxConcurrentCalls) and aggregated in part order, so the verdict never depends on which answer arrives first. The assessment reason is ONE sentence of at most 30 words (the platform GPU spends 46 ms per output token and the reason was most of every answer). Requirement, categories and decision rule unchanged. (1.0.7: the rubric did not bound the judgement to the encryption of MESSAGES: a helper encrypting stored secrets at rest (signing keys, credentials, tokens) with AES-256-GCM from node:crypto was assessed as if it were the messaging protocol. The assessment is now scoped to message content, message keys and their sessions, and AEAD from node:crypto with random nonces and HKDF keys is named as a vetted construction. (1.0.6: a cryptographic file used to be judged by this check as soon as the checkout had a messaging feature ANYWHERE, even when the crypto file itself has nothing to do with messaging — an unrelated internal signing or envelope-encryption service that shares the repository with a real chat feature was assessed as if its primitives were the messaging feature's own encryption. A cryptographic file with STRONG evidence of being operational infrastructure (envelope encryption of secrets at rest under a KMS-managed key, or signing/verifying a machine-to-machine handshake or a third-party webhook) is no longer judged unless it also addresses a person (recipient, sender, participant) — general vocabulary any codebase uses for this, never a name of one particular repository or file. (1.0.5: NAMING a home-made construction is not implementing one. The custom-cipher rule matches NAMES (caesar, rot13, vigenere, xorCipher, myEncrypt, homemade, homebrew) and a name is reported only where it is CODE: a bare identifier that is declared, called or referenced, a quoted name in an ALGORITHM POSITION (the argument of a cipher/encrypt/decrypt call, or the value of an algorithm, cipher, mode or transformation field) or the module of an import or require. A name inside a hyphenated slug — never an identifier in JavaScript, TypeScript, Python or PHP — and a quoted name anywhere else (an entry of a catalogue or a security policy, the id of a rule or a check, a key of a map, the prose of a message) is the construction being MENTIONED, is not reported, and the trace names the file, the line and why: a checkout that DESCRIBES the defect does not have it. The other seven rules (ECB, createCipher, obsolete ciphers, weak randomness, hand-written XOR, static IV) and the model part are unchanged. (1.0.4: `assess` mode cuts the candidate set to what the chosen ENGINE serves in ONE call (`ctx.ai.maxInputChars`, published by the engine per its catalogue budget; `ENGINE_CALL_MAX_CHARS` = 32 000 when an older sandbox does not publish it), instead of filling the call up to the TRANSPORT cap of the proxy (`spec.promptMaxChars`, unchanged at 120 000). On a 1 319-file checkout the old packing produced single prompts of 17 672 / 26 709 / 29 351 tokens and the platform GPU answered "CUDA out of memory": the check ended in `error`. Every call of the same run under ~24 000 characters was answered normally. The part aggregation of 1.0.1 is unchanged: a HIGH in any part is a FAIL, an unsatisfied part is a FAIL, undecided never passes. (1.0.3: requires an AI engine (spec.requiresEngine: true): the check needs judgement and is executed only when the organisation provides an engine; the core catalogue runs without one. (1.0.2: applies only to a real messaging feature: a message or conversation model, a chat transport (socket, channel or pub/sub events of a conversation), a messaging protocol library (Signal, MLS, Olm/Megolm, XMPP) or a chat UI; messaging vocabulary alone (an error message pushed to a queue, a notification channel, a case signal route) is not-applicable with the reason; never analyses temporary files (*.tmp.*; spec.excludes follows AUDITOR_ANALYSIS_EXCLUDES). (1.0.1: never analyses test, spec, fixture and mock paths nor the auditor's own scripts (spec.excludes = AUDITOR_ANALYSIS_EXCLUDES); a fixture-looking secret (sk_test_, example, dummy) in real code is LOW, informative; every model call stays under the engine prompt cap (spec.promptMaxChars = AUDITOR_AI_PROMPT_MAX_CHARS): assess mode sends the candidate set in parts and aggregates the verdicts (a HIGH in any part is a FAIL, an unsatisfied part is a FAIL).))))))))
| { re: /\b(?:aes|AES)[-_ ]?(?:128|192|256)?[-_ ]?(?:ecb|ECB)\b|MODE_ECB|['"]ecb['"]|AES\/ECB/i, what: 'ECB mode' }, // -> FAIL |
| { re: /Math\.random\s*\(\)[^\n]{0,80}\b(?:key|nonce|iv|salt|secret)\b/i, what: 'non-cryptographic randomness for a key, nonce or IV' }, // -> FAIL |
| { re: /\b(?:caesar|rot13|vigenere|xorCipher|myEncrypt|homemade|homebrew)\b/i, what: 'custom cipher', nameOnly: true }, // a NAME: only where it is CODE |
| // naming is not implementing: a hyphenated slug is no identifier, and a quoted name outside an algorithm or import position is a catalogue entry, a rule id or prose |
| const ALGORITHM_POSITION = /(?:cipher|decipher|algorithm|algo|\bmode|transformation|encrypt|decrypt|crypt|digest)\w*\s*(?:\(|=>|[:=]\s*)\s*$/i; |
| const RECOGNISED_PROTOCOL = /libsignal|SessionCipher|X3DH|DoubleRatchet|openmls|@matrix-org\/olm|vodozemac|megolm|openpgp|libsodium|tweetnacl|@noble\/|crypto\.subtle|cryptography\.hazmat|PyNaCl/i; // required |
The full script is disclosed on request in a read-only viewer (never published on GitHub); the attestation binds to this exact hash.