Conarium
MCP serverDatabases & dataGoverned database access for AI assistants: masking, signed receipts, coverage reconciliation.
Unavailable. This server has no hosted endpoint yet, so ahel can't serve it.
Add to setup to save this item as a reference. ahel cannot run it, and signing in will not install it.
Getting started
- Save this item in Your setup as a reference.
- Read the source or reference documentation for its setup requirements. Saving it here does not connect it to your AI.
- Check this page for availability before trying to install it through ahel.
From the project's README
As published by dogrucanemek-alt/conarium in README.md.
The site lives at conarium.dev; this repository is the product.
Check it before you read the rest
Nothing below has to be taken on trust. There is a live receipt chain; verify it against its public key on your own machine, with no account and no data of yours:
npm i @conarium-ai/core
curl -fsS https://conarium.dev/proof/chain.jsonl -o chain.jsonl
curl -fsS https://conarium.dev/proof/key.pem -o key.pem
curl -fsS https://conarium.dev/proof/key.pem.keyid -o key.pem.keyid
npx conarium-verify chain.jsonl --pubkey key.pem
note: tail truncation is not visible — this run did not see receipts deleted from the
end of the file. Pin with --expect-count, --expect-last-hash, or --anchor-check.
ok: 3 receipt(s) verified (3 with undeclared model, 3 with undeclared client)
Exit code 0. The three receipts are one ordinary read, one where five email addresses and a card number were masked before the model saw them, and one refusal. Change any field and the recomputed hash stops matching the stored one — exit 10. Change the signature instead — exit 13.
The verifier is a single file that imports nothing from the package it is checking, so a compromised Conarium cannot talk it into a passing result. Note that it volunteers what it did not check, in the first line of its own output, before the good news.
Limitations
What this repository has not done is in LIMITATIONS.md (Türkçe). The dated comparison page is conarium.dev/compare.html — that is the only copy; this repo does not keep a second one.
Standards
draft-dogru-scitt-disclosure-evidence
is an individual submission. Not adopted by an IETF working group, and it
carries no formal standing — an Internet-Draft is a dated public record, not a standard.
It is published so the receipt format can be implemented without us.
Source files live in standards/.
👁️ The Problem
Point 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.
🛡️ The Solution: Conarium
Conarium 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.
The 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 rather than left for you to discover.
Key Features
- 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. - Allow / Deny Lists: Whitelist what AI can access. Your
secretsandfinancialstables stay invisible. - Row Caps: Hard per-query limits. Prevent the silent exfiltration of millions of rows.
- 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.
- Verifiable Receipts: Ed25519-signed, independently verifiable receipts — see below.
- 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.
- 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. - 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 withCONARIUM_NO_UPDATE_CHECK=1, or point it at your internal mirror withCONARIUM_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. - MCP-Native: Works out of the box with Cursor, GitHub Copilot, Claude Code, and Codex.
Verifiable Receipts
Conarium can emit portable receipts (Art. 12 / 19 shaped) that a third party verifies offline with a single file — no Conarium install required.
Official claim (do not widen): A Conarium Receipt proves that the records
still in the file have not been altered, reordered, or backdated after they
were created, and that none were removed from the middle of the chain
(prevHash / seq). It does not prove they were correct at the moment of
creation. It also cannot, by itself, prove that records were not dropped from
the end: a shorter leftover chain is still internally consistent. Catching
tail truncation needs a pin from outside the file — --expect-count,
--expect-last-hash, an OpenTimestamps anchor, or conarium-reconcile against
the database's own counters.
(TR) Conarium Makbuzu, dosyada hâlâ duran kayıtların oluşturulduktan sonra değiştirilmediğini, ortadan silinmediğini, yeniden sıralanmadığını ve geriye dönük tarihlenmediğini kanıtlar. Oluşturma anında doğru olduğunu kanıtlamaz. Sondan kesmeyi tek başına göremez: kalan zincir tutarlıdır, yalnızca kısadır. (/TR)
# Generate an Ed25519 keypair (private PEM + .pub.pem + .keyid sidecars).
# The .keyid sidecars are not optional: without them the verifier answers 13
# for every receipt, which reads like tampering and is not.
npx conarium-init
export CONARIUM_AUDIT_SIGNING_KEY=./audit-ed25519.pem
# init writes keys and config, not receipts: your own audit file does not exist
# until the gateway has served a query. The three commands below therefore run
# against the demo chain downloaded above, so they work as written — swap in
# your own sink (conarium.config.json → audit.sink) once it has records.
# Verify a receipt chain (exit 0 = the records *in the file* are intact)
npx conarium-verify chain.jsonl --pubkey key.pem
# Pin length / last hash if you need to catch records dropped from the end
npx conarium-verify chain.jsonl --pubkey key.pem --expect-count 3
# Check the OpenTimestamps sidecar. The demo chain ships without one, so this
# answers 14, deliberately not 0: an absent anchor is not a verified anchor.
# A sidecar that exists but is not yet confirmed → exit 0 with a warning.
npx conarium-verify chain.jsonl --pubkey key.pem --anchor-check
A second verifier, Go and the standard library only, is in verifiers/go. go build -o conarium-verify . then the same arguments as conarium-verify; test-vectors/ is the contract.
Anchoring is a separate step. Stamp a document with npx conarium-stamp <file>,
or submit a chain-head hash with npx conarium-anchor-service.
CONARIUM_ANCHOR_SINK=opentimestamps selects the in-tree calendar client those
tools use; it does not stamp receipts as they are written. Upgrade pending
proofs later with npx conarium-anchor-upgrade ./audit.jsonl.anchors.jsonl.
The client is in-tree (Node crypto + calendar HTTPS). It does not install
javascript-opentimestamps. See LIMITATIONS.md.
Per-person masking profiles
Masking that is correct for an AI agent is wrong for the person who owns the data. The owner asking "which customer owes the most" needs the name; the assistant summarising revenue does not. Answering that with a global on/off switch would disable the product's only real guarantee, so masking resolves per person:
{
"policy": {
"allowTables": ["zion.customers", "zion.orders"],
"maskColumns": ["*.customer_name", "*.email", "*.phone"], // default: everyone
"maxRows": 100,
"profiles": {
// The controller sees customer names; email and phone stay masked.
"controller-full": { "maskColumns": ["*.email", "*.phone"], "maxRows": 1000 }
},
"actorProfiles": { "emekcan": "controller-full" }
}
}
Deliberately narrow, because this is the one feature that can loosen protection:
- A profile may override
maskColumns,maxRowsandmaskLabelledNames— and nothing else. Table, tool and connector permissions stay global; a profile can never widen what is reachable, only what is legible within it.protectedColumnsis not overlayable: a profile that could drop it would be a per-person back door. - Per-user tokens only. An actor authenticated with a shared token never receives a profile. "Whoever holds this string sees unmasked PII" is precisely the failure this product exists to prevent.
- Fail-closed everywhere else: no actor, unlisted actor, or a profile name that does not exist all fall back to the base policy, never to a wider one.
- The content scanners still run. Email / national-ID / phone / card /
IBAN / secret detectors are not overridable at all, so those stay masked in free
text no matter which profile applied. IBAN is accepted only when ISO 7064
mod-97-10 holds. Passport MRZ (TD3, 7-3-1 check digits) is on by default and
likewise cannot be turned off by a profile — only
policy.detectors.mrz: falseon the base policy opts it out. IP addresses are off untilpolicy.detectors.ip: true. Name masking is the one detector a profile can switch off (maskLabelledNames: false), because the controller reading their own customer list is the case this feature exists for. - The receipt says which profile applied —
policy.idbecomesconarium.policy/<profile>, inside the signed hash. An access made under a relaxed profile cannot later be presented as having been fully masked. This is what keeps the audit story honest: the point was never "nobody sees PII", it is "every access is governed, and the evidence says under which rules."
Names in free text
Every other identifier has a shape. An email has an @, a national ID has a
checksum, a card has a length — a regex decides, and the decision reproduces.
A name has no shape, so maskColumns was the only thing catching one, and a name
typed into a free-text note reached the model verbatim.
Two deterministic passes close the part of that gap that can be closed honestly:
| Pass | What triggers it | Example |
|---|---|---|
| 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 |
| Labelled | The text itself marks it: a title or a field label | Sn. Ahmet Yılmaz, Yetkili: Ayşe Demir, customer: John Smith |
What this does not do, deliberately: a bare name in running prose is not detected. "Ahmet called yesterday" goes through. Catching that needs NER — a model, a dictionary and a confidence score — and every decision this gateway makes is meant to be reproducible from the rule alone, by someone who does not trust us. A probabilistic masker would also be a probabilistic receipt. Tools that do run NER (Presidio-based ones, for instance) cover more entity types; they buy that with a confidence threshold. Neither position dominates — this one is stated so an auditor knows which one they are holding.
Still not caught by content scanners — by design, not by omission: street
addresses and bare names. An address detector cannot tell "Atatürk Caddesi No:15"
from "Atatürk Barajı" without a gazetteer. A name detector cannot tell Deniz /
Güneş / Umut from the words. Both would need a dictionary or a model; this
gateway's decisions are deterministic. Close those gaps with maskColumns (column
names) and conarium-suggest-policy (a name-based guess that does not write
your config).
IP addresses are caught when you turn them on (policy.detectors.ip: true).
They are off by default: a server IP is not always personal data, and a mask you
cannot disable breaks SOC work. 1.2.3.4 is structurally a valid IPv4 address;
when the detector is on it is masked, even if you meant a version number. Dates
(13.08.2026) and amounts (1.250,00) are not IPv4.
Passport numbers in free text are not caught. MRZ is: two TD3 lines × 44
characters, P in position 1, 7-3-1 check digits. A checksum miss is not an MRZ
and is left alone. TD1/TD2 are not implemented.
HTML @ / @, JSON \u0040, and %40 are masked when they sit inside
an email-shaped token. A lone 5@ store or C:\path\u0040abc is left alone.
One decode pass; &#64; is not chased.
A TCKN split across two similarly named fields on the same row (tckn_1 /
tckn_2) is masked when the concatenation checksums. Unrelated columns are not
combined.
Zero-width characters, fullwidth digits / @, and unicode dashes are stripped
or mapped to ASCII before the detectors — that pass is not a general encoding
decoder; wrapped base64/hex tokens inside a field are masked only when they
decode to an existing detector hit.
Scan length. A single text field longer than policy.scanCharCap (default
16 384; env CONARIUM_SCAN_CHAR_CAP overrides) is replaced with [MASKED_PII]
as a whole, even when it contains no identifier. The scanner is not skipped:
skipping would mean a long note, JSON blob, or log line is the way past masking.
This is a usability setting. Raising it grows scan cost quadratically — a
40 KB alphanumeric field was ~1 s on the unbounded email regex before that regex
was bounded. maskedCount records that a decision was made.
Carry-over ignores values under three characters (a two-character value matches
everywhere and would shred the output) and matches on Unicode word boundaries, so
Ali is masked in Ali onayladı but not inside Kalite.
Coverage & reconciliation (bypass detection)
Receipts prove what went through the gateway. Reconciliation asks the database what it saw, and compares:
Neither command invents its inputs and conarium-init does not create them, so
both answer 20 (input missing) until you have produced them: declaration.json
is your own period-and-scope statement (docs/RECEIPT-SPEC.md
names the fields), and the two snapshots come from
scripts/pg-snapshot.sql.
# One-sided: signed coverage declaration over a period + declared scope
npx conarium-coverage ./declaration.json --pubkey ./audit-ed25519.pub.pem --receipts ./receipts.jsonl
# Two-sided: reconcile the DB's own per-role query counters against receipts.
# Snapshots come from pg_stat_statements (scripts/pg-snapshot.sql), taken at
# window start and window end with a dedicated DB role per gateway instance.
npx conarium-reconcile --before before.json --after after.json --receipts ./receipts.jsonl
# exit 0 = every DB query pattern in the window is attributable to a receipt for
# the same table (object attribution, not per-statement coverage —
# see LIMITATIONS.md)
# exit 40 = the DB recorded activity no receipt covers — the gateway may have
# been bypassed, or the receipt sink failed
The language is deliberate: absence is reported as "access NOT RECORDED" / "not receipted", never "no access occurred" — an absent record is ambiguous by nature, and a tool that pretends otherwise is lying to its auditor.
Run against our own production ERP the day it shipped, including a real bypass we
performed on ourselves and the tool caught:
docs/dogfood/2026-08-06-reconcile.md.
Full schema, exit codes, and known gaps: docs/RECEIPT-SPEC.md.
Countersigning (the part you cannot do for yourself)
Receipts prove what went through the gateway. Reconciliation proves nothing went around it. Both are yours, self-hosted, and signed by your own key — which is exactly what an auditor discounts: you kept the record, you signed it, and you stored it. A countersignature answers that by putting a second party on the same chain head.
The service is in this package, so you can run your own and sign your own heads — useful for a second internal custodian, and pointless against the objection above. What makes it worth anything is that the signer is not you.
# Run the endpoint. It refuses to start without a signing key or a token file:
# with neither present the three lines below exit 2 and name what is missing,
# which is the intended answer, not a failed install. Generating both is in
# deploy/anchor-service/.
CONARIUM_ANCHOR_TOKENS=./anchor.tokens.json \
CONARIUM_ANCHOR_SIGNING_KEY=./anchor.pem \
CONARIUM_ANCHOR_BASE_URL=https://anchor.example.com \
npx conarium-anchor-service
# Verify a countersignature you were given — offline, no network, no package.
# record.json is what the endpoint returned to you; without it, exit 20.
npx conarium-countersign-verify ./record.json --pubkey ./anchor.pub.pem
# exit 0 = signature valid (and inclusion valid if a proof or --log-url was given)
# exit 13 = signature invalid / unknown keyId
# exit 14 = inclusion proof present and false
# exit 15 = the log could NOT be checked — deliberately not the same as 14
The log is a hash chain: entries are appended, never rewritten, and an OTS
timestamp covers the head rather than each submission. What a countersignature
proves — and, just as importantly, what it does not — is written out in
docs/COUNTERSIGN.md, together with what a leaked
signing key would cost.
Pro is the hosted countersignature — someone other than you signs the chain head. $20/month or $200/year — save $40. One period, not a subscription. It does not renew by itself — when the period ends, access ends and you can buy it again. 14-day no-questions refund; after that, no partial refunds. VAT added where applicable. Checkout is not open yet: conarium.dev/buy redirects to the waitlist form until the payment path goes live, so these terms are the published price rather than something you can pay for today. The binary above is what you run yourself; Pro is the second signer. Shipped in the package since 0.2.16; the VERAX-operated endpoint is not open to customers yet. Business stays on the waitlist: scheduled reconciliation, coverage alerts and the signed period report are in the contract, not shipped yet.
Implementing the format yourself
The receipt is meant to outlive this implementation, so it ships with
conformance vectors — thirteen frozen cases plus a machine-readable manifest in
test-vectors/:
npm run test:vectors # our verifier against the frozen cases
Point your own verifier at each receipts.jsonl, pass the arguments listed in
manifest.json, and compare the exit code. expected-hashes.json gives the
canonical JCS → SHA-256 hashes so you can check your canonicalisation without
needing our private key, which is deliberately not published.
The vectors found two things in this repository on their first run: a schema check that reported a structurally invalid receipt as tampered, and a wrong assumption of ours about unsigned receipts. Both are now frozen as cases 007 and 008.
Anchoring your chain (optional)
conarium-stamp anchors a file to the OpenTimestamps calendars, and
conarium-anchor-upgrade fills in the Bitcoin block height once it lands.
Those two are all most setups need.
If you would rather expose anchoring as a small service — for several
gateways, or to hand an auditor a stable URL —
bin/conarium-anchor-service.mjs is one:
it submits hashes, retains proofs, serves the raw .ots at a permanent path,
and upgrades pending anchors on a timer.
It is code you run, not a service we operate — there is no hosted instance to sign up for. It also serves the raw proof precisely so a third party can verify with the reference OpenTimestamps client and ignore the service entirely. An anchoring endpoint you have to trust would defeat the purpose of anchoring.
Signing is fail-closed: set CONARIUM_AUDIT_SIGNING_KEY and/or
CONARIUM_AUDIT_HMAC_KEY, or explicitly CONARIUM_AUDIT_UNSIGNED=1 for throwaway setups.
Key rotation: keep prior public PEMs in CONARIUM_AUDIT_TRUST_PUBKEYS (, / ;
separated). After the first signed audit line, every later line must carry sig.
Where this sits among similar projects
Shortened here. Read the whole README on GitHub.
Signals
- GitHub stars
- 3
- Forks
- 1
- Last commit
- Oct 2026
- Weekly_downloads
- 236 weekly_downloads
Advanced
- Delivery
- conarium MCP server → your ahel connector (mcp.ahel.ai) → your AI.
- Item type
- mcp-server
- Key
io-github-dogrucanemek-alt-conarium- Source
- github.com/dogrucanemek-alt/conarium
github.com/dogrucanemek-alt/conarium
More in Databases & data
MCP server
More in Databases & dataAIOProductOS MCP
MCP server · aioproductos
More in Databases & dataQuantRisk
MCP server · 78degrees
More in Databases & dataPostHog MCP Server
MCP server · posthog
More in Databases & dataagent-native-analytics
MCP server · builderio
More in Databases & dataRun402
MCP server · kychee-com
More in Databases & data