v0.13.0Found and fed

Security

Last updated: September 4, 2026

Iris stores a lot of signal about your agents — inputs, outputs, tool calls, eval results. This page explains the concrete controls in place, the threat model we design against, and how to verify each claim for yourself. Every number on it is either read from the shipped source when the page is built or measured from a public record, and each section says which.

Data location

Self-hosted (OSS): every trace, span, eval result, and audit entry is written to a SQLite database on your machine (default: ~/.iris/iris.db). No data ever leaves your environment. Iris does not phone home. There is no telemetry.

A hosted tier is under consideration, not under construction. No version is committed to it and no pricing exists. The cross-tenant isolation described below is already enforced at four independent layers in the self-hosted code — see the architecture guide for the technical detail — so if a hosted tier ever ships it inherits those boundaries rather than retrofitting them. Until it does, every control on this page describes software that runs on your machine.

Tenant isolation

Every row in every data table carries a tenant_id column. Reads, writes, updates, and deletes require a tenant context parameter — there is no “get all traces” query path in the codebase. The four-layer defense:

  1. Type system. TenantId is a branded TypeScript type. Forgetting to pass tenant context is a compile error.
  2. Runtime guard. Every storage method calls assertTenant() which throws if the tenant is missing — even if the type checker was bypassed.
  3. SQL scope. Every SQL statement carries an explicit WHERE tenant_id = ? clause.
  4. Composite indexes. Every hot-path index leads with tenant_id so cross-tenant scans are physically impossible in the planner’s fast path.

Self-hosted Iris runs a single implicit tenant, so the boundary is scaffolding rather than a live multi-tenant surface today. The design rule it encodes: a tenant ID would be resolved server-side from an auth token’s claims — never from a client-supplied query parameter or header. Regression coverage lives in tests/unit/storage/sqlite-adapter.test.ts (cross-tenant isolation) and migration-tenant.test.ts (upgrade path).

Supply-chain transparency

Every Iris release produces artifacts you can independently verify:

  • npm provenance. Every published tarball carries a GitHub-signed attestation linking it to the source commit and workflow run. Verify with npm audit signatures.
  • SPDX SBOMs. A Software Bill of Materials ships with every release, covering direct and transitive dependencies for both the npm package and the Docker image. Attached to the GitHub release page as iris-npm-sbom.spdx.json and iris-docker-sbom.spdx.json.
  • Cosign signatures. Docker images are signed with Sigstore cosign using GitHub OIDC (no long-lived signing key). Verify with:
    cosign verify ghcr.io/iris-eval/mcp-server:vX.Y.Z \
      --certificate-identity-regexp='https://github.com/iris-eval/mcp-server' \
      --certificate-oidc-issuer='https://token.actions.githubusercontent.com'
  • SLSA build provenance. Both artifacts carry GitHub-signed attest-build-provenance attestations. Inspect with gh attestation verify.

Runtime defenses

Every figure in this section is a configuration default, read from the line of source that enforces it when this page is built. None of them is a measurement: they say what the shipped server refuses out of the box, not how it performed under load. Change a value in config.json and the figure here no longer describes your install. The one measured section on this page is how fast things get fixed, below.

  • Helmet headers on the dashboard API (HSTS, X-Frame-Options, X-Content-Type-Options, strict CSP).
  • Bearer-token auth on HTTP mode with crypto.timingSafeEqual-based comparison to block timing side-channels. Off until you configure a key — HTTP mode is not authenticated by default.
  • Rate limiting (configuration): 20 req/min on MCP endpoints, 600 req/min on dashboard APIs, standard RateLimit headers. Configurable via security.rateLimit.
  • Zod input validation on every MCP tool and REST endpoint. Invalid requests fail fast with structured errors.
  • ReDoS protection (configuration): every match of a user-supplied regex runs in a sandbox worker thread under a hard 100 ms deadline. A match still backtracking at the deadline is terminated mid-execution and the rule reports skipped with budgetExceeded: true; a per-evaluation circuit breaker opens after 3 breaches, so one hostile request cannot stall the server no matter how many regex rules it carries. safe-regex2 and the 1,000-character pattern cap remain as fast-path rejection, not as the boundary — safe-regex2 is a star-height heuristic and (a|a)*$ passes it. The trade-off, stated plainly: this fails open per rule. A budget-killed rule did not judge the output, so an adversary who knows your pattern can craft input that stalls it into skipping. Failing closed would let the same adversary force false violations on benign output, which is worse for an eval product — so the budgetExceeded flag is exported precisely so a gate that must fail closed can treat those skips as failures on its own terms.
  • Request size limit (configuration): 1 MB per request body on the dashboard API and the HTTP transport, to prevent memory-exhaustion attacks. Configurable via security.requestSizeLimit.

How fast things get fixed

This section is measured, not asserted. The figures are computed from the public GitHub issues API for iris-eval/mcp-server and re-sampled by hand, so the sample date matters more than the numbers: an old date means nobody has looked lately.

Issues closed
8
last 90 days
Median time to close
54.8 h
2.3 days
p75 time to close
520.6 h
21.7 days
Open now
16
issues, not PRs

Sampled September 4, 2026. n = 8 closed (8 completed, 0 closed as not planned); median 54.8 h (2.3 d), p75 520.6 h (21.7 d). With a sample this small, one long-lived issue moves the p75 by days; read the median as the typical case and the p75 as the tail. Method: GitHub REST /repos/iris-eval/mcp-server/issues, unauthenticated; pull requests excluded; hours = closed_at minus created_at; median and p75 by linear interpolation between order statistics; window = issues whose closed_at falls in the last 90 days. Check it against the closed issues and the open ones; the generator is scripts/claims/generators/issues.mjs.

Threat model

We maintain an internal STRIDE threat model covering ingestion, storage, dashboard API, auth, file I/O, and multi-tenant boundaries. The summary:

  • In scope: data confidentiality, tenant isolation, supply chain integrity, DoS resistance on the API surface, audit log tamper detection, prompt-injection-aware eval rules.
  • Out of scope: physical access to the host machine (self-hosted), insider threats at the hosting provider, compromise of your LLM provider’s infrastructure, social engineering of your developers.

The full threat model is a private document reviewed and updated quarterly. We share redacted excerpts with enterprise customers under NDA on request.

Reporting a vulnerability

If you believe you’ve found a security issue, please email security@iris-eval.com or use private vulnerability reporting on GitHub. Please do not open a public GitHub issue for security matters.

The commitments below are the ones in SECURITY.md, rendered from the same file so the two cannot disagree. They are best-effort targets from a solo maintainer; critical vulnerabilities get top priority regardless. We commit to:

  • Acknowledge your report within 48 hours.
  • Provide a detailed response (confirmed / not a vulnerability / need more info) within 5 business days.
  • Coordinate a disclosure timeline with you — we ask for 90 days by default, negotiable for high-severity or actively exploited issues.
  • Release the fix, publish an advisory, and credit you in it and in the release notes unless you prefer to remain anonymous.

Compliance roadmap

Iris is pre-SOC-2 today. For enterprise buyers asking about compliance posture:

  • Today: security-by-design architecture, signed releases, SBOMs, internal STRIDE threat model.
  • If a hosted tier ever ships: formal SOC 2 Type I readiness, an independent penetration test and an incident-response playbook would come with it. A hosted tier is under consideration, not under construction — no version is committed to it.
  • The commitment: no compliance certification will be claimed before it is held. Nothing on this page is a certification.

Related reading