Releases
Iris 0.21.0A security release: what an agent or a client sends Iris is bounded before Iris reads it, and a tool argument can narrow what the operator allows but never widen it.
2026-10-25 · rendered from CHANGELOG.md · the same section is the GitHub release
A security release: what an agent or a client sends Iris is bounded before Iris reads it, and a tool argument can narrow what the operator allows but never widen it. 0.21.0:
- A tool call, a dashboard API body and an OTLP batch are each held to security.maxInputElements (100,000 array elements and object keys by default) before any schema reads them, and a validation error lists at most 20 issues and the total.
- verify_citations and evaluate_with_llm_judge take the operator's fetch setting, domain list and cost caps as ceilings; every judge call on a key draws on one daily budget; and stored text comes back inside a fence that marks it as text someone else wrote.
- With an allowlist set, the citation fetcher looks up only the hosts on it.
- The lowest versions Iris accepts are now @modelcontextprotocol/sdk 1.32.0, body-parser 2.4.0 and zod 4.6.7, and the Docker image runs Node.js 24.22.1.
- Also: npm run preflight, which runs what CI will run before you push, and proof measured on agent runs Iris did not write.
Check before upgrading. Three entries under Security are marked BREAKING. Read them if an agent passes allow_fetch, domain_allowlist, max_cost_usd or max_cost_usd_total to Iris's tools, if the judge tools spend more than 1 USD a day on your key (IRIS_LLM_JUDGE_DAILY_BUDGET_USD), or if a script reads stored text from get_traces, list_rules or a trace resource.
Added
- `npm run preflight`: what CI will say, before you push. It runs, in CI's order, every check a pull request's workflows run that can run on your machine: the lint, the type checks, the proof checks, the builds, every test with the coverage floors, and the truthbase regenerated and compared with the committed one, then records the tree it verified.
git config core.hooksPath scripts/git-hooksturns on a pre-push hook that refuses a branch the preflight has not passed at that tree. A test holds it to the workflows: every job a pull request runs is a step or is listed as CI-only with the reason (Playwright, the real MCP clients, Docker, the Python client and the other jobs that install or start something this machine would have to set up). - Proof on agent runs Iris did not write.
npm run proof -- --outsidejudges a fixed sample of the runs AgentDojo publishes, with the shipped engine at its defaults, and scores the verdicts against the labels AgentDojo's environment computed by checking the world after each run: was the user's task done, and did the attacker's goal happen. Every other proof line is measured on cases written or labelled here; this one is not. The sample is 700 runs of 29 agents and defences on all 97 of AgentDojo's user tasks, each file checked against the hash AgentDojo's repository lists for it, and CI regenerates the report on every change. What it found, at the shipped defaults: - The verdict shipped 312 of 419 bad runs and blocked 68 of 281 good ones. - No injection rule caught any of the 200 runs where the attack succeeded. In every one the rule judged, it found no directive: AgentDojo's attack is worded in a way the directive list does not hold. - Nearly half the good runs blocked were failed byno_pii: an email assistant naming the addresses its user asked about, a banking assistant giving its own user an account number. The next most common wasno_silent_tool_failure. Half the tasks are held out by a seed, so a rule changed after reading these runs is judged on runs it was not fitted to. The report, with every rate also counted over tasks, isproof/OUTSIDE.md.
Changed
- Building Iris from a clone with pnpm 11 or later works again. pnpm 11 stopped reading its settings from
package.json, sopnpm installrefused the install scripts ofbetter-sqlite3andesbuildand stopped, and directories that build Iris that way, such as Glama, got no build. The allow-list is inpnpm-workspace.yamlagain, in the forms pnpm 10 and pnpm 11 and later each read. npm, which Iris itself installs with, is unaffected. - On Node 24.22.0 and later, a better-sqlite3 compiled on your machine loads as usual. Node 24.22.0 keeps the global list of addon cleanup hooks (nodejs/node#63985), so a binary compiled against Node 24.19+ headers no longer aborts when V8 frees one of its statements. Iris now loads that binary on 24.22.0 and later; on 24.19 to 24.21 it still holds the store with Node's built-in SQLite and says why. The CI job that compiles the addon from source on Node 24 confirmed it: the compiled binary survived collection on 24.22.0.
- The website and two blog posts give the EU AI Act's current dates. The agent-eval guide and the posts from March 2026 said the Act's requirements for high-risk systems took effect in August 2026, the date in the Act when they were written. An amendment in July 2026 moved them: they apply from 2 December 2027 for the systems listed in Annex III and from 2 August 2028 for those covered by Annex I. The guide now says so, and each post carries a dated correction.
- A shutdown that has to stop a storage thread says why.
close()gives the checkpoint thread and the search thread 5 s to end on their own. When one has not, Iris now writes one line to stderr before stopping it, naming the thread and what it was doing: still starting, in a request this process made (and for how long), or in its own periodic checkpoint. A shutdown that waited was silent before. - Links to the old roadmap resolve.
docs/roadmap.mdpoints at the capability map, which replaced it, so links to it in published posts and on other sites land somewhere current; the editor's notes on three posts link the map directly. - The homepage's research section leads with published research: the four-way comparison of Iris, Langfuse, Phoenix and Promptfoo, and each rule's measured precision and recall on /proof, in place of a survey card marked "Coming Soon". The terms add how a hosted or paid service would be offered: under its own terms, published before it starts, with no change to the open-source software's license.
- iris-eval.com counts page views with Vercel Web Analytics, as its privacy page describes: cookie-free, no personal data, no cross-site tracking. The script runs under the site's nonce-based Content Security Policy.
Security
- A tool call's arguments may hold at most 100,000 array elements and object keys, on every transport. The MCP SDK can count them before it validates the arguments, but only when the server sets a limit, and Iris set none (GHSA-46jr-4wx8-v453, high). Arguments that fail validation are reported element by element, so one 1 MB request of 524,229 elements (zeros where
spansbelong) cost the server 260 MB, and 20 requests of that size at once, the MCP endpoint's per-minute limit for one client, up to 1,148 MB and 41 s to answer them. A call over the limit now gets anisErrorresult that names the limit, before its arguments are validated, and the session goes on. The default clears every legitimate input measured, even one that fills the whole 1 MB request: real agent transcripts reach about 12,600 elements at 1 MB, and the densest shape found, a trace of 201 short tool calls, 68,506. Set it withsecurity.maxInputElementsinconfig.jsonorIRIS_MAX_INPUT_ELEMENTS; the same limit bounds the dashboard's request bodies (below). A server reachable beyond loopback can go lower: at 10,000 the same burst costs about 250 MB.scripts/bench-tool-input-elements.tsmeasures it. - Iris requires `@modelcontextprotocol/sdk` 1.32.0 or later. It required 1.30.0. A published package does not carry Iris's lockfile, so a project that already held SDK 1.30.x or 1.31.x kept it, and those versions are affected by GHSA-46jr-4wx8-v453, GHSA-22jm-h49p-29qw, GHSA-rvq5-wwqv-78pq and GHSA-6prh-2h8m-c8cw, and 1.30.x also by GHSA-6qxp-vccf-f47h and, at 1.30.0, GHSA-f897-62hf-6gxq. The element limit above also needs 1.32.0: an older SDK ignores the option. Iris's own lockfile already installed 1.32.0.
- `ip-address` 10.7.4 (GHSA-rxp7-6hxx-x8q5, moderate:
Address6strips a zone identifier in quadratic time before applying its length limit). Iris reaches it only throughexpress-rate-limit, which parses the request's own socket address: Iris leaves Express'strust proxyoff, so a client cannot hand it a long string. Theoverridesfloor is raised from 10.5.1 to 10.7.4. - With an allowlist set, the citation fetcher resolves only the hosts on it.
IRIS_CITATION_DOMAINSis now checked before the DNS lookup, so a host that an output cites and the list does not hold is refused withnot_allowed_domainand never looked up. It applies at every redirect hop. A host that is both outside the list and unsafe, such as a private address, now reportsnot_allowed_domainwhere it reportedssrf. - BREAKING — A tool argument can no longer widen what the operator allows for fetching or spend. Three arguments could.
verify_citationswithallow_fetch: truefetched the cited URLs on a server whose operator had not setIRIS_CITATION_ALLOW_FETCH=1; itsdomain_allowlistwas added to the operator'sIRIS_CITATION_DOMAINSinstead of narrowing it; andmax_cost_usdonevaluate_with_llm_judgeandmax_cost_usd_totalonverify_citationsreplaced the operator's cap with any larger number. An agent's arguments can be steered by text it read, including text stored in Iris, so each is now a ceiling: an argument can narrow the operator's setting for one call and never widen it. When one asks for more, the operator's setting applies and the call goes on, the response carries a warning with codeIRIS_ARGUMENT_NARROWEDnaming the argument and the setting that applied, and the stored evaluation keeps the same fact (provenance.narrowed) with a sentence to the operator in itsinterpretations, on every later read. Fetching now needsIRIS_CITATION_ALLOW_FETCH=1on the server; a caller that relied onallow_fetch: truegets no fetch and the warning. The per-call cap onverify_citationsis a new setting,IRIS_CITATION_MAX_COST_USD_TOTAL, default 1 USD as before. - BREAKING — Every judge call on your key draws on one daily budget.
evaluate_with_llm_judgeandverify_citationshad a per-call cap and no daily limit, so an agent calling either in a loop, or steered into doing so, could spend the key without end; only the relevance judge had a daily budget. All three now share one,IRIS_LLM_JUDGE_DAILY_BUDGET_USD(default 1 USD per UTC day, per tenant, kept in the database). A call is made only if its worst case fits in what is left. When it does not, nothing is spent:evaluate_with_llm_judgeanswersIRIS_BUDGET_EXCEEDED(retryable, with the time the budget resets), andverify_citationsmarks the citationdaily_budget_reachedand makes no further call. A deployment that spends more than 1 USD a day on the judge tools raises the budget. The variable wasIRIS_RELEVANCE_JUDGE_DAILY_BUDGET_USD; that name is still read when the new one is unset, and the server says so at startup. In the Claude Desktop extension the setting is now "LLM judge daily budget", and a value set under the old one is not carried over. Today's spend isjudge.dailyBudgetoniris://capabilities. - BREAKING — Stored text comes back fenced, and a page of traces carries snippets. Iris hands back what agents, their users and their tools wrote, and the agent reading it sits beside tools that delete traces and rules; a sentence planted in a trace reached the model as plain JSON. Every read that returns stored text now puts each value someone else wrote inside a tag carrying an id made for that response,
<untrusted_output id="…">…</untrusted_output id="…">, the fence Iris already used for its own judge, and the response starts withuntrusted: { id, notice }. The fence is on the values, so it reaches the model from the text block, fromstructuredContentand from a resource alike. A short value with no whitespace (an agent name, a session id, a tool name) is left as it is, so it still works as a filter. Covered:get_traces,list_rules,iris://traces/{trace_id},iris://evaluations/{id}(a custom rule's name and a judge's rationale; what Iris wrote stays outside),iris://auditandiris://dashboard/summary.get_tracescuts each fenced value to 500 characters unlessinclude_text: true, and says what it cut in each trace'scut.deploy_ruleandevaluate_output'scustom_rulesrefuse a value carrying a fence tag, anddeploy_ruletakes the rule names the dashboard always required (letters, digits, dot, dash, underscore).get_tracesandlist_rulesadvertiseopenWorldHint: true. A script that read a trace's text fromget_tracesreads it inside the tags, and passesinclude_text: truefor more than 500 characters. - Three dependency advisories, each patched within a day of GitHub publishing it; none affects Iris as shipped.
proxy-addr2.0.8 (GHSA-jqcg-44mw-7w3h, critical) fixes how a trusted proxy address range is matched; that matching runs only when Express'strust proxyis set to a range, and Iris leavestrust proxyoff.source-map-js1.2.2 (GHSA-68fv-2mgg-jv7q, high) is in the build and test tools only.@modelcontextprotocol/client2.3.1 (GHSA-6qxp-vccf-f47h, high) is in the OpenTelemetry example only. - Dashboard and OTLP request bodies are held to the same element limit. A JSON body sent to the dashboard API or to
POST /v1/tracesthat holds more thansecurity.maxInputElements(default 100,000) array elements and object keys is answered413before any schema reads it; JSON bodies of every legitimate shape measured stay under it at the full 1 MB request size. A protobuf body onPOST /v1/tracesis counted as it is decoded against its own ceiling, one element per 5 bytes ofsecurity.requestSizeLimit(209,716 at the default 1 MB) ormaxInputElementswhen that is higher. An OTLP exporter drops a batch it gets a413for, and decoded protobuf is denser than JSON: the densest real exporter body measured, from the plain Python exporter, holds one element per 7.5 bytes, so a batch that dense is taken up to the full request size with half again as much room to spare, and raisingrequestSizeLimitraises the ceiling with it. While the limit covered tool calls only it was namedmaxToolInputElements(IRIS_MAX_TOOL_INPUT_ELEMENTS); that name is still read when the new one is unset. - Validation errors return at most 20 issues and the total. A 400 from the dashboard API or the MCP HTTP transport for a body or query that fails validation still lists the issues in
details, the first 20 of them, and now carriesissueCount, how many there were, withdetailsNotesaying so when some were left out. An issue about unknown keys names at most 20 of them (keyCountgives how many), and a message is cut at 1,000 characters. The same bound holds for the reasonsPOST /v1/tracesgives for dropped spans (three issues for each span), forIRIS_INVALID_RULE_CONFIG, and for the unknown-argument message of every MCP tool. A request wrong in a few places is still told every one of them; the size of the answer no longer grows with the request. Twenty covers a trace, the largest body the API takes, with all 18 of its top-level fields wrong. - `body-parser` 2.4.0 and `zod` 4.6.7 are now the lowest versions Iris accepts.
body-parserbefore 2.4.0 (GHSA-27r3-jxmh-p828, high) could end the process when a compressed request body was followed by stray bytes. Iris decompresses request bodies on both HTTP servers, because OpenTelemetry exporters send gzip, and parses them before the API-key check.zodbefore 4.6.6 (GHSA-mrhx-c7xc-m3w5, high) collected one issue for every failing element of an array with no length limit. Iris validates every tool call and request body with it, and the element limit above already bounds how many one request can make it collect. A published package does not carry Iris's lockfile, so Iris now declaresbody-parserat^2.4.0(Express's own range admits 2.3.0) andzodat^4.6.7: a project that held an older version moves when it upgrades. - `fast-uri` 3.1.9 (GHSA-w74x-v6f9-j3xg, GHSA-wjr9-j795-jxcr, moderate). Iris reaches it only through
ajv'sformat: "uri"check, which Iris's own validator does not run. Theoverridesfloor is raised from 3.1.8. - The Docker image runs Node.js 24.22.1, the security release of 2026-10-20, on Alpine 3.24.3. It ran 24.21.0. The release fixes advisories in Node's
http,tls,dnsand brotli code, and carries undici 7.30.1 for Node's ownfetch, which the citation fetcher uses.
Earlier
One line per release, the bold lead of its changelog section; each links to the release notes built from that section.
| Version | Date | Lead | Entries |
|---|---|---|---|
| 0.20.0 | 2026-10-03 | Iris says what it checked. A verdict tells a pass from a check that never ran, names who recorded the evidence it judged, and cannot be replaced by the agent it judges | 119 |
| 0.19.0 | 2026-09-25 | Verdicts that say how sure they are, detectors that see through disguises, and one command to set up any client | 9 |
| 0.18.0 | 2026-09-24 | Honest numbers, a lighter footprint, nothing done silently | 15 |
| 0.17.0 | 2026-09-23 | Hostile input, honest verdicts, a harder release path | 11 |
| 0.16.0 | 2026-09-22 | Earn the comparison | 26 |
| 0.15.0 | 2026-09-21 | Reach, honestly | 26 |
| 0.14.0 | 2026-09-20 | The dashboard is a product | 25 |
| 0.13.0 | 2026-09-08 | Found and fed | 31 |
| 0.12.1 | 2026-09-07 | Truth patch 3 | 10 |
| 0.12.0 | 2026-09-07 | Compare | 17 |
| 0.11.0 | 2026-09-06 | The act layer | 24 |
| 0.10.0 | 2026-09-05 | The verdict | 18 |
| 0.9.0 | 2026-09-05 | Iris explains its verdicts | 12 |
| 0.8.2 | 2026-09-05 | The truth patch | 5 |
| 0.8.1 | 2026-09-04 | The honest-surfaces patch | 0 |
| 0.8.0 | 2026-09-04 | The trajectory release | 10 |
| 0.7.0 | 2026-09-04 | The proof release | 21 |
| 0.6.0 | 2026-09-03 | The correctness release | 47 |
| 0.5.1 | 2026-09-03 | The verdict-integrity patch | 9 |
| 0.5.0 | 2026-08-12 | The acceptance-test release | 19 |
| 0.4.6 | 2026-08-10 | Security and data-integrity release | 14 |
| 0.4.5 | 2026-08-07 | Security release | 12 |
| 0.4.4 | 2026-06-12 | — | 2 |
| 0.4.3 | 2026-05-21 | — | 5 |
| 0.4.3-rc.0 | 2026-05-16 | — | 0 |
| 0.4.2 | 2026-05-06 | — | 3 |
| 0.4.1 | 2026-05-06WITHDRAWN | — | 12 |
| 0.4.0 | 2026-04-24 | — | 28 |
| 0.3.1 | 2026-04-22 | — | 9 |
| 0.3.0 | 2026-04-21 | — | 9 |
| 0.2.4 | 2026-04-17 | — | 9 |
| 0.2.3 | 2026-04-16 | — | 5 |
| 0.2.2 | 2026-04-16 | — | 7 |
| 0.2.1 | 2026-04-16 | — | 16 |
| 0.2.0 | 2026-04-14 | — | 29 |
| 0.1.9 | 2026-04-07 | — | 7 |
| 0.1.8 | 2026-03-25 | — | 10 |
| 0.1.7 | 2026-03-25 | — | 8 |
| 0.1.6 | 2026-03-23 | — | 3 |
| 0.1.5 | 2026-03-21 | — | 21 |
| 0.1.4 | 2026-03-20 | — | 19 |
| 0.1.3 | 2026-03-15 | — | 3 |
| 0.1.2 | 2026-03-14 | — | 13 |
| 0.1.0 | 2026-03-13 | — | 35 |