diff --git a/docs/regulated/README.md b/docs/regulated/README.md new file mode 100644 index 0000000..0decb16 --- /dev/null +++ b/docs/regulated/README.md @@ -0,0 +1,73 @@ + + +# Trinity for a regulated lab: what this pack is, and what it is not + +For an ISSM or an assessment team deciding whether Trinity can run inside an accredited boundary. +It is written to be checked, not believed. Every claim below names a path in this repository, and +anything without one says **NOT IN TREE**. + +## Read this part first + +**Trinity is customer-controlled software. It is not a cloud service and this project is not a CSP.** +You run the binary, on your host, in your boundary. There is no Trinity-operated control plane, no +Trinity tenancy, and no Trinity staff with access to your data. That is what makes the rest of this +pack short: most of the controls an assessor asks about are yours, because the software is yours to +run. + +**Trinity is not FedRAMP authorized.** There is no FedRAMP package, no 3PAO assessment and no agency +ATO for this software. A FedRAMP authorization is a property of a service offering; this is not one. + +**Trinity is not CMMC certified.** There is no C3PAO assessment of this project. CMMC certifies an +organization's handling of CUI, not a library or an application, so the certifiable thing is your +lab, not this repository. + +**This pack is not a HIPAA attestation.** No SOC 2, no HITRUST, no third-party audit of any kind has +been performed on this software. Sudo Apt Holdings LLC is not a Business Associate by virtue of you +running this binary, and nothing here should be read as offering a BAA. + +**NIST has not evaluated, certified or endorsed anything in this repository.** Where this pack cites +a CMVP certificate it is the certificate of an operating system's cryptographic module, named so you +can check it against your own running binary, and `docs/fips-leg.md` says in terms that Trinity does +not claim the binaries are the same. + +## What the project does claim + +Three things, each of which an assessor can verify from the tree rather than from this page. + +1. **Every governed action is recorded in a hash-linked, signed chain**, and the chain can be + verified by a reader who does not trust the program that wrote it. The verifier is + `lib/trinity/receipts/verifier.ex`; the task is `lib/mix/tasks/trinity.receipts.verify.ex`; + a standalone script that needs no application is `bin/verify_receipt.exs`. See + `receipts-verify.md` in this directory. +2. **The set of code paths that can cause an effect is closed**, and that closure is asserted by a + census over the tree rather than by intent. The boundary is `lib/trinity/effects.ex`; the + published population is `docs/effects-catalog.md`. +3. **Standalone, Trinity decides, acts and records by itself**, which is a specific and limited kind + of evidence. `authorization-boundary.md` in this directory states the limit rather than leaving + it to be discovered. + +## What this pack does not decide for you + +Whether Trinity may process CUI or PHI in your boundary is your determination, not this project's. +`customer-responsibility-matrix.md` sets out which controls are yours, which are the software's, and +which belong to the host or the cloud account underneath it. `data-flow.md` states what may enter a +model prompt and what may not. Read both before the others. + +## The files + +| File | What it answers | +|---|---| +| `authorization-boundary.md` | What is inside the boundary, what is outside, and where the trust ends | +| `customer-responsibility-matrix.md` | Who owns each control: the software, you, or the host | +| `data-flow.md` | What may enter a prompt, and what must never leave the boundary | +| `incident.md` | Reporting clocks and who to contact | +| `receipts-verify.md` | How an assessor verifies the record without trusting the application | +| `crypto-inventory.md` | Every cryptographic capability, its module, and its certificate status | + +## Status of this pack + +This directory is documentation only. It adds no code, changes no behaviour, and asserts no control +that is not already in the tree. Where the tree does not implement something this pack names, the +row says **NOT IN TREE** rather than describing an intention. + +This pack documents the gap; it does not close it. diff --git a/docs/regulated/authorization-boundary.md b/docs/regulated/authorization-boundary.md new file mode 100644 index 0000000..964d260 --- /dev/null +++ b/docs/regulated/authorization-boundary.md @@ -0,0 +1,103 @@ + + +# Authorization boundary + +What sits inside the accredited boundary when Trinity runs, what sits outside it, and the one thing +about the standalone configuration an assessor has to understand before anything else in this pack +is useful. + +## The boxes + +``` + ACCREDITED BOUNDARY (yours) + +--------------------------------------------------------------+ + | | + | [ Trinity process ] [ Receipts store ] | + | BEAM/OTP application hash-linked signed chain | + | permission gate, effect own SQLite file, own | + | membrane, session loop connection pool | + | | + | [ Customer files ] | + | the filesystem roots you | + | grant, and nothing else | + | | + +--------------------------------------------------------------+ + | | + | prompt + completion | OIDC / OAuth 2.1 + v v + [ Model endpoint ] [ Customer IdP ] + OUTSIDE the boundary OUTSIDE the boundary +``` + +| Box | Inside the boundary? | Path | +|---|---|---| +| Trinity process | Yes. Runs on your host, under your accounts | `lib/trinity/application.ex` | +| Receipts store | Yes. Its own database file, separate from the primary | `lib/trinity/repo/receipts.ex`, `lib/trinity/paths.ex` | +| Customer files | Yes. Only the roots you grant | `lib/trinity/tools/fs/` | +| Model endpoint | **No.** Whatever you point it at, wherever it runs | `config/llm.exs:14` (the model list) | +| Customer IdP | **No.** Yours, and Trinity is a resource server to it | `lib/trinity/mcp/auth/`, `config/runtime.exs:100` | + +Trinity holds a single-instance lock on the data directory so two processes cannot share one store: +`lib/trinity/data_dir/lock.ex`. + +## Standalone: judge, actor and scribe + +In the default configuration (`TRINITY_AUTHORITY` unset, or `local`) Trinity decides whether an +effect may happen, performs it, and writes the record of having done so. All three roles are the +same process. + +- The decision is `lib/trinity/permissions/`, through `lib/trinity/authority/local.ex`. +- The effect crosses one boundary, `lib/trinity/effects.ex`, which re-derives the fingerprint from + the arguments actually passed before admitting the call. +- The record is `lib/trinity/receipts/chain_writer.ex`, signed before the row is written. + +**What that is worth.** It is strong evidence to a reader who accepts that the host was not +compromised: the chain is hash-linked and signed, the effect population is closed and published, +and the verifier runs without the application. It proves the software did what the record says. + +**What it is not.** There is no separation of duties. A party who does not already trust the host +has only the host's word for it, because the same process made the decision and wrote the record of +the decision. `docs/10-assurance-case.md` states this, and criterion C6 there records that a signing +key in a file proves the records were not altered by anything lacking read access to that file, and +proves nothing about custody of the key. + +**A signed receipt with an untrusted clock is weaker evidence than one with a trusted clock**, and +Trinity's clock is the host's own reading: `lib/trinity/receipts/clock.ex`. A deployment that needs +the time itself to be trustworthy supplies a trusted time source. `docs/09-standards-register.md` +carries that as a real-world dependency rather than a property of this tree. + +## Regulated use requires an external authority + +For a boundary where the decision must not be the machine's to make, Trinity supports pointing +`TRINITY_AUTHORITY` at an adapter that lives outside this repository. When one is in force, Trinity +keeps **no executor** for the effects that adapter governs, and that absence is asserted by a census +over the tree rather than by intent (`lib/trinity/authority.ex`, and the census recorded in slice +027's record). + +**Stated precisely, because the loose version of this sentence is wrong.** The seam is in the tree: +`lib/trinity/authority.ex` is the behaviour, and `lib/trinity/authority/selection.ex` picks an +implementation once at boot from `TRINITY_AUTHORITY` (ADR-0010). **One implementation ships here, +and only one:** `Trinity.Authority.Local` (`lib/trinity/authority/local.ex`), which is the sole +module in the tree that calls a tool's `execute/2` for an effectful tool. Grepping the behaviour +resolves two files and the second is a test fixture (`test/support/authority/adapters.ex`). + +**No adapter other than `Local` ships in this tree.** An external authority adapter is a module in +another repository, and ADR-0008 (`docs/adr/0008-authority-is-an-adapter.md`) states that no slice +in this repository builds one. Nothing in `mix.exs` or `mix.lock` depends on one; an assessor can +confirm that from the dependency list rather than from this page. + +An assessor evaluating a regulated deployment is evaluating that adapter as well as this software, +and this pack says nothing about it. + +## What crosses the boundary, and when + +| Crossing | Direction | Gate | Path | +|---|---|---|---| +| Prompt and completion | Out, then in | Every turn | `lib/trinity/sessions/prompt.ex:45` | +| Tool effects on files | Inside only | Permission gate, then membrane | `lib/trinity/effects.ex` | +| Web fetch and search | Out | Permission gate | `lib/trinity/tools/` | +| MCP server requests | In | OAuth 2.1 resource server | `lib/trinity/mcp/auth.ex` | +| Receipt forwarding | Out, if configured | Optional authority callback | `lib/trinity/receipts/forwarder.ex` | + +A witness or transparency service that would let a third party detect backdated forgery without +trusting the host is **NOT IN TREE**. diff --git a/docs/regulated/crypto-inventory.md b/docs/regulated/crypto-inventory.md new file mode 100644 index 0000000..5cd6eea --- /dev/null +++ b/docs/regulated/crypto-inventory.md @@ -0,0 +1,75 @@ + + +# Cryptographic inventory + +Every cryptographic capability Trinity uses, the module that calls it, and what is underneath. + +**Read the certificate column carefully.** Trinity implements no cryptography of its own. Every +primitive is OTP's `:crypto`, which calls the OpenSSL the host provides. A CMVP certificate belongs +to that provider, on that host, not to this software. **Anything not established from a document is +`UNKNOWN` and stays `UNKNOWN`.** + +## The table + +| Capability | Trinity module | Underlying | CMVP cert | Notes | +|---|---|---|---|---| +| Receipt signing, default | `lib/trinity/receipts/signer/ed25519.ex` | `:crypto.sign(:eddsa, :none, …, :ed25519)`, line 21 | **UNKNOWN** | Ed25519, RFC 8032. `available?/0` line 14 returns false when `:crypto.info_fips() == :enabled`, so it is not selected in FIPS mode | +| Receipt signing, FIPS mode | `lib/trinity/receipts/signer/p384.ex` | `:crypto.sign(:ecdsa, :sha384, …, :secp384r1)`, line 25 | **UNKNOWN** | ECDSA P-384 with SHA-384. Selected when the mode is enabled | +| Receipt signing, opt-in | `lib/trinity/receipts/signer/mldsa87.ex` | `:crypto` ML-DSA-87, FIPS 204 | **UNKNOWN** | Never the default. `available?/0` line 24 checks `:mldsa87 in :crypto.supports(:public_keys)` | +| Receipt hashing | `lib/trinity/receipts/envelope.ex:36` | `:crypto.hash(:sha256, …)` | **UNKNOWN** | Over the DSSE pre-authentication encoding, not the bare payload | +| Merkle tree over a chain | `lib/trinity/receipts/merkle.ex` | `:crypto.hash(:sha256, …)` | **UNKNOWN** | RFC 6962 hashing: distinct leaf and node prefixes, odd nodes promoted not duplicated | +| Blob encryption at rest | `lib/trinity/vault.ex:180` | `:crypto.crypto_one_time_aead(:aes_256_gcm, …)` | **UNKNOWN** | Fresh 256-bit data key per blob, `docs/encryption-at-rest.md` | +| Data key wrapping | `lib/trinity/keys/local.ex:179` | `:crypto.crypto_one_time_aead(:aes_256_gcm, …)` | **UNKNOWN** | The key encryption key wraps the data key beside the ciphertext | +| Key derivation from a passphrase | `lib/trinity/keys/local.ex:280` | `:crypto.pbkdf2_hmac(:sha512, …)`, 600,000 iterations (line 57) | **UNKNOWN** | Only when `TRINITY_KEYS_PASSPHRASE` is set | +| Sub-key derivation | `lib/trinity/keys/local.ex:415` | `:crypto.mac(:hmac, :sha256, …)` | **UNKNOWN** | | +| Key id | `lib/trinity/keys/local.ex:226` | `:crypto.hash(:sha256, "trinity/key-id" <> root)` | Not applicable | An identifier, not a secret | +| MCP request state envelope | `lib/trinity/mcp/server/envelope.ex:49` | `:crypto.crypto_one_time_aead(:aes_256_gcm, …)` | **UNKNOWN** | Slice 061, key in the keys directory | +| Device identifier | `lib/trinity/receipts/clock.ex` | `:crypto.strong_rand_bytes/1` | Not applicable | Random, names nothing about the host | +| JWK thumbprint | `lib/trinity/receipts/signer.ex` | `:crypto.hash(:sha256, …)` | Not applicable | RFC 7638 | +| Canonical JSON | `lib/trinity/receipts/envelope.ex:15` | `Jcs.encode/1` | Not applicable | RFC 8785, not a cryptographic primitive | + +## Why every certificate says UNKNOWN + +Because the honest answer depends on the host, and this repository cannot know it. + +`docs/fips-leg.md` is the only place a certificate number appears in this tree, and it appears with +a qualification that this inventory repeats rather than softens. From `docs/fips-leg.md:53-59`: + +> Red Hat's page, https://access.redhat.com/compliance/fips, read 2026-09-20, lists for Red Hat +> Enterprise Linux 9.8 the module "OpenSSL", version `3.0.7-395c1a240fbfffd8`, CMVP certificate +> #4857, status "Active". The version string the image reports and the version string the page +> lists differ in their build hash. Trinity does not claim they are the same binary, and does not +> claim a validation. What the leg establishes is narrower and exact: the gate and the suite run +> with OTP in FIPS mode against the FIPS provider that this distribution ships and names, with the +> algorithms the mode removes listed below. Whether a deployment's provider is a validated one is +> that deployment's question, answered from the certificate and the running binary, not from this +> file. + +So: **certificate #4857 is the certificate of a Red Hat module**, cited for your convenience in +checking your own host. It is not Trinity's certificate, it does not travel with this software, and +on a different distribution it is irrelevant. + +## What the FIPS leg does establish + +`docs/fips-leg.md`, and the `fips` job in `.github/workflows/gate.yml`: + +- The full gate and test suite run with OTP in FIPS mode, on every push, in a container built from + `ci/fips/Containerfile`. +- The mode is entered by `ERL_AFLAGS="-crypto fips_mode true -eval application:load(crypto)"`. +- The algorithms the mode removes are listed in that document, and the signer selection responds: + Ed25519 reports unavailable in the mode (`lib/trinity/receipts/signer/ed25519.ex:14`) and P-384 is + selected instead. + +That is a statement about behaviour under a mode. It is **not** a validation, and no part of this +repository should be cited as one. + +## What an assessor should do with this page + +1. Establish which OpenSSL provider your host actually loads, and its certificate, from the running + binary. Not from this file. +2. Decide whether Ed25519 is acceptable in your boundary. If it is not, run in FIPS mode and the + selection changes by itself. +3. Note that **key custody is not addressed by any row above.** The default is a 0600 file + (`lib/trinity/paths.ex`), the seam for a KMS or HSM exists (`lib/trinity/keys.ex`), and **no KMS + or HSM adapter is in the tree**. +4. Note that **ML-DSA-87 is never selected by default** and requires the linked OpenSSL to carry it. diff --git a/docs/regulated/customer-responsibility-matrix.md b/docs/regulated/customer-responsibility-matrix.md new file mode 100644 index 0000000..74ff00a --- /dev/null +++ b/docs/regulated/customer-responsibility-matrix.md @@ -0,0 +1,78 @@ + + +# Customer responsibility matrix + +Who owns each control. **Trinity software** means this repository implements it and a path is named. +**Customer** means your lab owns it and Trinity cannot do it for you. **Host or AWS** means the +operating system, the volume, or the cloud account underneath the process. + +A row with no path says **NOT IN TREE**, which means the software does not implement it and you +should not plan as though it does. + +## Identity provider + +| Control | Trinity software | Customer | Host or AWS | +|---|---|---|---| +| Operating the IdP | No | **Yes.** Trinity is a resource server, never an authorization server for your users | No | +| Validating a bearer token on an inbound MCP request | **Yes.** `lib/trinity/mcp/auth.ex`, `lib/trinity/mcp/auth/token.ex` | Configure issuer, audience, resource | No | +| Choosing the profile | **Yes**, three exist: `:local`, `:production`, `:personal` at `config/runtime.exs:92-98` | **Yes.** `TRINITY_MCP_AUTH_PROFILE` is yours to set | No | +| Mapping a subject to an authorization decision | Scopes only, `lib/trinity/mcp/auth/scopes.ex` | **Yes.** Entitlement policy is yours | No | +| Token revocation and session lifetime | Introspection when enabled, `config/runtime.exs:105` | **Yes** | No | + +## Model + +| Control | Trinity software | Customer | Host or AWS | +|---|---|---|---| +| Which model is called | Configuration only, `config/llm.exs:14` | **Yes.** You choose the endpoint and the provider | Network path | +| Enforcing that only an approved model may be called | **NOT IN TREE.** No allow-list mechanism exists in the tree; the model list is configuration and nothing refuses an unapproved one at runtime | **Yes.** Enforce by configuration control and egress policy | Egress filtering | +| Whether the model provider trains on your data | No | **Yes.** A contract question with your provider | No | +| Keeping the model inside your boundary | No | **Yes**, if required. Self-hosting is a customer deployment choice | **Yes** | + +## Keys + +| Control | Trinity software | Customer | Host or AWS | +|---|---|---|---| +| Receipt signing key generation and use | **Yes.** `lib/trinity/receipts/key_custody.ex`, seam at `lib/trinity/keys.ex` | No | No | +| Where key material lives | Default is a 0600 file in the data directory, `lib/trinity/paths.ex` (`keys_dir/0`) | **Yes.** Custody is a deployment decision | Filesystem permissions | +| A KMS or HSM adapter | Seam exists (`lib/trinity/keys.ex`); **no KMS or HSM adapter is in the tree** | **Yes**, if required | **Yes** | +| Custody of the signing key | **No, and this is stated rather than implied.** `docs/10-assurance-case.md` C6 | **Yes** | **Yes** | +| Envelope encryption of blobs | **Yes.** AES-256-GCM, `docs/encryption-at-rest.md` | No | No | +| Encryption of everything SQLite indexes | **No, by design.** `docs/encryption-at-rest.md` | No | **Yes.** Volume or page level below SQLite | + +## Egress + +| Control | Trinity software | Customer | Host or AWS | +|---|---|---|---| +| Suppressing network access in the test suite | **Yes.** `test/network_guard_test.exs` | No | No | +| Refusing an effect that was not approved | **Yes.** `lib/trinity/effects.ex` re-derives the fingerprint before admitting | No | No | +| Restricting which hosts the process may reach | **NOT IN TREE** | **Yes** | **Yes.** Security group, egress proxy, firewall | +| Redacting credential-shaped strings from logs | **Yes**, as a last line. `lib/trinity/telemetry/redaction.ex` | No | No | +| Deciding what may leave the boundary at all | No | **Yes.** See `data-flow.md` | **Yes** | + +## Backups + +| Control | Trinity software | Customer | Host or AWS | +|---|---|---|---| +| Producing a portable archive | **Yes.** `mix trinity.export`, `lib/mix/tasks/trinity.export.ex`, `docs/backup.md` | Run it | No | +| Restoring onto a fresh install | **Yes.** `lib/mix/tasks/trinity.import.ex` | Run it and test it | No | +| Scheduling, offsite copies, retention of backups | **NOT IN TREE** | **Yes** | **Yes** | +| Encrypting the archive at rest | Archive blobs are encrypted, `docs/encryption-at-rest.md` | Storage of the archive | **Yes** | + +## Retention + +| Control | Trinity software | Customer | Host or AWS | +|---|---|---|---| +| Ageing memory entries | **Yes.** Stale at 30 days, archived at 90, `lib/trinity/memory/curator.ex` | Tune the thresholds | No | +| Deleting anything on a schedule | **No. The curator deletes nothing**, it marks and archives in place (`lib/trinity/memory/curator.ex`) | **Yes**, if your policy requires deletion | No | +| Receipt chain retention | **Append only. Nothing in the tree deletes a receipt** | **Yes.** How long you keep the store | **Yes** | +| A records schedule, or legal hold | **NOT IN TREE** | **Yes** | No | + +## Incidents + +| Control | Trinity software | Customer | Host or AWS | +|---|---|---|---| +| Raising an alarm when the signer is unavailable | **Yes.** `lib/trinity/receipts/alarm.ex`, and every effect is denied until one is | Monitor it | No | +| Telemetry events an operator can subscribe to | **Yes.** `docs/telemetry.md`, `lib/trinity/telemetry.ex` | Collect and alert | **Yes** | +| Shipping logs or metrics anywhere | **NOT IN TREE.** Trinity emits; it does not forward | **Yes** | **Yes** | +| Reporting to a government customer within a clock | No | **Yes.** See `incident.md` | No | +| A published vulnerability disclosure process for this software | **Yes.** `SECURITY.md` | Report to it | No | diff --git a/docs/regulated/data-flow.md b/docs/regulated/data-flow.md new file mode 100644 index 0000000..cba7c41 --- /dev/null +++ b/docs/regulated/data-flow.md @@ -0,0 +1,83 @@ + + +# Data flow: what may enter a prompt, and what must not leave + +The model endpoint is outside the accredited boundary (`authorization-boundary.md`). Everything in a +prompt has therefore left your boundary by the time the model sees it. This page says what Trinity +puts in a prompt, what it will not put there, and which of those is enforced in code rather than by +policy. + +## What enters a prompt + +Assembled by `Trinity.Sessions.Prompt.build/5`, `lib/trinity/sessions/prompt.ex:45`. + +| Tier | What it is | Path | +|---|---|---| +| Persona | The always-on instructions for the session's persona | `lib/trinity/personas.ex` | +| Always-on memory | Memory entries in the session, persona and global scopes | `lib/trinity/memory.ex` | +| Project context | `AGENTS.md` from the project root, when one is set | `lib/trinity/context.ex` | +| Conversation | The recent turns of this session | `lib/trinity/sessions/store.ex` | +| Tool definitions | Name, description and JSON schema of each available tool | `lib/trinity/tools/registry.ex` | +| Retrieved memory | Entries recalled for this turn | `lib/trinity/memory.ex` | + +Each tier has a token budget and is truncated rather than dropped silently; a truncation is itself +recorded as a query receipt. The budgets are `config :trinity, :prompt_budgets`. + +**Everything in that table is customer data by default.** A file read by a tool, a memory entry, a +line of `AGENTS.md` and the text of a conversation all go to the model. If any of it is CUI or PHI, +then CUI or PHI is leaving your boundary on every turn that includes it. + +## What never enters a prompt + +**Token material does not reach model context.** An inbound MCP request is validated and reduced to +a principal before anything downstream sees it, and the principal is defined not to carry the token. +`lib/trinity/mcp/auth/principal.ex:7`: + +> Who a validated request is from (slice 062): the issuer, the subject, the scopes, the client, the +> token's id when it has one, and the profile that admitted it. What the server puts in the context +> and the receipts carry; never the token. + +The same rule holds on the way out: a refusal reason is "a word or two, never a token" +(`lib/trinity/mcp/auth_host.ex:168`), and `Trinity.MCP.Auth.authorize/2` returns "the principal, or +why not (never the token)" (`lib/trinity/mcp/auth.ex:45`). + +The receipt signing key never enters a prompt either. It is read only by the custody module, +`lib/trinity/receipts/key_custody.ex`, which no prompt tier touches. + +**A last line, not the control.** `lib/trinity/telemetry/redaction.ex` masks credential-shaped +strings on their way into a log. It is a Logger filter, it does not sit between the session and the +model, and its own moduledoc says it is not the control. Do not rely on it to keep a secret out of a +prompt. + +## CUI and PHI + +**This is the part that is policy, not code.** + +The requirement: CUI or PHI must not leave the boundary except to a model the lab has approved for +that category of data, under a contract that permits it. + +The enforcement: **NOT IN TREE.** There is no allow-list of approved models in this repository, and +nothing refuses a call to an unapproved endpoint at runtime. The model in force is configuration +(`config/llm.exs:14`, and `TRINITY_MODEL` at runtime). Trinity will call whatever it is pointed at. + +What this means for an accreditation: + +1. **Model selection is a configuration-controlled item.** Treat a change to the model endpoint as a + change to the boundary, because it is one. +2. **Egress restriction is yours.** A security group, an egress proxy or a firewall rule is what + actually stops a prompt reaching an unapproved endpoint. See `customer-responsibility-matrix.md`. +3. **If no approved model exists for the category, the data must not enter a session at all.** Once + it is in the conversation it is in the prompt on the next turn, because the conversation tier is + sent verbatim. + +## Where the data rests + +| Store | Contents | Encrypted by Trinity? | Path | +|---|---|---|---| +| Primary database | Sessions, messages, memory, approvals | **No.** Left to the volume, by design | `docs/encryption-at-rest.md` | +| Receipts database | The signed chain, separate file and pool | **No.** Left to the volume | `lib/trinity/repo/receipts.ex` | +| Skill files, staged changes, archives | Blobs nothing indexes | **Yes.** AES-256-GCM, fresh data key each | `docs/encryption-at-rest.md` | +| Keys directory | Signing key, device id | Mode 0600 in a 0700 directory | `lib/trinity/paths.ex` | + +The split is deliberate and the reasoning is in `docs/encryption-at-rest.md`: anything SQLite indexes +is left to the volume beneath it, because encrypting it in code would defeat the index. diff --git a/docs/regulated/incident.md b/docs/regulated/incident.md new file mode 100644 index 0000000..947cb48 --- /dev/null +++ b/docs/regulated/incident.md @@ -0,0 +1,83 @@ + + +# Incident reporting + +Clocks that may apply when Trinity is inside an accredited boundary, and what the software gives you +to work with. **The reporting obligation is the lab's, not this project's.** Trinity is customer +controlled software; there is no provider-side incident process that runs on your behalf. + +## CONTACT = TODO + +**This pack does not yet name a contact.** Fill this in before the pack is used in an assessment: + +| Role | Contact | +|---|---| +| Lab incident response lead | TODO | +| Contracting officer or COR | TODO | +| Privacy official, if PHI is in scope | TODO | +| Security reports about this software | `SECURITY.md` in this repository | + +Leaving these as TODO is deliberate. An invented name is worse than a blank, because a blank gets +filled and a wrong name gets used. + +## The 72-hour DFARS clock + +If the lab holds a contract with **DFARS 252.204-7012**, a cyber incident affecting a covered +contractor information system, or the covered defense information on it, is **reported to DoD within +72 hours of discovery**, through the DoD reporting mechanism. A medium assurance certificate is +required to submit. The clock runs from discovery, not from confirmation, and the obligation is the +contractor's. + +**What Trinity contributes:** a signed, hash-linked record of every governed action, with the +verifier in `receipts-verify.md`. That is evidence for the report. It is not the report, and +producing it does not start, stop or satisfy the clock. + +**What Trinity does not do:** it does not detect an incident, does not notify anyone, and does not +ship logs anywhere. Forwarding is **NOT IN TREE** (`customer-responsibility-matrix.md`, Incidents). + +## The HIPAA clock, if PHI is in scope + +If PHI is in scope, the Breach Notification Rule applies to the covered entity: individuals without +unreasonable delay and **no later than 60 days** from discovery, the Secretary on the same clock for +a breach of 500 or more individuals, and annually otherwise. + +Two things to be clear about before relying on that paragraph: + +1. **This pack is not a HIPAA attestation** and nothing in this repository has been audited against + the Security Rule. +2. **Sudo Apt Holdings LLC is not your Business Associate** by virtue of you running this software. + Running a binary on your own host does not create the relationship, and no BAA is offered here. + +Whether an event is a breach under 45 CFR 164.402, including the risk assessment, is the covered +entity's determination. + +## What the software gives you at the moment of an incident + +| Need | What exists | Path | +|---|---|---| +| A record of what was decided and done | Signed hash chain, per scope | `lib/trinity/receipts/chain_writer.ex` | +| Verify that record without trusting the app | Standalone verifier and a mix task | `receipts-verify.md` | +| Know the signer was unavailable | Alarm, and every effect denied until a signer returns | `lib/trinity/receipts/alarm.ex` | +| Subscribe to events | Telemetry, documented | `docs/telemetry.md` | +| Take a copy of the whole state | `mix trinity.export` | `docs/backup.md` | +| Know which code paths can cause an effect | Published, closed population | `docs/effects-catalog.md` | + +## What the software does not give you + +- **No intrusion detection.** Trinity does not decide that something bad happened. +- **No alerting.** It emits telemetry; collecting and alerting on it is yours. +- **No log shipping.** NOT IN TREE. +- **No third-party witness.** Nothing outside the host attests to the chain, so a reader who does not + trust the host cannot detect backdated forgery. NOT IN TREE, and stated in + `authorization-boundary.md`. +- **No tamper alarm on the store itself.** The chain is verifiable on demand; nothing watches it + continuously. + +## A note on the clock and the clock + +The receipt clock is the host's own reading (`lib/trinity/receipts/clock.ex`). It never goes +backwards within a chain, and it is signed, so the ordering cannot be edited afterwards by anything +that cannot sign. It is **not** a trusted timestamp and must not be offered to an investigator as +one. If your incident process needs authoritative time, supply a trusted time source; the standards +register records that as a deployment dependency rather than a property of this tree +(`docs/09-standards-register.md`). diff --git a/docs/regulated/receipts-verify.md b/docs/regulated/receipts-verify.md new file mode 100644 index 0000000..c50acec --- /dev/null +++ b/docs/regulated/receipts-verify.md @@ -0,0 +1,98 @@ + + +# Verifying the record + +How an assessor checks Trinity's record of what it did, **without trusting the program that wrote +it**. Everything here quotes a task or a script that exists in the tree. Nothing is invented. + +## Two entry points, and why there are two + +| Entry point | Needs | Path | +|---|---|---| +| `mix trinity.receipts.verify` | The application, or just the exported file | `lib/mix/tasks/trinity.receipts.verify.ex` | +| `bin/verify_receipt.exs` | Elixir only. **Not the application** | `bin/verify_receipt.exs` | + +The second is the one that matters for an assessment. A verifier that has to start Trinity to check +Trinity's records is asking you to trust the thing under examination. `bin/verify_receipt.exs` reads +an exported file and needs nothing from this application. + +## The task, quoted + +From `lib/mix/tasks/trinity.receipts.verify.ex`: + +> Runs `Trinity.Receipts.Verifier` over a scope in the database or over an exported file +> (slice 024): +> +> mix trinity.receipts.verify --scope session: +> mix trinity.receipts.verify --file receipts.json +> +> Exit codes, the same vocabulary as `bin/verify_receipt.exs`: 0 verified, 1 invalid, +> 2 usage, 5 trust not established (a key id the registry does not know), 6 compromised key. + +## Exporting a scope first + +From `lib/mix/tasks/trinity.receipts.export.ex`: + +> Exports a scope for the standalone verifier (slice 024, AC7): +> +> mix trinity.receipts.export --scope session: --out receipts.json +> mix trinity.receipts.export --scope boot --out boot.json +> +> The file is `Trinity.Receipts.export/1`'s map: the rows in seq order, the checkpoints and the +> registry, so `bin/verify_receipt.exs` needs nothing else. Exit 2 on usage. + +## The exit vocabulary + +Both entry points use the same codes. Treat them as the finding, not the console text. + +| Code | Meaning | +|---|---| +| 0 | Verified | +| 1 | Invalid | +| 2 | Usage | +| 5 | Trust not established: a key id the registry does not know | +| 6 | Compromised key | + +Codes 5 and 6 are the interesting ones for an assessor. **5 is not a failure of the chain**; it says +the chain refers to a key the reader cannot place, which is a question about key distribution rather +than about the records. **6 is a refusal**: the registry marks that key compromised and the verifier +will not vouch for rows signed under it. + +## What the verifier actually checks + +`lib/trinity/receipts/verifier.ex`, per row, in order: + +1. `seq` is the previous plus one, and `prev_hash` is the previous row's `receipt_hash`. +2. The scheme is one the caller allowed, and the key id resolves in the registry. +3. The row's algorithm is the scheme's family, taken **from the registry row and never from the + receipt**. +4. The stored hash equals the hash of the pre-authentication encoding over the stored body. +5. The stored columns agree with the signed body, so a row edited after the fact is caught. +6. For a signed kind, the signature verifies under the registry's public key. + +Checkpoints are verified as well, so query receipts that are covered rather than individually +signed are still accounted for. + +## What a clean verification does and does not prove + +**Does prove:** the rows are in the order they claim, nothing was altered after signing, and each +signed row was signed by the key the registry names. + +**Does not prove:** that the host was honest. Standalone, the same process decided, acted and wrote +the record, so a clean chain is the host's own consistent account. `authorization-boundary.md` sets +this out, and `docs/10-assurance-case.md` C6 records that a key in a file proves nothing about +custody of that key. + +**Does not prove when.** The clock is the host's reading (`lib/trinity/receipts/clock.ex`). It is +signed and monotone within a chain, so the ordering cannot be rewritten; it is not a trusted +timestamp. + +## Scheme versions an assessor will meet + +Rows exist under two scheme versions and both verify. `receipt_v2_*` carries ten signed fields; +`receipt_v3_*` carries eleven, the extra one being the clock. A chain that spans the change verifies +row by row under each row's own scheme. The mapping to published constructs, including the three +fields that have no equivalent, is `docs/receipt-scheme-mapping.md`. + +That document also states, in terms, that Trinity issues SCITT **Signed Statements** and never SCITT +**Receipts**, because it runs no Transparency Service. No conformance to RFC 9943 is claimed.