{
  "markdown": "<div align=\"center\">\n  <h1>Conarium</h1>\n  <p><strong>The Third Eye for Your Company's Data.</strong></p>\n  <p>A self-hosted, governed gateway that lets AI coding assistants (Cursor, Copilot, Claude) touch your real data under a policy you write—protected values masked before they leave. When a receipt sink is configured it writes a signed, independently verifiable receipt of every access it mediates; <code>conarium-init</code> sets that sink, so the default layout does.</p>\n  \n  <p>\n    <a href=\"https://www.npmjs.com/package/@conarium-ai/core\"><img src=\"https://img.shields.io/npm/v/@conarium-ai/core?style=for-the-badge\" alt=\"npm\" /></a>\n    <a href=\"https://github.com/dogrucanemek-alt/conarium/actions/workflows/security.yml\"><img src=\"https://img.shields.io/github/actions/workflow/status/dogrucanemek-alt/conarium/security.yml?branch=main&style=for-the-badge&label=CI\" alt=\"CI\" /></a>\n    <a href=\"https://securityscorecards.dev/viewer/?uri=github.com/dogrucanemek-alt/conarium\"><img src=\"https://api.securityscorecards.dev/projects/github.com/dogrucanemek-alt/conarium/badge\" alt=\"OpenSSF Scorecard\" /></a>\n    <a href=\"https://www.bestpractices.dev/projects/14160\"><img src=\"https://www.bestpractices.dev/projects/14160/badge\" alt=\"OpenSSF Best Practices\" /></a>\n    <a href=\"https://conarium.dev\"><img src=\"https://img.shields.io/badge/Website-conarium.dev-5a8cff?style=for-the-badge\" alt=\"Website\" /></a>\n    <a href=\"https://github.com/dogrucanemek-alt/conarium/blob/main/LICENSE\"><img src=\"https://img.shields.io/badge/License-MIT-f2d79a?style=for-the-badge\" alt=\"License\" /></a>\n    <img src=\"https://img.shields.io/badge/Status-Early%20Access-ff6f80?style=for-the-badge\" alt=\"Early Access\" />\n  </p>\n</div>\n\n<br/>\n\nThe site lives at [conarium.dev](https://conarium.dev); this repository is the product.\n\n## Check it before you read the rest\n\nNothing below has to be taken on trust. There is a live receipt chain; verify it\nagainst its public key on your own machine, with no account and no data of yours:\n\n```bash\nnpm i @conarium-ai/core\ncurl -fsS https://conarium.dev/proof/chain.jsonl   -o chain.jsonl\ncurl -fsS https://conarium.dev/proof/key.pem       -o key.pem\ncurl -fsS https://conarium.dev/proof/key.pem.keyid -o key.pem.keyid\nnpx conarium-verify chain.jsonl --pubkey key.pem\n```\n\n```\nnote: tail truncation is not visible — this run did not see receipts deleted from the\nend of the file. Pin with --expect-count, --expect-last-hash, or --anchor-check.\nok: 3 receipt(s) verified (3 with undeclared model, 3 with undeclared client)\n```\n\nExit code 0. The three receipts are one ordinary read, one where five email addresses\nand a card number were masked before the model saw them, and one refusal. Change any\nfield and the recomputed hash stops matching the stored one — exit 10. Change the\nsignature instead — exit 13.\n\nThe verifier is a single file that imports nothing from the package it is checking, so\na compromised Conarium cannot talk it into a passing result. Note that it volunteers\nwhat it did *not* check, in the first line of its own output, before the good news.\n\n## Limitations\n\nWhat this repository has **not** done is in [LIMITATIONS.md](LIMITATIONS.md)\n([Türkçe](LIMITATIONS.tr.md)). The dated comparison page is\n[conarium.dev/compare.html](https://conarium.dev/compare.html) — that is the\nonly copy; this repo does not keep a second one.\n\n## Standards\n\n[draft-dogru-scitt-disclosure-evidence](https://datatracker.ietf.org/doc/draft-dogru-scitt-disclosure-evidence/)\nis an individual submission. Not adopted by an IETF working group, and it\ncarries no formal standing — an Internet-Draft is a dated public record, not a standard.\nIt is published so the receipt format can be implemented without us.\nSource files live in [`standards/`](standards/).\n\n## 👁️ The Problem\n\nPoint Cursor or Copilot at a production database and it drinks the raw stream—SSNs, credit cards, salaries, and live keys. One rogue prompt can expose your most sensitive tables. Security teams simply can't allow that.\n\n## 🛡️ The Solution: Conarium\n\nConarium acts as a high-performance **MCP (Model Context Protocol) Proxy**. It sits directly between the AI Assistant and your databases, evaluating policies in milliseconds to enforce row limits and mask PII (Personally Identifiable Information) on the wire.\n\nThe AI gets the context it needs to write code; the values your policy protects are masked before they reach it. Masking hides a value — it does not make it unlearnable, and where a request language allows predicates over a protected column, an allowed query can still answer questions about one. `protectedColumns` is the narrower answer to that, and the limit is stated in [LIMITATIONS.md](LIMITATIONS.md) rather than left for you to discover.\n\n### Key Features\n\n- **Inline PII Masking:** Emails, IDs, cards, and secrets are redacted in the response stream (`[MASKED_PII]` / `[MASKED_SECRET]`) before the model sees a single character.\n- **Allow / Deny Lists:** Whitelist what AI can access. Your `secrets` and `financials` tables stay invisible.\n- **Row Caps:** Hard per-query limits. Prevent the silent exfiltration of millions of rows. \n- **Tamper-Evident Audit Ledger:** Every access *through Conarium* is logged (who, what, when, rows, decision). Hash-chained, which makes alteration and mid-chain removal detectable — not impossible: a file on disk can still be deleted or truncated, and catching truncation needs a pin from outside the file (see Coverage & Reconciliation below). PII-safe: no raw PII is written to the logs.\n- **Verifiable Receipts:** Ed25519-signed, independently verifiable receipts — see below.\n- **Per-person masking profiles:** what to mask for an AI agent is not what to mask for the data controller. A named profile relaxes masking for one identified person, and the receipt records which profile applied — see below.\n- **Coverage & Reconciliation:** a signed coverage declaration over the receipt chain (`conarium-coverage`), plus two-sided reconciliation against the database's own query counters (`conarium-reconcile`) — DB-recorded activity that no receipt covers is surfaced instead of staying invisible.\n- **100% Self-Hosted:** Runs entirely on your infrastructure. Nothing we ship transmits your data anywhere: raw protected values stay inside your perimeter, and what reaches your AI client is the policy-approved disclosure — whose exact bytes the receipt records (`disclosure.hash`). Saying your data never leaves at all would be the wrong claim: releasing a governed disclosure to an assistant is the job. The gateway makes exactly one outbound request that is not yours: at startup it asks the public npm registry whether a newer version exists, and prints one line to stderr if so. It sends nothing about you — no identifier, no config, no counts — and a remote gateway nobody looks at for weeks is the reason it exists at all. Disable it with `CONARIUM_NO_UPDATE_CHECK=1`, or point it at your internal mirror with `CONARIUM_NPM_REGISTRY`. It has a 2-second timeout and never blocks or fails startup. We list it here because a governance product that makes an undisclosed outbound connection has already lost the argument.\n- **MCP-Native:** Works out of the box with **Cursor**, **GitHub Copilot**, **Claude Code**, and **Codex**.\n\n### Verifiable Receipts\n\nConarium can emit portable **receipts** (Art. 12 / 19 shaped) that a third party\nverifies offline with a single file — no Conarium install required.\n\n**Official claim (do not widen):** A Conarium Receipt proves that the records\n**still in the file** have not been altered, reordered, or backdated after they\nwere created, and that none were **removed from the middle** of the chain\n(`prevHash` / `seq`). It does **not** prove they were correct at the moment of\ncreation. It also cannot, by itself, prove that records were not **dropped from\nthe end**: a shorter leftover chain is still internally consistent. Catching\ntail truncation needs a pin from outside the file — `--expect-count`,\n`--expect-last-hash`, an OpenTimestamps anchor, or `conarium-reconcile` against\nthe database's own counters.\n\n*(TR)* Conarium Makbuzu, dosyada **hâlâ duran** kayıtların oluşturulduktan sonra\ndeğiştirilmediğini, **ortadan** silinmediğini, yeniden sıralanmadığını ve geriye\ndönük tarihlenmediğini kanıtlar. **Oluşturma anında doğru olduğunu kanıtlamaz.**\nSondan kesmeyi tek başına göremez: kalan zincir tutarlıdır, yalnızca kısadır.\n*(/TR)*\n\n```bash\n# Generate an Ed25519 keypair (private PEM + .pub.pem + .keyid sidecars).\n# The .keyid sidecars are not optional: without them the verifier answers 13\n# for every receipt, which reads like tampering and is not.\nnpx conarium-init\n\nexport CONARIUM_AUDIT_SIGNING_KEY=./audit-ed25519.pem\n\n# init writes keys and config, not receipts: your own audit file does not exist\n# until the gateway has served a query. The three commands below therefore run\n# against the demo chain downloaded above, so they work as written — swap in\n# your own sink (conarium.config.json → audit.sink) once it has records.\n\n# Verify a receipt chain (exit 0 = the records *in the file* are intact)\nnpx conarium-verify chain.jsonl --pubkey key.pem\n\n# Pin length / last hash if you need to catch records dropped from the end\nnpx conarium-verify chain.jsonl --pubkey key.pem --expect-count 3\n\n# Check the OpenTimestamps sidecar. The demo chain ships without one, so this\n# answers 14, deliberately not 0: an absent anchor is not a verified anchor.\n# A sidecar that exists but is not yet confirmed → exit 0 with a warning.\nnpx conarium-verify chain.jsonl --pubkey key.pem --anchor-check\n```\n\nA second verifier, Go and the standard library only, is in [`verifiers/go`](verifiers/go). `go build -o conarium-verify .` then the same arguments as `conarium-verify`; `test-vectors/` is the contract.\n\nAnchoring is a separate step. Stamp a document with `npx conarium-stamp <file>`,\nor submit a chain-head hash with `npx conarium-anchor-service`.\n`CONARIUM_ANCHOR_SINK=opentimestamps` selects the in-tree calendar client those\ntools use; it does not stamp receipts as they are written. Upgrade pending\nproofs later with `npx conarium-anchor-upgrade ./audit.jsonl.anchors.jsonl`.\nThe client is in-tree (Node `crypto` + calendar HTTPS). It does not install\n`javascript-opentimestamps`. See [LIMITATIONS.md](LIMITATIONS.md).\n\n### Per-person masking profiles\n\nMasking that is correct for an AI agent is wrong for the person who owns the data.\nThe owner asking *\"which customer owes the most\"* needs the name; the assistant\nsummarising revenue does not. Answering that with a global on/off switch would\ndisable the product's only real guarantee, so masking resolves **per person**:\n\n```jsonc\n{\n  \"policy\": {\n    \"allowTables\": [\"zion.customers\", \"zion.orders\"],\n    \"maskColumns\": [\"*.customer_name\", \"*.email\", \"*.phone\"],  // default: everyone\n    \"maxRows\": 100,\n\n    \"profiles\": {\n      // The controller sees customer names; email and phone stay masked.\n      \"controller-full\": { \"maskColumns\": [\"*.email\", \"*.phone\"], \"maxRows\": 1000 }\n    },\n    \"actorProfiles\": { \"emekcan\": \"controller-full\" }\n  }\n}\n```\n\nDeliberately narrow, because this is the one feature that can *loosen* protection:\n\n- A profile may override **`maskColumns`, `maxRows` and `maskLabelledNames` — and\n  nothing else.** Table, tool and connector permissions stay global; a profile can\n  never widen what is reachable, only what is legible within it.\n  `protectedColumns` is not overlayable: a profile that could drop it would be a\n  per-person back door.\n- **Per-user tokens only.** An actor authenticated with a shared token never\n  receives a profile. \"Whoever holds this string sees unmasked PII\" is precisely\n  the failure this product exists to prevent.\n- **Fail-closed everywhere else:** no actor, unlisted actor, or a profile name that\n  does not exist all fall back to the base policy, never to a wider one.\n- **The content scanners still run.** Email / national-ID / phone / card /\n  IBAN / secret detectors are not overridable at all, so those stay masked in free\n  text no matter which profile applied. IBAN is accepted only when ISO 7064\n  mod-97-10 holds. Passport MRZ (TD3, 7-3-1 check digits) is on by default and\n  likewise cannot be turned off by a profile — only `policy.detectors.mrz: false`\n  on the **base** policy opts it out. IP addresses are **off** until\n  `policy.detectors.ip: true`. Name masking is the one detector a profile can\n  switch off (`maskLabelledNames: false`), because the controller reading their\n  own customer list is the case this feature exists for.\n- **The receipt says which profile applied** — `policy.id` becomes\n  `conarium.policy/<profile>`, inside the signed hash. An access made under a\n  relaxed profile cannot later be presented as having been fully masked. This is\n  what keeps the audit story honest: the point was never \"nobody sees PII\", it is\n  \"every access is governed, and the evidence says under which rules.\"\n\n### Names in free text\n\nEvery other identifier has a shape. An email has an `@`, a national ID has a\nchecksum, a card has a length — a regex decides, and the decision reproduces.\nA name has no shape, so `maskColumns` was the only thing catching one, and a name\ntyped into a free-text `note` reached the model verbatim.\n\nTwo deterministic passes close the part of that gap that can be closed honestly:\n\n| Pass | What triggers it | Example |\n|---|---|---|\n| **Carry-over** | The value is one **this policy already masks** in some column | `customer_name` is masked, so `note: \"Ayşe Demir called\"` is masked too — including across rows |\n| **Labelled** | The **text itself** marks it: a title or a field label | `Sn. Ahmet Yılmaz`, `Yetkili: Ayşe Demir`, `customer: John Smith` |\n\n**What this does not do, deliberately: a bare name in running prose is not\ndetected.** \"Ahmet called yesterday\" goes through. Catching that needs NER — a\nmodel, a dictionary and a confidence score — and every decision this gateway\nmakes is meant to be reproducible from the rule alone, by someone who does not\ntrust us. A probabilistic masker would also be a probabilistic *receipt*. Tools\nthat do run NER (Presidio-based ones, for instance) cover more entity types; they\nbuy that with a confidence threshold. Neither position dominates — this one is\nstated so an auditor knows which one they are holding.\n\n**Still not caught by content scanners — by design, not by omission:** street\naddresses and bare names. An address detector cannot tell \"Atatürk Caddesi No:15\"\nfrom \"Atatürk Barajı\" without a gazetteer. A name detector cannot tell Deniz /\nGüneş / Umut from the words. Both would need a dictionary or a model; this\ngateway's decisions are deterministic. Close those gaps with `maskColumns` (column\nnames) and `conarium-suggest-policy` (a name-based *guess* that does not write\nyour config).\n\nIP addresses are caught **when you turn them on** (`policy.detectors.ip: true`).\nThey are off by default: a server IP is not always personal data, and a mask you\ncannot disable breaks SOC work. `1.2.3.4` is structurally a valid IPv4 address;\nwhen the detector is on it is masked, even if you meant a version number. Dates\n(`13.08.2026`) and amounts (`1.250,00`) are not IPv4.\n\nPassport numbers in free text are not caught. **MRZ is:** two TD3 lines × 44\ncharacters, `P` in position 1, 7-3-1 check digits. A checksum miss is not an MRZ\nand is left alone. TD1/TD2 are not implemented.\n\nHTML `&#64;` / `&#x40;`, JSON `\\u0040`, and `%40` are masked when they sit inside\nan email-shaped token. A lone `5&#64; store` or `C:\\path\\u0040abc` is left alone.\nOne decode pass; `&amp;#64;` is not chased.\n\nA TCKN split across two similarly named fields on the same row (`tckn_1` /\n`tckn_2`) is masked when the concatenation checksums. Unrelated columns are not\ncombined.\n\nZero-width characters, fullwidth digits / `＠`, and unicode dashes are stripped\nor mapped to ASCII *before* the detectors — that pass is not a general encoding\ndecoder; wrapped base64/hex *tokens* inside a field are masked only when they\ndecode to an existing detector hit.\n\n**Scan length.** A single text field longer than `policy.scanCharCap` (default\n16 384; env `CONARIUM_SCAN_CHAR_CAP` overrides) is replaced with `[MASKED_PII]`\nas a whole, even when it contains no identifier. The scanner is not skipped:\nskipping would mean a long note, JSON blob, or log line is the way past masking.\nThis is a **usability** setting. Raising it grows scan cost quadratically — a\n40 KB alphanumeric field was ~1 s on the unbounded email regex before that regex\nwas bounded. `maskedCount` records that a decision was made.\n\nCarry-over ignores values under three characters (a two-character value matches\neverywhere and would shred the output) and matches on Unicode word boundaries, so\n`Ali` is masked in `Ali onayladı` but not inside `Kalite`.\n\n### Coverage & reconciliation (bypass detection)\n\nReceipts prove what went **through** the gateway. Reconciliation asks the database\nwhat it saw, and compares:\n\nNeither command invents its inputs and `conarium-init` does not create them, so\nboth answer **20 (input missing)** until you have produced them: `declaration.json`\nis your own period-and-scope statement ([`docs/RECEIPT-SPEC.md`](docs/RECEIPT-SPEC.md)\nnames the fields), and the two snapshots come from\n[`scripts/pg-snapshot.sql`](scripts/pg-snapshot.sql).\n\n```bash\n# One-sided: signed coverage declaration over a period + declared scope\nnpx conarium-coverage ./declaration.json --pubkey ./audit-ed25519.pub.pem --receipts ./receipts.jsonl\n\n# Two-sided: reconcile the DB's own per-role query counters against receipts.\n# Snapshots come from pg_stat_statements (scripts/pg-snapshot.sql), taken at\n# window start and window end with a dedicated DB role per gateway instance.\nnpx conarium-reconcile --before before.json --after after.json --receipts ./receipts.jsonl\n# exit 0  = every DB query pattern in the window is attributable to a receipt for\n#           the same table (object attribution, not per-statement coverage —\n#           see LIMITATIONS.md)\n# exit 40 = the DB recorded activity no receipt covers — the gateway may have\n#           been bypassed, or the receipt sink failed\n```\n\nThe language is deliberate: absence is reported as **\"access NOT RECORDED\"** /\n**\"not receipted\"**, never \"no access occurred\" — an absent record is ambiguous\nby nature, and a tool that pretends otherwise is lying to its auditor.\n\nRun against our own production ERP the day it shipped, including a real bypass we\nperformed on ourselves and the tool caught:\n[`docs/dogfood/2026-08-06-reconcile.md`](docs/dogfood/2026-08-06-reconcile.md).\n\nFull schema, exit codes, and known gaps: [`docs/RECEIPT-SPEC.md`](docs/RECEIPT-SPEC.md).\n\n### Countersigning (the part you cannot do for yourself)\n\nReceipts prove what went **through** the gateway. Reconciliation proves nothing\nwent **around** it. Both are yours, self-hosted, and signed by your own key —\nwhich is exactly what an auditor discounts: you kept the record, you signed it,\nand you stored it. A countersignature answers that by putting a **second party**\non the same chain head.\n\nThe service is in this package, so you can run your own and sign your own heads\n— useful for a second internal custodian, and pointless against the objection\nabove. What makes it worth anything is that the signer is not you.\n\n```bash\n# Run the endpoint. It refuses to start without a signing key or a token file:\n# with neither present the three lines below exit 2 and name what is missing,\n# which is the intended answer, not a failed install. Generating both is in\n# deploy/anchor-service/.\nCONARIUM_ANCHOR_TOKENS=./anchor.tokens.json \\\nCONARIUM_ANCHOR_SIGNING_KEY=./anchor.pem \\\nCONARIUM_ANCHOR_BASE_URL=https://anchor.example.com \\\nnpx conarium-anchor-service\n\n# Verify a countersignature you were given — offline, no network, no package.\n# record.json is what the endpoint returned to you; without it, exit 20.\nnpx conarium-countersign-verify ./record.json --pubkey ./anchor.pub.pem\n# exit 0  = signature valid (and inclusion valid if a proof or --log-url was given)\n# exit 13 = signature invalid / unknown keyId\n# exit 14 = inclusion proof present and false\n# exit 15 = the log could NOT be checked — deliberately not the same as 14\n```\n\nThe log is a hash chain: entries are appended, never rewritten, and an OTS\ntimestamp covers the head rather than each submission. What a countersignature\nproves — and, just as importantly, what it does not — is written out in\n[`docs/COUNTERSIGN.md`](docs/COUNTERSIGN.md), together with what a leaked\nsigning key would cost.\n\nPro is the hosted countersignature — someone other than you signs the chain\nhead. **$20/month or $200/year — save $40.** **One period, not a subscription.**\nIt does not renew by itself — when the period ends, access ends and you can\nbuy it again. 14-day no-questions refund; after that, no partial refunds.\nVAT added where applicable. Checkout is not open yet:\n[conarium.dev/buy](https://conarium.dev/buy) redirects to the waitlist form\nuntil the payment path goes live, so these terms are the published price\nrather than something you can pay for today. The binary above is what you\nrun yourself; Pro is the second signer. Shipped in the package since 0.2.16;\nthe VERAX-operated endpoint is not open to customers yet. Business stays on the waitlist:\nscheduled reconciliation, coverage alerts and the signed period report are\nin the contract, **not shipped yet**.\n\n### Implementing the format yourself\n\nThe receipt is meant to outlive this implementation, so it ships with\nconformance vectors — thirteen frozen cases plus a machine-readable manifest in\n[`test-vectors/`](test-vectors/):\n\n```bash\nnpm run test:vectors     # our verifier against the frozen cases\n```\n\nPoint your own verifier at each `receipts.jsonl`, pass the arguments listed in\n`manifest.json`, and compare the exit code. `expected-hashes.json` gives the\ncanonical JCS → SHA-256 hashes so you can check your canonicalisation without\nneeding our private key, which is deliberately not published.\n\nThe vectors found two things in this repository on their first run: a schema\ncheck that reported a structurally invalid receipt as *tampered*, and a wrong\nassumption of ours about unsigned receipts. Both are now frozen as cases 007\nand 008.\n\n### Anchoring your chain (optional)\n\n`conarium-stamp` anchors a file to the OpenTimestamps calendars, and\n`conarium-anchor-upgrade` fills in the Bitcoin block height once it lands.\nThose two are all most setups need.\n\nIf you would rather expose anchoring as a small service — for several\ngateways, or to hand an auditor a stable URL —\n[`bin/conarium-anchor-service.mjs`](bin/conarium-anchor-service.mjs) is one:\nit submits hashes, retains proofs, serves the raw `.ots` at a permanent path,\nand upgrades pending anchors on a timer.\n\nIt is code you run, not a service we operate — there is no hosted instance to\nsign up for. It also serves the raw proof precisely so a third party can verify\nwith the reference OpenTimestamps client and ignore the service entirely. An\nanchoring endpoint you have to trust would defeat the purpose of anchoring.\n\nSigning is fail-closed: set `CONARIUM_AUDIT_SIGNING_KEY` and/or\n`CONARIUM_AUDIT_HMAC_KEY`, or explicitly `CONARIUM_AUDIT_UNSIGNED=1` for throwaway setups.\nKey rotation: keep prior public PEMs in `CONARIUM_AUDIT_TRUST_PUBKEYS` (`,` / `;`\nseparated). After the first signed audit line, every later line must carry `sig`.\n\n### Where this sits among similar projects\n\nConarium is **not** the first project to produce signed, verifiable receipts for AI\nactivity. [Acta](https://github.com/VeritasActa/Acta),\n[Emilia Protocol](https://github.com/emiliaprotocol/emilia-protocol),\n[AuthProof](https://github.com/Commonguy25/authproof-sdk),\n[Agent Receipts](https://github.com/agent-receipts) and\n[Invariant SVR](https://github.com/Jasonleonardvolk/invariant-svr) all do a form of this,\nand some are ahead of us on standardisation — Acta and Emilia both have IETF\nInternet-Drafts. Related research: Aegon (arXiv 2604.06693), Decentralised Trust Layers\n(ACM Web Conf 2026), and ISO/IEC TS 27560:2023 for signed consent records.\n\nThose receipts attest to what an agent **did**. A Conarium receipt attests to what the\nmodel was **prevented from seeing** — because the component that masks the data is the\nsame component that signs the record. Enforcement and evidence are one part here, not\ntwo systems that have to be reconciled.\n\nWhat we will defend: Conarium is the only implementation we are aware of that combines\nall three of **(1) inline enforcement** (policy + masking), **(2) a portable,\noffline-verifiable receipt** of that enforcement, and **(3) coverage\nreconciliation** — checking the database's own query counters against the receipt\nchain, so access that bypassed the gateway is surfaced instead of staying\ninvisible. Signing receipts without enforcing is common; enforcing without\nportable receipts is common; reconciling both sides against the data source's\nown bookkeeping is the part we have not found elsewhere. Measured end to end on\na real operating company's live ERP — 121,374 records, 121,366 identities\nmasked, 485,496 fields masked, zero leaked to the model\n([Governance Report 001](https://conarium.dev/report-001.html)).\n\n**What that number is, and what it is not.** It comes from a batch run against\nour own company's ERP, and what backs it is a **hash-chained audit file** of 123\nlines whose arithmetic you can re-add yourself and whose chain was re-verified 17\ndays later. What does *not* back it is a **receipt chain**: that run emitted audit\nentries, not signed portable receipts, and its actor is a batch service identity,\nnot a person. So if you ask \"show me the receipts for those 485,496 fields\", the\nhonest answer is that they do not exist — the receipt chain is a separate and much\nsmaller measurement. Scale and offline verifiability are two different claims here,\nand we would rather draw that line ourselves than have you find it. The mechanism\nis verifiable without trusting us; this particular figure is our own measurement,\nand [Governance Report 001](https://conarium.dev/report-001.html) lists its limits.\n\nThat claim is hedged on purpose, and [`docs/PRIOR-ART.md`](docs/PRIOR-ART.md) is the\nevidence behind it: eleven projects — ten checked on 6 August 2026 and Vaara added\non 19 August — what each one has, the closest academic prior art\n([Sello / *Notarized Agents*](https://arxiv.org/html/2606.04193v1), which names this\ngap better than we did), and nine things we could **not** verify. If you know of an\nimplementation combining all three, open an issue and it will be corrected.\n\n⚠️ The Vaara row narrowed this claim rather than confirming it. That project\n*specifies* a coverage reconciliation in its design documents; a search of its\ntree found no code running it, so the row reads \"specified, not found\nimplemented\". The idea is not ours alone — the running code, as far as this scan\nreaches, still is, and the file says so above its own table.\n\n---\n\n## 🏗️ Architecture (The Trifecta)\n\nConarium operates on a strict tripartite architecture, balancing power between three pillars:\n\n```mermaid\ngraph LR\n    A([AI Assistant\\nCursor / Copilot]) -- \"MCP Query\" --> B{The Gateway\\nConarium Proxy};\n    B -- \"Intercept & Parse\" --> C[The Engine\\nGovernance & Regex];\n    C -- \"Execute Query\" --> D[(Your Database\\nPostgres / SQL Server / Oracle)];\n    D -- \"Raw Data\" --> C;\n    C -- \"Mask & Cap\" --> B;\n    B -- \"Sanitized Data\" --> A;\n    C -. \"Write Log\" .-> E[The Ledger\\nAudit DB];\n    \n    style A fill:#05070f,stroke:#5a8cff,stroke-width:2px,color:#fff\n    style B fill:#05070f,stroke:#ff6f80,stroke-width:2px,color:#fff\n    style C fill:#05070f,stroke:#6fe0e0,stroke-width:2px,color:#fff\n    style D fill:#05070f,stroke:#f2d79a,stroke-width:2px,color:#fff\n    style E fill:#05070f,stroke:#838dad,stroke-width:2px,color:#fff\n```\n\n1. **The Gateway:** A proxy that speaks fluently to LLM assistants.\n2. **The Engine:** Evaluates JSON policies, regex scans, and row caps in milliseconds.\n3. **The Ledger:** A tamper-evident audit log recording every query and decision it mediates.\n\n---\n\n## 🚀 Quick Start\n\n```bash\n# 1. Install\nnpm i @conarium-ai/core\n\n# 2. Write a fail-closed skeleton (config + Ed25519 pair + .keyid sidecars)\nnpx conarium-init\nexport CONARIUM_AUDIT_SIGNING_KEY=\"$PWD/audit-ed25519.pem\"\n\n# 3. Check the install before trusting it. Until step 4 points the config at a\n#    reachable DSN, doctor reports the placeholder host unreachable and exits 1.\n#    That FAIL is the check working, not the install being broken — it is the one\n#    thing a gateway must not be quiet about, because it keeps running with zero\n#    connectors and looks healthy while serving nothing.\nnpx conarium-doctor\n\n# 4. Point the generated conarium.config.json at your read-only DSN,\n#    fill policy.allowTables, then run the governed MCP gateway\nnpx conarium\n```\n\nStep 3 is not decoration. A missing config file does **not** stop the gateway —\nit starts with zero connectors and governs nothing — and a connector that fails\nto connect is logged, not raised. `conarium-doctor` names both, exits `1` when\nsomething is wrong so it can gate a deployment, and never prints a secret, so\nits output is safe to paste into an issue.\n\n<details>\n<summary>From source instead</summary>\n\n```bash\ngit clone https://github.com/dogrucanemek-alt/conarium.git\ncd conarium\nnpm install && npm run build\n# The repository already ships a conarium.config.json, so init refuses rather\n# than overwrite it (exit 1). Pass --force only if you want it regenerated.\nnode bin/conarium-init.mjs --force\nnode bin/conarium-doctor.mjs --no-net\nnpm start\n```\n\n</details>\n\n`conarium-init` refuses to overwrite existing files unless you pass `--force`.\nIt never prints the private key — only its path.\n\n### Desktop shortcut for the console\n\nThe policy editor is `npx conarium-console`. It still binds `127.0.0.1` and\nstill requires a token. These two commands only add a door on the desktop:\n\n```bash\nnpx conarium-console --install-shortcut\nnpx conarium-console --uninstall-shortcut\n```\n\n| | |\n|--|--|\n| Windows | `.lnk` on the desktop (console window minimized) |\n| macOS | `~/Applications/Conarium Console.app` |\n| Linux | `~/.local/share/applications/conarium-console.desktop` |\n\nDouble-click starts the same console, waits until the port is listening, then\nopens your browser. The token is not put in the URL; a one-time nonce (≤30s)\nis exchanged for a session cookie. If a shortcut with that name already exists,\na `-2` suffix is used instead of overwriting.\n\nExport `CONARIUM_CONSOLE_TOKEN` before `--install-shortcut` so the launcher\ncan read it from `~/.conarium/console.token` (created `0600`). The shortcut\nfile itself does not contain the token.\n\nThe shortcut uses `assets/conarium-mark.ico` / `.icns` / `-512.png`,\nall from the same SVG. If those files are missing the shortcut is still\ncreated and the command warns.\n\nThe console **Makbuzlar** tab lists signed receipts from `audit.receiptSink`\n(newest first) and shows the same receipt HTML as `demo.conarium.dev/proof`.\nIt verifies the hash chain and writes **zincir sağlam** or **kırık (satır N)**.\nIf the sink is empty or unset, it says so — it does not invent a sample\nreceipt. Audit Logs remain the unsigned playground trail; they are not receipts.\n\nWhen the package is on npm, the same binaries will ship in the tarball\n(`conarium-init`, `conarium-doctor`, `conarium-verify`, `conarium-suggest-policy`).\nUntil then, run them from this repository as above.\n\n### Before you file a bug: run the doctor\n\n`conarium-doctor` checks the things that fail quietly. Two of them matter most:\na **missing config file does not stop the gateway** — it starts with zero\nconnectors and governs nothing — and a **connector that cannot connect is logged,\nnot raised**, so the process looks healthy while serving nothing. The doctor also\ncatches the missing `<pubkey>.keyid` sidecar, which makes every receipt verify as\n`13` (reads like tampering, isn't).\n\nIt exits `0` when clean and `1` when something is wrong, so it can gate a\ndeployment. **It never prints a secret** — passwords, tokens and key material are\nreported as shape only (`postgresql://appuser@db.internal:5432/prod (password\nset, not shown)`), which means the output is safe to paste into an issue or an\nemail.\n\nConarium speaks MCP over **stdio**, so your AI assistant launches it as a command. Add this to your MCP client config (e.g. Cursor):\n\n```json\n{\n  \"mcpServers\": {\n    \"conarium\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"--package=@conarium-ai/core\", \"conarium\", \"--config\", \"/path/to/your/conarium.config.json\"]\n    }\n  }\n}\n```\n\n## ⚙️ Configuration (Policy as Code)\n\nControl access using a simple `conarium.json` policy file:\n\n```json\n{\n  \"maxRows\": 50,\n  \"allowTables\": [\"public.customers\", \"public.orders\"],\n  \"denyTables\": [\"public.secrets\", \"public.financials\"],\n  \"maskColumns\": [\"email\", \"ssn\", \"*.card\", \"*.api_key\"],\n  \"protectedColumns\": [\"*.email\", \"customers.tckn\"],\n  \"allowConnectors\": [\"postgres-main\", \"docs\"]\n}\n```\n\nAnything not in `allowTables` is denied by default; matched `maskColumns` are redacted to `[MASKED_PII]` before the data ever reaches the model.\n\n`protectedColumns` uses the same glob syntax. Every pattern is also masked in\nthe result. In addition, that column may not appear in a predicate (`WHERE`,\n`HAVING`, `JOIN … ON`, `ORDER BY`, `GROUP BY`) or a derived `SELECT` expression\n— the query is refused. A bare `SELECT email` is still allowed and comes back\nmasked. Omit the field and behaviour is unchanged. A profile cannot set it.\n`mssql` / `oracle` refuse to boot if the field is non-empty: those gates cannot\nwalk predicate positions, and this product does not claim a rule it cannot\nenforce.\n\n`policy.dialect` selects the SQL gate the `query` tool uses: `postgres` (omitted default), `mssql`, or `oracle`. It is the operator's declaration — Conarium does not guess the dialect from the statement. A typo or `mysql` rejects the config.\n\n> **Connectors are fail-closed.** `allowConnectors` is a strict allow-list:\n> if it is missing or empty, **no** connector is permitted (previously an empty\n> list meant \"allow all\"). If you configure connectors, you must list them here —\n> otherwise the server refuses to start and tells you exactly which field to add.\n> `denyConnectors` still takes precedence over `allowConnectors`.\n\n### `policy.detectors` and `policy.scanCharCap`\n\nIdentity detectors — TCKN, card, IBAN, email — cannot be switched off. A config\nthat tries (`detectors: { tckn: false }`) is rejected at load. That is the\nproduct: masking that a bank can disable from a JSON file is not masking.\n\n| Key | Default | Why |\n|---|---|---|\n| `detectors.ip` | `false` | A server IP is not always personal data. A mask with no off switch breaks SOC (\"how many requests from this address?\"). Opt in when the column really is a client address. |\n| `detectors.mrz` | `true` | A passport MRZ is identity and has check digits. Turn off on the base policy if you do not handle travel documents. |\n| `scanCharCap` | `16384` | Usability. Fields longer than this are replaced whole (`[MASKED_PII]`), never skipped. Env `CONARIUM_SCAN_CHAR_CAP` overrides. Raise it and scan cost grows quadratically. Ceiling 1 048 576. |\n\n```json\n{\n  \"scanCharCap\": 32768,\n  \"detectors\": { \"ip\": true }\n}\n```\n\n### `policy.customPatterns`\n\nFormats the built-in detectors do not know — a bank customer number, a\nhouse account code — can be registered as extra rules on the **same**\nscanner. This is not a second masking path and it does not replace\n`maskColumns`.\n\nEach rule needs a name (what the receipt records), a pattern, optional\ncolumn globs, and a mask label. An optional `sample` is what `conarium-doctor`\ntries the compiled pattern against — compile success is not a catch. A\nbroken or ReDoS-shaped pattern rejects the config; the pattern and the\nsample are never written to logs, receipts, or doctor output.\n\n```json\n{\n  \"customPatterns\": [\n    {\n      \"name\": \"teb-hesap\",\n      \"pattern\": \"HSP-[0-9]{8}\",\n      \"columns\": [\"*.hesap_no\"],\n      \"label\": \"[MASKED_HESAP]\"\n    }\n  ]\n}\n```\n\nQuantifiers must be bounded (`{8}`, `{4,12}`). `+`, `*`, nested groups and\nlookaround are rejected at load. A rule names a format you already know;\nit does not invent one.\n\n`conarium-suggest-policy --sql schema.sql` prints a `maskColumns` guess from\ncolumn names (`*name*`, `*address*`, `*tckn*`, …). It does not write your\nconfig. The first line of the output says so.\n\n## 🗺️ Roadmap\n\nConarium is **early access** — and honest about what's real:\n\n**Shipping now:** governed MCP gateway (stdio + HTTP) · deterministic PII masking,\nincluding labelled names in free text · allow/deny + row caps · per-person masking\nprofiles · tamper-evident hash-chained audit ledger · Ed25519-signed receipt per access\nonce a receipt sink is configured, with an offline verifier · signed coverage\ndeclarations · two-sided reconciliation\nagainst the database's own counters · OpenTimestamps anchoring and an optional\nanchoring service · conformance vectors · SQL gate: Postgres, Microsoft SQL Server, Oracle\n(MySQL is not implemented; Oracle synonyms and database links are not resolved — see LIMITATIONS) · Postgres, Supabase, docs, OpenAPI, Jira\nand Slack connectors · `conarium-init` / `conarium-doctor` via `npx` (`@conarium-ai/core`).\n\n**Next:** consent binding ([spec published](docs/CONSENT-BINDING-SPEC.md), no code —\npatent review first) · a second independent implementation of the receipt format ·\nper-user identity bound to an identity provider rather than an operator token map.\n\n**Deliberately not planned**, so nobody waits for it:\n\n- **LLM-based \"semantic\" masking.** The gate is deterministic on purpose. A\n  probabilistic mask would make a probabilistic receipt, which is not a receipt.\n- **Hosted cloud console.** Self-hosted is the claim; a hosted console would put\n  us in the data path we tell you we are not in.\n- **No SOC 2 for us.** At this stage the priority is independent penetration\n  testing and implementation-level assurance rather than organisational\n  certification. This is about our certification, not yours: the signed receipts\n  and coverage declarations are yours to show your own auditor, and whether they\n  satisfy a given audit is between you and that auditor. If we ever hold your\n  data, or an engagement turns on the certificate itself, this line changes first.\n- **The OpenSSF Best Practices badge above is self-certification, not an audit.**\n  We answered its 67 questions and published the answers; anyone can read them at\n  [project 14160](https://www.bestpractices.dev/projects/14160) and check each one\n  against this repository. That is worth something — the answers are falsifiable —\n  and it is not the same thing as someone independent having looked. Three of the\n  67 are marked not-applicable and say why. The Scorecard badge beside it is\n  machine-measured and includes a `Code-Review` score of 0, because pull requests\n  here are merged without a second approver.\n\nKnown gaps: [LIMITATIONS.md](LIMITATIONS.md), the README above,\n[`docs/RECEIPT-SPEC.md`](docs/RECEIPT-SPEC.md),\n[`docs/BENCHMARK.md`](docs/BENCHMARK.md), and\n[`docs/API-STABILITY.md`](docs/API-STABILITY.md).\n\n## 📜 License\n\nMIT — all of it, including the verifier, the reconciliation tooling and the\nanchoring service. There is no feature held back for a paid tier; the code\nis MIT. What [conarium.dev](https://conarium.dev) sells is a second signer\n(Pro) and, later, operated coverage (Business — not shipped yet) — not\naccess to code.\n",
  "bytes": 39626,
  "sha": "9a470711861beb43ae7dbed5298791bd3a729a0ce9e480d977aa29b081d8c4bf",
  "repo_slug": "dogrucanemek-alt/conarium",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dogrucanemek_alt_conarium_7888f95e/readme"
}