Report a vulnerability
Email security@kirto.com. A GitHub issue, pull request or discussion is a public channel, and a report does not belong in one. The address reaches the maintainer of both products directly.
Say which product and version, what you did, what happened, and what you expected. Machine-readable: /.well-known/security.txt.
What happens after you report
Gate. The policy that ships in Gate's repository commits to the following:
- An acknowledgement within three working days. An assessment follows: whether the report is confirmed, what the severity looks like, and when a fix is expected.
- A report of active exploitation goes first. It is handled ahead of everything else.
- Disclosure is coordinated with you. Publication waits for a release carrying the fix. An issue that is already public or being exploited goes out with whatever mitigation exists.
- Reporters are credited by name in the changelog and the advisory, unless they ask not to be.
Flow. Flow's repository has no security policy file yet, so Flow makes no response-time or disclosure commitment. A Flow report goes to the same address.
Neither product has a tagged release yet. main is the only version of each, and a fix lands there. Gate's policy adds that once releases begin, the newest tag carries security fixes and older tags get critical fixes on a best-effort basis. Gate's releases and compatibility page is where any longer commitment will be written.
Three things start a report: a one-line title, a technical description of the flaw and where it lives, and reproduction steps. For Gate, the smallest config and request that shows it, or a kirto-gate -test-rules case. For Flow, the model and the case history that shows it. The tag, commit or image digest you tested, who you are, the impact as you see it, a suggested fix and your disclosure plans all help, and none of them is required.
Where your data goes
Both products are self-hosted: the control plane runs in your network. Kirto runs no hosted service in your data path, so there is no subprocessor between your traffic and your providers.
Gate
The binary sends Kirto nothing. There is no licence check, no activation, no usage report and no diagnostic upload. The licence carries no metering code, so no build has one compiled out.
The outbound connections Gate makes are the ones your configuration turns on: the model providers, MCP servers and peer agents you route to; your OTLP collector; your identity provider's JWKS endpoint; huggingface.co when a models entry resolves an hf: reference, which kirto-gate artifacts pin turns into a one-time resolution; a Presidio analyzer you run, for the remote detector; a provider's admin usage API, for metering reconciliation; and an ACME directory, when the front door issues its own certificates. ONNX Runtime runs a telemetry uploader on Linux, and Gate turns it off by default before the runtime starts. The threat model disclosure keeps this list.
Gate does not retain prompt content. No log line carries a body, a prompt, a completion or a matched span. Guardrail decisions log entity counts, such as pii/CREDIT_CARD:1, and audit records name entity types without the text. The store holds virtual-key metadata and token aggregates, and has no column that could hold a prompt. Two exceptions are documented: a guardrail CEL rule you write can read the matched span, and the delta cache keeps annotations keyed by content hash in memory for the life of the process.
Deploy it as the front door with Helm, as the front door on VMs, or beside a gateway you already run.
Flow
Flow runs on a durable host you operate: the broker by default, or DBOS on your Postgres. Its record goes where you point it: rows in your Postgres, one object per event in your S3, GCS, Azure Blob or MinIO bucket, or OpenTelemetry logs to your collector.
Flow's record carries content. Replay re-feeds what arrived from outside the engine, so an agent step records the raw model response and a service task records its outputs. The record is as sensitive as the case data in it, and its store needs the same controls.
The outbound connections Flow makes are the model providers and the endpoints your process models call. The default network policy refuses loopback, private, link-local, CGNAT and unspecified addresses at connect time, which keeps cloud metadata endpoints out of a model author's reach. A deployment opens private ranges deliberately, and a model cannot. Flow has no licence check and no usage report.
The hosts and their configuration are in the Flow docs.
This site
The site is static files served by Cloudflare. It sets no cookies and runs no analytics, and a demo request is an email you send from your own mail client. What happens to that email is in the privacy notice.
Evidence you can check yourself
Gate: a signed chain
Every audit record is a link in a hash chain kept per node. Each entry carries a sequence number, the hash of the entry before it, its own hash, and an ed25519 signature by the node's audit key, generated on first start into cluster.data_dir/audit.key. A chain opens with audit.chain.start, which carries the public key, and a node that stops on purpose closes it with audit.chain.close.
kirto-gate audit verify reads an exported trail offline, with no gateway, cluster or credential. It reports, per chain, whether every record hashes to its entry, links to the one before it, carries a valid signature under the chain's key, and whether a sequence number is missing. It exits 1 on any finding, so it runs as a scheduled job.
kubectl logs kirto-gate-0 | kirto-gate audit verify -A chain cannot show a trail cut at the end. Every node offers its signed head to the cluster about once a minute, the cluster keeps the last day of heads in replicated state, and verify -heads reports a trail that stops short of one. A cluster of one witnesses itself, so there the collector is the witness.
Records leave on a synchronous exporter, and a record the collector refuses is counted in kirto_gate_audit_export_failures_total. Every record is also written to the node's own output, whether or not a collector is configured.
The trail proves what the key signed. Keeping the key where only the node can reach it is the deployment's part. Verify the audit trail walks through each finding, and the audit trail explains the design.
Flow: a replayable record
Every case records what it decided and why: the pinned model and version, each event that arrived, each command dispatched and its result, each condition and DMN decision, each agent turn and each human approval. The durable host writes the record with the state change it describes, so the record and the engine cannot disagree.
kirto-flow-replay takes the pinned model and a case's history, re-feeds the recorded inputs through a fresh engine, and diffs the decisions it reaches against the stored ones. It exits 0 on a match, 1 on a divergence or a version skew, and 2 when the replay cannot run. The same check is served at GET /v1/cases/{kind}/{id}/replay.
kirto-flow-replay -model order.bpmn -history case-1.jsonReplay holds within one MAJOR.MINOR line: the verdict names the version that recorded the case and the version that replayed it, and flags a skew.
Documented limits, not findings
A control that does less than its documentation says is the thing to report. A report of one of the limits below is a documentation question. Report anything that is worse than documented, or reaches further.
For Gate, findings in credential handling, key storage, the admin plane's default-deny, or the inspector's separation from routing are security-relevant whatever their severity looks like.
Gate
- Pattern detection is not adversary-resistant. It does not catch paraphrase, unusual encodings or a description of a secret. Guardrails are compliance and accident prevention.
- Prompt injection detectors reduce and do not prevent. The
classifydetector catches the phrasings it was trained on. Thellmjudge reads exactly the text an attacker wrote. - A body over
max_body_bytesis not inspected. It is counted inkirto_gate_guardrail_uninspected_total, with unparseable bodies and unknown content types. - The configuration, the cluster secret and root on the host are the whole deployment. An attacker holding any of them has already won.
The full list is what Gate does not defend against, and threats and limits sets it beside what Gate is asked to stop.
Flow
- The record is not signed. Replay shows the recorded decisions follow from the recorded inputs under the pinned model. It does not show that nobody with write access to the store edited both.
- Replay re-feeds a model's answer and does not ask again. An agent step is checked against the response it recorded, so a different answer from the same model today is not a divergence.
- A version skew is not a verdict. Across MAJOR.MINOR lines a diff may be an expected engine change, and the replay exits 1 so nobody reads it as verified.