chron
<div align="center">
<img src="assets/chron-icon-png-transperent.png" alt="Chron" width="96" />
<h1>Chron</h1>
<p>Local-first, tamper-evident audit trails for AI conversations, tool calls, code changes, and secrets — with deterministic compliance review across SOC 2, ISO 27001, EU AI Act, and NIST AI RMF.</p>
</div>
AI tools show when you sent a message. Chron logs when the AI responded too — and keeps a permanent, queryable, tamper-evident record of the AI work happening across your tools.
Works with Claude Desktop, Claude Code, Cursor, Windsurf, Codex, Gemini CLI, and any MCP-compatible AI tool.
---
## Why
AI tools produce no audit trail by default. You cannot answer:
- What did the AI say, and when exactly?
- How long did the AI take to respond?
- What was the full conversation that produced this output?
- Which tool calls, command results, and code diffs happened during the session?
- Did a secret or credential get pasted into an AI prompt?
- Has this audit record been edited after the fact?
- What did I ask Claude last week about this codebase?
Chron fixes that. Every exchange is logged with a precise local datetime (including timezone offset) to a SQLite file you own. It can hash-chain every event, sign sessions with Ed25519, detect secrets/PII, run deterministic compliance review across four frameworks, and stream metadata-only events to your SIEM. No cloud, no vendor lock-in, no message content leaving your machine.
---
## What Chron captures
Chron stores a local audit trail of:
- User and assistant messages
- Tool calls and tool results
- Code changes with file path, operation, and unified diff
- Session start/resume metadata, including parent session and external refs
- Secret/PII detections with masked values
- Hash-chain integrity data
- Optional Ed25519 session signatures
- Optional NTP clock attestation metadata
Chron's SIEM integrations send only metadata: session starts, role-only message events, and masked secret detections. Message content and raw secret values stay local.
Need runtime policy enforcement for AI agents? CLAIIM adds agent identity, approval workflows, and ALLOW/DENY gates on top of Chron proof. Try the public preview: https://claiim.io/preview
---
## Fail-closed capture — `chron run`
Chron's MCP logging depends on the AI client calling a tool. `chron run` does not.
```bash
chron run -- claude
chron run -- codex
```
The wrapper records each interaction chunk to a local append-only journal and
makes it durable before releasing it. No byte reaches your terminal until the
journal write covering it has been flushed and fsynced. If a journal write
fails, forwarding stops in both directions, the child is terminated, and
whatever was being held is **not** printed — the command exits `99` rather than
showing you output that was never recorded.
```bash
chron run --scope # exactly what this does and does not establish
chron verify --journal # verify the journal chain and see its scope
chron project # replay the journal into the database (idempotent)
```
### What it establishes, and what it does not
`chron run` records the controlled stdin, stdout and stderr channels before
forwarding or releasing them. That is the whole of the guarantee.
It does **not** enforce that the wrapper is used — running the client directly
bypasses it, and that requires a control outside Chron, such as a managed
`PATH`, a restricted shell or an MDM policy. It does **not** capture every side
effect of the client: files it reads or writes, subprocesses it spawns and
network requests it makes never pass through the wrapper's pipes.
`chron run --scope` prints the full list, and every run writes that same scope
into its first journal record, so the claim travels with the evidence.
### Verified enforcement
A run projected into the database carries a binding to the journal records it
came from. `chron verify <session-id>` follows that binding — the journal
segments must verify as a chain, the run must still be in it unchanged, and the
projected messages must match the journal field by field — and only then reports
`enforced:wrapper`. A claim that does not check out is a verification failure,
not a footnote.
A journal chain that verifies establishes that its records are internally
consistent with each other. It does not establish that they were never edited:
the chain is unanchored, so anyone able to rewrite the whole file can recompute
every hash. Chron signs sessions and bundles; it does not yet sign journal heads.
---
## Install
**CLI + MCP server:**
```bash
npm install -g chron-mcp
chron doctor --fix
```
> `npx chron-mcp` starts the MCP server only — it does not put the `chron` CLI command in your PATH. You need a global install for the CLI.
`chron doctor --fix` adds Chron to supported AI clients automatically: Claude Desktop, Claude Code, Cursor, Gemini CLI, Windsurf, and Codex. Restart your AI client after it runs.
First run creates `~/.chron/chron.db` automatically. No database setup, no env vars, no migrations.
Check your setup:
```bash
chron doctor
```
For CI or automation:
```bash
chron doctor --json
```
Manual MCP config, if you need it:
```json
{
"mcpServers": {
"chron": {
"command": "npx",
"args": ["-y", "chron-mcp"]
}
}
}
```
---
## CLI
After `npm install -g chron-mcp`, the `chron` command is available globally:
```
Usage: chron <command> [options]
Commands:
run Run a command with fail-closed capture — output is withheld until recorded
project Replay the capture journal into the database (idempotent)
reconcile Read-only: what your client records that Chron cannot account for
history List sessions or show full log for a session
report Aggregate audit stats across sessions
export Export a session as markdown
secrets List detected secrets across sessions
settings View current configuration
connect Connect to a SIEM or AI tool (codex, cursor, gemini, crowdstrike, sentinel, splunk)
summary Structured summary of a session (timeline, mutations, secrets)
sign Sign a session with its Ed25519 key — produces a .chron.sig file
verify Verify a session's hash chain and Ed25519 signature
prune Delete sessions older than a retention cutoff
doctor Check your Chron setup — Node version, DB, MCP configs, SIEM
import Import conversations from external AI tools into Chron
review Review AI sessions against compliance control criteria
update Update chron to the latest version
Options (history):
--limit=<n> Max sessions to show (default: 20)
--search=<query> Full-text search across all sessions (FTS5: phrases, boolean, prefix*)
--ref=<value> Filter by external_ref prefix (e.g. --ref=jira:ENG-123)
<id-prefix> Show full log for the session with this ID prefix
Options (report):
--since=<range> Filter by date: 7d, 30d, or YYYY-MM-DD (default: all time)
--format=soc2 Generate SOC 2 HTML evidence package
--output=<file> Output file for --format=soc2
Options (export):
<id-prefix> Markdown export for a single session
--signed Tamper-evident bundle (JSONL + manifest + Ed25519 sig)
--session=<id> Filter bundle to a single session
--since=<range> Filter bundle by date: 7d, 30d, or YYYY-MM-DD
--output=<file> Output path (default: bundle.chron.tar.gz)
Options (reconcile):
--json Machine-readable JSON output, including the compared message indices
Options (verify):
<id-prefix> Verify a session's hash chain + Ed25519 signature
--bundle=<file> Verify a signed bundle offline (no DB needed)
Options (prune):
--older-than=<n>d Cutoff in days (falls back to retention_days in config)
--dry-run Show what would be deleted without deleting
--confirm Required flag to actually delete
Options (doctor):
--json Machine-readable JSON output
Options (import):
chatgpt <file> Import from ChatGPT export (.zip or conversations.json)
Options (review):
--framework=<name> Framework to review against: soc2, iso27001, euaiact, nist-ai-rmf
--since=<range> Limit to sessions since: 7d, 30d, or YYYY-MM-DD
--all Include accepted, dismissed, and resolved findings
--output=<file> Write HTML report to file (printable to PDF from browser)
accept <id> Mark a finding as accepted; supports --note=<text>
dismiss <id> Mark a finding as dismissed; supports --note=<text>
resolve <id> Mark a finding as resolved; supports --note=<text>
```
---
## Reconciliation — `chron reconcile`
Your AI client keeps its own transcripts. Chron keeps evidence. `chron reconcile`
reads both and reports what the client recorded that Chron cannot account for.
```bash
chron reconcile # human-readable
chron reconcile --json # includes the compared message indices
```
It is read-only. It opens the database through a copy, never writes a row, and
touches neither the capture journal nor the transcripts it reads.
### What it establishes, and what it does not
Three outcomes, kept apart on purpose:
- **Recorded identity** — an imported session is bound to a transcript by
`external_ref`. That says which session an import created. It does not say
that the session's messages correspond to the transcript's, so content is
reported separately and in its own terms.
- **Candidate association** — the messages correspond, but nothing binds them.
It needs confirmation; it is not a conclusion.
- **No association found** — within the search that was actually performed.
**A transcript with no match does not establish that a session was bypassed.**
Chron cannot see what it did not record, so an absence is a statement about the
search, not about the user. Every absence carries the scope that produced it: the
sources inspected, the time, how many messages were compared out of how many
were comparable, and — in `--json` — the exact transcript ordinals compared, so
a sampled result can be reproduced rather than taken on trust.
### The limits, stated separately
There is **no transcript size limit**: transcripts are streamed, and a 480MB
file is inspected completely. Each transcript is compared at **up to 200 of its
messages**, evenly spaced. These are independent — more files are inspectable
than before, and none is compared more deeply.
Messages shorter than 24 characters once normalised are not compared, because a
match on "ok" means little and a false correspondence is worse than a missed
one. Lines past 4MB are skipped and reported. Timestamps are not compared at
all: clock tolerance is `null`, so no finding rests on two clocks agreeing.
A transcript that is only partly readable says so, and its completeness travels
with every finding about it — including whether the file changed while it was
being read, in which case its digest is the digest of the bytes read and not of
a settled file.
Claude Code only, for now. Codex and Cursor reuse the same contract once their
formats are validated against real samples.
---
## Chron Intelligence — compliance review
`chron review` scans your session history and flags findings against a compliance framework. No model inference. No API calls. Pure deterministic pattern matching on structured events Chron already captured.
```bash
chron review --framework=soc2
chron review --framework=iso27001
chron review --framework=euaiact
chron review --framework=nist-ai-rmf
```
Add `--since=30d` to scope to recent sessions, and `--output=report.html` to generate a printable HTML evidence report.
**Four frameworks supported:**
| Framework | Rules | Controls covered |
|-----------|-------|-----------------|
| SOC 2 | 5 | CC6.1, CC6.6, CC6.7, CC7.2, CC8.1 |
| ISO 27001:2022 | 6 | A.8.2, A.8.3, A.8.12, A.8.20, A.8.24, A.8.31 |
| EU AI Act | 6 | Art. 9, Art. 10, Art. 12, Art. 13, Art. 14 |
| NIST AI RMF 1.0 | 7 | GOVERN, MAP, MEASURE, MANAGE |
**Finding workflow:**
Every finding has a stable SHA-256 ID. Act on findings as you review:
```bash
chron review accept <id> --note="approved by security team, PR #512"
chron review dismiss <id> --note="test fixture, not production code"
chron review resolve <id> --note="credential rotated, no commit exposure"
chron review --framework=soc2 --all # show accepted/dismissed/resolved too
```
Reviewed findings do not reappear as noise on the next run. New findings — from sessions since your last review — surface cleanly.
**Important:** Chron covers the subset of controls evaluable from AI coding session evidence. A full framework assessment requires policy documentation, board records, and auditor judgement that session logs cannot replace. Each HTML report includes a framework-specific disclaimer on the cover page.
### `chron otel receipts`
Every export writes a receipt: where the evidence went, how much of it, and what came back.
```bash
chron otel receipts # most recent 20
chron otel receipts --limit=5
chron otel receipts --json # machine-readable
```
```
2026-09-29 12:36 c53022ae partly accepted 20 span(s) https://collector.example.com/v1/traces
spans: 12 accepted, 8 unanswered
```
A receipt is an append-only, hash-chained record: the destination, scope and content posture are bound into the chain, and each batch records what was about to be sent before it was sent. A process killed mid-export therefore reads as `delivery unknown` rather than claiming nothing was sent.
Receipts are verified on every read. One that does not verify shows the reason and **no delivery state** — a record that does not hold up is not evidence of anything — and the command exits non-zero so automation notices without parsing each row.
**Wording matters here.** "Accepted" means the collector returned success for the payload. Whether it retained the data is something only that platform can show, and Chron does not claim it. Credentials never enter a receipt: userinfo, query parameters and unrecognised path segments are all stripped before the destination is recorded, and collector error bodies are reduced to a category rather than stored.
### `chron doctor`
`chron doctor` validates the pieces that make Chron useful in real life:
- Node.js version (v18+ required)
- Local Chron version vs npm latest
- The exact MCP launcher written into client configs
- DB directory and key directory write access
- Claude Desktop, Claude Code, Cursor, Gemini CLI, Windsurf, and Codex MCP config presence
- Whether Chron is configured in each MCP client
- Optional HTTP mode health check on `/health`
- Splunk, Sentinel, and LogScale configuration via env vars or `~/.chron/config.json`
Run `chron doctor --fix` to add the Chron MCP server to supported clients automatically. Warnings for optional integrations do not fail the command. Real failures exit with code `1`.
---
## What it logs
Every exchange is recorded with precise local timestamps — user message when received, assistant response when sent:
```
[user: 2026-05-08 14:32:11 +02:00 | assistant: 2026-05-08 14:32:43 +02:00]
The main risks of deploying this contract are...
```
The gap between user and assistant timestamps is real generation time. Both are stored in your local SQLite DB with full timezone offset.
---
## Config by tool
### Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
```json
{
"mcpServers": {
"chron": {
"command": "/absolute/path/to/chron-mcp",
"args": []
}
}
}
```
### Claude Code
```bash
claude mcp add chron -- /absolute/path/to/chron-mcp
```
Then add the skill hook to `~/.claude/settings.json`:
```json
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "cat ~/.chron/chron.skill.md"
}
]
}
]
}
}
```
### Cursor
Run:
```bash
chron connect cursor
chron doctor --verify-logging cursor
```
This writes the global MCP config and a project workspace rule:
- `~/.cursor/mcp.json`
- `.cursor/mcp.json`
- `.cursor/rules/chron.mdc`
Chron also advertises the logging contract through the MCP server's initialization instructions, so Cursor receives it outside project workspaces. Cursor must be restarted after setup. Use Cursor Agent mode, then run the verify command above to confirm a real Chron session appears in the local database.
Manual MCP config:
```json
{
"mcpServers": {
"chron": {
"command": "/absolute/path/to/chron-mcp",
"args": []
}
}
}
```
### Gemini CLI
Run:
```bash
chron connect gemini
```
Or edit `~/.gemini/settings.json`:
```json
{
"mcpServers": {
"chron": {
"command": "/absolute/path/to/chron-mcp",
"args": []
}
}
}
```
### Windsurf
Edit `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"chron": {
"command": "/absolute/path/to/chron-mcp",
"args": []
}
}
}
```
---
## Companion skill file
Chron ships with `skills/chron.skill.md` — a plain-text instruction file that tells the AI how to use the MCP tools automatically. Load it into your AI tool once. After that, the AI:
1. Creates or resumes a named session at the start of every conversation
2. Logs your message before it starts responding (captures the real user timestamp)
3. Logs its response after composing it (captures the real assistant timestamp)
4. Shows `[user: YYYY-MM-DD HH:MM:SS ±HH:MM | assistant: YYYY-MM-DD HH:MM:SS ±HH:MM]` at the top of every response
5. Retrieves prior session history so context is never lost across conversations
---
## MCP Tools
| Tool | Description |
|---|---|
| `init_session` | Initialize or resume a session — returns session_id, message count, and recent messages in one call |
| `start_session` | Create a new named audit session (legacy — prefer `init_session`) |
| `log_message` | Record a single message with the current local datetime |
| `log_tool_call` | Record an AI tool invocation as an audit event |
| `log_tool_result` | Record tool output and link it to a tool call |
| `log_code_change` | Record a file edit with operation and unified diff |
| `log_exchange` | Log a user/assistant pair atomically (for batch imports) |
| `list_sessions` | List all sessions ordered by most recently active |
| `get_session_history` | Retrieve the full timestamped log for a session |
| `verify_session` | Verify the tamper-evident hash chain — detects any post-log edits |
| `scan_prompt` | Scan text for secrets (API keys, credentials) before logging — returns masked detections |
| `rehydrate_response` | Restore redacted placeholders in an assistant response back to their original values |
| `delete_session` | Delete a session and cascade its messages/secrets |
| `summarize_session` | Return a timeline summary with latency, mutations, secrets, and prod references |
---
## Integrity and signing
Every new audit event is linked to the previous one via a SHA-256 chain:
```
content_hash = SHA256(session_id | role | content | created_at | prev_hash | event_type)
```
`verify_session` walks the chain and returns:
- `valid: true` — no messages were modified after logging
- `valid: false, first_break: <id>` — exact row ID of the first tampered message
This turns your local log into a verifiable audit artifact. Any edit to a stored message — content, timestamp, or role — breaks the chain and is detected immediately.
Chron also generates an Ed25519 keypair per new session when possible. The private key stays under `~/.chron/keys`. Use:
```bash
chron sign <session-id-prefix>
chron verify <session-id-prefix>
```
For portable evidence:
```bash
chron export --signed --session=<session-id-prefix> --output=evidence.chron.tar.gz
chron verify --bundle=evidence.chron.tar.gz
```
`chron verify` also reports NTP clock attestation metadata for sessions created with clock-check support.
---
## Secrets and PII detection
Chron can detect and mask:
- API keys: OpenAI, Anthropic, AWS, Google, GitHub, Slack, Stripe, SendGrid, HuggingFace
- Private keys, JWTs, URL credentials, password assignments, env-style secrets
- Credit cards, IBANs, SSNs, DOBs, passports
- Email addresses, US/E.164 phone numbers, internal RFC-1918 IPs
Use:
```bash
chron secrets
chron secrets <session-id-prefix>
```
MCP tools can also call `scan_prompt` before sending content to an AI tool. Redaction uses `$CHRON_*` placeholders and `rehydrate_response` can restore values from the returned token map.
---
## Reports and evidence
```bash
chron history
chron summary <session-id-prefix>
chron report --since=30d
chron report --format=soc2 --output=soc2-report.html
chron export <session-id-prefix>
chron export --signed --session=<id> --output=bundle.chron.tar.gz
chron prune --older-than=90d --dry-run
```
`--signed` covers one session, because a manifest carries one signature and the
signing key belongs to that session. It fails rather than producing an unsigned
bundle: a flag that names a guarantee and exits successfully without it would
hand someone an artefact they had no reason to distrust. `chron verify --bundle`
likewise exits non-zero on a bundle with no signature — matching files against
their own manifest establishes internal consistency, not origin.
The SOC 2 report and signed bundles are designed for audit review, legal hold, and incident reconstruction without sending conversation content to Chron infrastructure.
---
## CrowdStrike LogScale integration
Stream AI session telemetry directly from developer machines into your CrowdStrike LogScale repository. No relay service — events go straight to your own LogScale instance.
**What gets sent:** session starts, message counts (role only), and masked credential detections. Message content never leaves the machine.
### Setup
**1. Create a LogScale repository and ingest token**
In Falcon console → **Log Management** → **Repositories** → **New repository** (name it `ChronAIEvents`).
Then **Settings** → **API Tokens** → **Add token** → select **Ingest** permission → copy the token.
Your ingest URL follows this pattern:
```
https://<your-cluster>.humio.com/api/v1/ingest/humio-structured
```
**2. Install the CLI and run the onboarding wizard**
```bash
npm install -g chron-mcp
chron connect crowdstrike
# Paste your ingest URL and token when prompted
# The wizard sends a test event and confirms before saving
```
**3. Add env vars to your MCP client config**
The wizard prints the exact block to paste. For Claude Code, add to the `chron` entry in `~/.claude.json`:
```json
{
"mcpServers": {
"chron": {
"command": "npx",
"args": ["-y", "chron-mcp"],
"env": {
"CHRON_LOGSCALE_URL": "https://<your-cluster>.humio.com/api/v1/ingest/humio-structured",
"CHRON_LOGSCALE_TOKEN": "<your-ingest-token>"
}
}
}
}
```
Restart your AI tool to pick up the new env vars.
**4. Import the pre-built dashboard**
In Falcon → **Log Management** → **Dashboards** → **Import**:
```
node_modules/chron-mcp/dashboards/logscale/chron-ai-activity.yaml
```
Full guide with SOC alert setup: [dashboards/logscale/README.md](dashboards/logscale/README.md)
### Env vars
| Env var | Description |
|---|---|
| `CHRON_LOGSCALE_URL` | LogScale ingest endpoint |
| `CHRON_LOGSCALE_TOKEN` | LogScale ingest token |
Both must be set for events to flow. If unset, the integration is silently disabled.
---
## Splunk integration
Stream AI session telemetry into Splunk via HTTP Event Collector (HEC). Works with Splunk Enterprise, Splunk Cloud, and a local Docker instance.
**What gets sent:** session starts, message counts (role only), and masked credential detections. Message content never leaves the machine.
### Setup
**Option A — Local Docker (fastest, no account needed)**
```bash
# Start a local Splunk instance (Apple Silicon: --platform linux/amd64 is required)
docker run -d --name splunk-chron \
--platform linux/amd64 \
-p 8000:8000 -p 8088:8088 \
-e SPLUNK_START_ARGS=--accept-license \
-e SPLUNK_GENERAL_TERMS=--accept-sgt-current-at-splunk-com \
-e SPLUNK_PASSWORD=Admin1234! \
-e SPLUNK_HEC_TOKEN=chron-test-token \
splunk/splunk:latest
# Wait ~2 minutes, then watch until ready:
docker logs -f splunk-chron 2>&1 | grep -i "Ansible playbook complete"
```
**Option B — Splunk Enterprise or Cloud**
Settings → **Data Inputs** → **HTTP Event Collector** → **New Token** → name it `chron-ingest` → source type `chron:event` → copy the token.
**2. Install the CLI and run the onboarding wizard**
```bash
npm install -g chron-mcp
chron connect splunk
# For local Docker: URL = https://localhost:8088, Token = chron-test-token
# TLS verification is skipped automatically for localhost (self-signed cert)
```
**3. Add env vars to your MCP client config**
The wizard prints the exact block to paste. For Claude Code, add to `~/.claude.json`:
```json
{
"mcpServers": {
"chron": {
"command": "npx",
"args": ["-y", "chron-mcp"],
"env": {
"CHRON_SPLUNK_URL": "https://localhost:8088",
"CHRON_SPLUNK_TOKEN": "chron-test-token",
"CHRON_SPLUNK_INSECURE": "1"
}
}
}
}
```
> Remove `CHRON_SPLUNK_INSECURE` for production Splunk instances with valid TLS certificates.
Restart your AI tool to pick up the new env vars.
**4. Search in Splunk**
Open [http://localhost:8000](http://localhost:8000) → **Search & Reporting** → run:
```
sourcetype="chron:event"
```
Events appear in real time as you have AI conversations.
Full guide with dashboard templates and alert setup: [dashboards/splunk/README.md](dashboards/splunk/README.md)
### Env vars
| Env var | Description |
|---|---|
| `CHRON_SPLUNK_URL` | Splunk HEC base URL, e.g. `https://localhost:8088` or `https://your-host:8088` |
| `CHRON_SPLUNK_TOKEN` | HEC ingest token |
| `CHRON_SPLUNK_INSECURE` | Set to `1` to skip TLS verification (local Docker with self-signed cert) |
`CHRON_SPLUNK_URL` and `CHRON_SPLUNK_TOKEN` must both be set for events to flow.
---
## Microsoft Sentinel integration
Stream AI session telemetry into your Microsoft Sentinel workspace via the Azure Monitor Logs Ingestion API. Events go directly from developer machines to your own Log Analytics workspace — no relay service.
**What gets sent:** session starts, message counts (role only), and masked credential detections. Message content never leaves the machine.
### Setup
**1. Register an Azure App**
Azure Portal → **Azure Active Directory** → **App registrations** → **New registration** → name it `chron-ingest`.
Note the **Application (client) ID** and **Directory (tenant) ID**.
Go to **Certificates & secrets** → **New client secret** → copy the value immediately.
**2. Create a custom table in Log Analytics**
Open your Log Analytics workspace → **Tables** → **Create** → **New custom log (DCR-based)** → name it `ChronEvents_CL`.
Add these columns (in addition to the auto-added `TimeGenerated`):
| Column | Type |
|---|---|
| `EventType` | string |
| `SessionIdPrefix` | string |
| `AiTool` | string |
| `OS` | string |
| `ChronVersion` | string |
| `Computer` | string |
| `Role` | string |
| `DetectionType` | string |
| `MaskedValue` | string |
The wizard creates a **Data Collection Endpoint (DCE)** and **Data Collection Rule (DCR)** — note both.
**3. Grant the App ingest permission**
Open the DCR → **Access control (IAM)** → **Add role assignment** → Role: **Monitoring Metrics Publisher** → Member: the `chron-ingest` app.
**4. Get the DCR Immutable ID**
Open the DCR → **Overview** → **JSON view** → copy the `immutableId` field (starts with `dcr-`).
**5. Install the CLI and run the onboarding wizard**
```bash
npm install -g chron-mcp
chron connect sentinel
# Enter tenant ID, client ID, client secret, DCE URL, DCR immutable ID, and stream name
# The wizard authenticates with Azure AD and sends a test event before saving
```
**6. Add env vars to your MCP client config**
The wizard prints the exact block to paste. For Claude Code, add to `~/.claude.json`:
```json
{
"mcpServers": {
"chron": {
"command": "npx",
"args": ["-y", "chron-mcp"],
"env": {
"CHRON_SENTINEL_TENANT_ID": "<your-tenant-id>",
"CHRON_SENTINEL_CLIENT_ID": "<your-client-id>",
"CHRON_SENTINEL_CLIENT_SECRET": "<your-client-secret>",
"CHRON_SENTINEL_DCE": "https://<your-dce>.ingest.monitor.azure.com",
"CHRON_SENTINEL_DCR_ID": "dcr-<your-immutable-id>",
"CHRON_SENTINEL_STREAM": "Custom-ChronEvents_CL"
}
}
}
}
```
Restart your AI tool to pick up the new env vars.
**7. Query in Sentinel**
Sentinel → **Logs** → paste any query from `dashboards/sentinel/`:
```kql
ChronEvents_CL
| where TimeGenerated > ago(24h)
| summarize count() by EventType, AiTool
```
Full guide with KQL queries and alert rule setup: [dashboards/sentinel/README.md](dashboards/sentinel/README.md)
### Env vars
| Env var | Description |
|---|---|
| `CHRON_SENTINEL_TENANT_ID` | Azure AD tenant ID |
| `CHRON_SENTINEL_CLIENT_ID` | App Registration client ID |
| `CHRON_SENTINEL_CLIENT_SECRET` | App Registration client secret |
| `CHRON_SENTINEL_DCE` | Data Collection Endpoint URL |
| `CHRON_SENTINEL_DCR_ID` | DCR Immutable ID (starts with `dcr-`) |
| `CHRON_SENTINEL_STREAM` | Stream name, e.g. `Custom-ChronEvents_CL` |
All six must be set for events to flow. Token refresh is automatic (1-hour Azure AD tokens, refreshed 60s before expiry).
---
## Configuration
| Env var | Default | Description |
|---|---|---|
| `CHRON_DB_PATH` | `~/.chron/chron.db` | Path to SQLite database file |
| `CHRON_KEYS_DIR` | `~/.chron/keys` | Where per-session Ed25519 signing keys are written |
| `CHRON_CONFIG_PATH` | `~/.chron/config.json` | SIEM configuration file. Point it elsewhere to keep a test run, CI job or sandbox away from real credentials |
| `CHRON_SIEM_TIMEOUT_MS` | `10000` | How long a single SIEM request may take before delivery is recorded as unknown |
| `CHRON_TRANSPORT` | `stdio` | Set to `http` to enable HTTP+SSE mode |
| `CHRON_API_KEY` | _(none)_ | Bearer token for HTTP mode |
| `PORT` | `3001` | Port for HTTP mode |
| `CHRON_LOGSCALE_URL` | _(none)_ | LogScale ingest endpoint (enables CrowdStrike integration) |
| `CHRON_LOGSCALE_TOKEN` | _(none)_ | LogScale ingest token |
| `CHRON_SENTINEL_TENANT_ID` | _(none)_ | Azure AD tenant ID (enables Sentinel integration) |
| `CHRON_SENTINEL_CLIENT_ID` | _(none)_ | App Registration client ID |
| `CHRON_SENTINEL_CLIENT_SECRET` | _(none)_ | App Registration client secret |
| `CHRON_SENTINEL_DCE` | _(none)_ | Data Collection Endpoint URL |
| `CHRON_SENTINEL_DCR_ID` | _(none)_ | DCR Immutable ID |
| `CHRON_SENTINEL_STREAM` | _(none)_ | Stream name (e.g. `Custom-ChronEvents_CL`) |
| `CHRON_SPLUNK_URL` | _(none)_ | Splunk HEC base URL (enables Splunk integration) |
| `CHRON_SPLUNK_TOKEN` | _(none)_ | Splunk HEC ingest token |
| `CHRON_SPLUNK_INSECURE` | _(none)_ | Set to `1` to skip TLS verification for local Splunk/self-signed certs |
| `CHRON_RELAY_URL` | _(none)_ | Generic relay endpoint (any SIEM or webhook) |
| `CHRON_RELAY_TOKEN` | _(none)_ | Bearer token for generic relay |
---
## HTTP+SSE mode (team / self-hosted)
For teams or remote use, run Chron as an HTTP server:
```bash
CHRON_TRANSPORT=http CHRON_API_KEY=your-key PORT=3001 npx chron-mcp
```
Point your MCP config at the URL:
```json
{
"mcpServers": {
"chron": {
"url": "https://your-server/sse",
"headers": {
"Authorization": "Bearer your-key"
}
}
}
}
```
---
## ChatGPT import
Bring your existing ChatGPT history into Chron's tamper-evident audit trail.
```bash
# From a ChatGPT data export ZIP
chron import chatgpt ~/Downloads/chatgpt-export.zip
# Or from an extracted conversations.json
chron import chatgpt ~/Downloads/conversations.json
```
**How to get your ChatGPT export:**
ChatGPT → Settings → Data Controls → Export data → wait for email → download the ZIP.
**What gets imported:**
- One Chron session per conversation, with `ai_tool=chatgpt`
- Original message timestamps from the export
- `external_ref=chatgpt:<conversation_id>` on every session (visible in `chron history`)
- Full SHA-256 hash chain across all imported messages
- Secret detection runs on all user messages
**Re-running is safe** — already-imported conversations are skipped by `external_ref` match.
After import, sessions appear in `chron history` and `chron verify` works on them the same as any natively-logged session.
---
## Your data
Your audit log lives at `~/.chron/chron.db` — a single SQLite file on your machine. Query it directly with any SQLite tool:
```bash
sqlite3 ~/.chron/chron.db \
"SELECT s.title, m.role, m.content, m.created_at
FROM messages m JOIN sessions s ON s.id = m.session_id
ORDER BY m.created_at"
```
No cloud, no analytics, and no vendor-held copy of your log. Change the location with `CHRON_DB_PATH`.
### What leaves your machine
Chron sends no usage data — there is no analytics endpoint, no account, and no
phone-home. Two kinds of packet can leave, and this is the whole list:
| What | When | Contains | Switch it off |
|---|---|---|---|
| NTP clock check | automatically, when a session is created | a 48-byte NTP request to `pool.ntp.org`. No session data, no identifier. | `CHRON_DISABLE_NTP=1`, or redirect it with `CHRON_NTP_SERVER=your.ntp.host` |
| SIEM / OTLP export | only after you configure a destination | the events you configured, to the endpoint you chose | nothing to switch off — it is inert until you run `chron connect` |
The clock check is what lets `chron verify` tell you whether a session's
timestamps were taken against a synchronised clock. It is the only packet Chron
sends without being asked, which is why it is listed first and why it has a
switch. Turn it off and `chron verify` says so, rather than reporting a clock it
never checked:
```
Clock attestation
! Clock attestation is disabled by configuration — none was requested.
```
---
## License
Copyright (c) 2026 Nivaya. All rights reserved.
Source code is public for transparency only. Cloning, forking, modification, and redistribution are not permitted without explicit written permission. See [LICENSE](LICENSE) for full terms.
TDQS
Scored across 6 tools
Each tool has a clearly distinct role: starting/resuming sessions, logging messages individually or in batch, listing sessions, retrieving full history, and verifying integrity. No overlaps in functionality.
All tool names follow a consistent verb_noun pattern in snake_case (e.g., start_session, list_sessions, verify_session), making the API predictable and easy to navigate.
Six tools cover the essential operations for an audit session manager without redundancy. The count is well-scoped for the server's purpose.
The tool set covers the full lifecycle: session creation, message logging (individual and batch), session listing, history retrieval, and integrity verification. No obvious gaps given the immutability requirement for audit logs.