> ## Documentation Index
> Fetch the complete documentation index at: https://docs.masker.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Session compliance reports: contents and chain verification

> What a Masker session compliance report contains, how to download JSON and PDF formats, and how to verify the HMAC-SHA256 audit chain integrity offline.

At the end of every session, Masker produces a compliance report — the artifact you hand a prospect's security team, your privacy counsel, or a HIPAA auditor. It is designed to answer one question: what PHI flowed through which systems, and how do we know?

## Two formats, one source of truth

Every session report is generated as two consistent artifacts derived from the same event chain:

<CardGroup cols={2}>
  <Card title="Masker Audit Schema v1 JSON" icon="code">
    Machine-checkable. Used for automated compliance pipelines, programmatic verification, and integration with your SIEM or GRC tooling.
  </Card>

  <Card title="Auditor-ready HIPAA PDF" icon="file-text">
    Human-readable. Structured for security reviewers, privacy counsel, and external auditors. Self-contained — no external links required to interpret it.
  </Card>
</CardGroup>

Both formats share the same `merkle_root_hex`. This is how you prove to an auditor that the PDF and the JSON describe the exact same event chain — the root hash is the cryptographic binding between them.

## What the report contains

The PDF and JSON report surfaces the following sections, in this order:

| Section                       | What it shows                                                                                                               |
| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| **Cover**                     | Scope, time window, generation timestamp, and the audit root hash                                                           |
| **Executive summary**         | Total sessions, total redactions, coverage percentage, and alert count                                                      |
| **HIPAA Safe Harbor matrix**  | Coverage status for each of the 18 categories, stamped at generation time                                                   |
| **PCI-DSS scope**             | Whether cardholder data was detected and Luhn-checked before egress; outcome per session                                    |
| **Leak detection**            | Any placeholder that did not round-trip cleanly, flagged with the offending turn                                            |
| **Retention attestation**     | Confirmation that originals were not persisted; for self-hosted deployments, confirms only the encrypted chain was retained |
| **BAA chain**                 | Which BAA-gated paths were used this session (e.g., production TTS rehydration slug)                                        |
| **Redaction summary**         | Counts by entity kind and detection pass                                                                                    |
| **Sample masked transcripts** | Up to 20 randomly sampled sessions showing the masked request body — proving the LLM only ever saw tokens                   |
| **Audit log integrity**       | Start hash, end hash, event count, and chain verification status                                                            |
| **Configuration snapshot**    | Active policy YAML and per-agent upstream model at the time of generation                                                   |
| **Appendix**                  | Full list of session IDs in scope — no PHI                                                                                  |

## The audit chain structure

Masker writes every detection event to an HMAC-chained, append-only journal:

```
event_n.hash = HMAC-SHA256(audit_key, event_{n-1}.hash || canonical_json(event_n))
```

Each event in the JSONL chain carries a sequence number, a link to the previous hash, and its own hash covering both content and provenance:

```jsonl theme={null}
{"seq":0,"kind":"detection","detector":"ssn_v1","placeholder":"[SSN_01]","prev_hash":"0000…","curr_hash":"a3f2…","ts":"2026-05-01T18:33:01Z"}
{"seq":1,"kind":"detection","detector":"usphone_v2","placeholder":"[USPHONE_01]","prev_hash":"a3f2…","curr_hash":"7c9e…","ts":"2026-05-01T18:33:02Z"}
{"seq":2,"kind":"redaction_applied","span":[12,23],"placeholder":"[SSN_01]","prev_hash":"7c9e…","curr_hash":"e1b4…","ts":"2026-05-01T18:33:02Z"}
```

Each event records:

* `seq` — monotonically increasing sequence number
* `kind` — event type (`detection`, `redaction_applied`, etc.)
* `detector` — which detector fired (e.g., `ssn_v1`, `usphone_v2`)
* `placeholder` — the token assigned to this PHI span
* `prev_hash` — SHA-256 hash of the previous event, linking it into the chain
* `curr_hash` — SHA-256 hash of this event's content combined with `prev_hash`
* `ts` — ISO 8601 timestamp

What is **not** in any event: the original PHI value, the token value, or any payload contents. Leaking the audit log does not leak PHI. The log records that a detection occurred — not what was detected.

### Fail-closed guarantee

If the journal append fails at any point, Masker returns `AuditUnavailable` and stops processing the request. There are no quiet drops. You will always know when the chain cannot be extended.

## How to download your report

<Steps>
  <Step title="Open the Reports tab">
    In the Masker portal, navigate to the session you want to report on and click the **Reports** tab.
  </Step>

  <Step title="Select a format">
    Choose **Download PDF** for the auditor-ready report or **Download JSON** for the machine-readable Masker Audit Schema v1 record. Both are available from the same one-click interface.
  </Step>

  <Step title="Verify the merkle_root_hex">
    Both downloads display the same `merkle_root_hex`. Confirm they match before sending to an auditor — this proves the PDF and JSON describe the same chain.
  </Step>
</Steps>

You can also generate reports programmatically:

```bash theme={null}
# Per-session via CLI
masker report --session-id <session_id> --output report.pdf

# Per-session via API
GET /api/v1/sessions/{id}/report
```

## Verifying chain integrity

There are three levels of verification, from quickest to most rigorous.

<AccordionGroup>
  <Accordion title="Level 1 — visual review">
    Open the PDF. Check the Safe Harbor coverage table, redaction counts, and sample masked transcripts. This is what most stakeholders do for routine review.
  </Accordion>

  <Accordion title="Level 2 — root hash check via the portal">
    Every report includes the audit root hash for the start and end of its window. In the portal, navigate to **Audit log → Verify hash** and paste the hash. Masker re-walks the chain and confirms the count and integrity. This is the screenshot you send an auditor for a quick proof.

    You can also call the API directly:

    ```bash theme={null}
    POST /audit/verify
    ```

    The response is:

    ```json theme={null}
    { "ok": true, "event_count": 42, "message": "chain ok" }
    ```

    The literal string `"chain ok"` in the `message` field is the programmatic signal that the chain is intact.
  </Accordion>

  <Accordion title="Level 3 — independent recomputation">
    For an external auditor who needs to verify without trusting Masker's infrastructure: request the audit key (provided under a separate signed envelope) and the raw JSONL event log for the session. The auditor can recompute the HMAC-SHA256 chain from scratch using any standard implementation.

    The `masker audit verify` command in the public repository performs this recomputation and can be run offline:

    ```bash theme={null}
    masker audit verify --log session.jsonl --key <audit_key>
    ```
  </Accordion>
</AccordionGroup>

<Warning>
  The `MASKER_AUDIT_HMAC_KEY` is set at deploy time. Never log it, never commit it, and treat it like a signing key. Losing it means future events cannot extend the chain.
</Warning>

## Honest limits

* Reports can only show what was logged. If `audit.log_events` is disabled (it should not be), the chain has gaps and the report says so explicitly.
* Reports cannot verify upstream behavior. Masker proves what it sent to your LLM provider; it cannot prove what that provider did with it. The LLM provider's own BAA and SOC 2 cover that leg.
* The configuration snapshot in a report reflects the active policy at generation time, not at the time of every individual call within the window. The raw audit log itself records the policy version on every event.
