Repository navigation
v0.18.0
[0.18.0] - 2026-09-24
Honest numbers, a lighter footprint, nothing done silently. 0.18.0 finishes the work of the 0.17.0 security review:
- The published accuracy numbers no longer count the real transcripts they are checked against, and the page says plainly what that check is.
- Regression alarms fire about ten times less often when nothing has changed.
- The tool list an agent loads in every session is 58% smaller.
- A verdict says when the rules under it changed while the server ran, and deleting a trace leaves a record the agent can read.
- The website runs only the scripts it issued, and its playground judges with the server's full safety libraries.
Every change carries a test. Every published number that moved is listed under Fixed; no verdict on the composite corpus changed.
Check before upgrading.
answers_the_askno longer blocks at the shipped thresholds. A gate that relied on it at the defaults stops blocking: set akeyword_overlaportopic_consistencythreshold of your own to keep it gating.- The health endpoints no longer return
trace_count. Readtotalfrom the authenticatedGET /api/v1/traces?limit=1instead. - Stdio refuses a request over
security.requestSizeLimit(default 1 MB), as HTTP already did. Send less, or raise the limit. - On a loopback bind, a request without a
Hostheader gets 403. Browsers, MCP clients,curland the container health check all send one. - Verdicts can carry
rules_changedafter a custom rule is deployed, deleted, enabled or disabled while the server runs. It never changespassed,scoreorverdict. no_piino longer counts an email address that appears ininput. A gate ondetector_vetostops tripping on answers that repeat an address the agent was given.- Several published accuracy numbers moved (see Fixed); the largest is
no_silent_tool_failurerecall, 68.0% → 63.6%.
Added
- Deleting a trace leaves a record, and the agent can read the record.
delete_traceused to remove a trace with no audit entry, so an agent could delete the evidence against its own run and nothing would show that it had. A deletion now appends atrace.deleteentry (time, user, trace id) to the same audit log that already records every rule deploy, delete, toggle and update. That log is now an MCP resource,iris://audit(the newest 100 entries, newest first), as well as the dashboard's Audit page, which now shows and filters trace deletions. The agent keeps the power to delete; what it loses is the ability to delete silently. - A verdict says when the rules under it changed while the server ran. An agent that can deploy, delete, enable or disable a custom rule can shape the rules it is then judged by, and the verdict that followed read exactly like one under the operator's rules.
evaluate_output,log_tracewithevaluate: true,evaluate_runs,POST /api/v1/tracesand OTLP ingest now carryrules_changed: { count, last_change_at, since, audit: "iris://audit" }whenever the deployed rule set changed since the server started. It never changespassed,scoreorverdict, and it is absent otherwise; the rules the demo seeds at startup count as its starting set.iris://auditand thetrace.deleteentrydelete_tracewrites now use the same audit file as the dashboard's Audit page, so a demo never reads or writes the real log.
Changed
- The tool list costs the agent being evaluated 58% less context. Every session of an agent that runs Iris pays for
tools/listbefore it does any work, and that list was 104,567 bytes (about 26,000 tokens) for 12 tools. It is now 44,285 bytes (about 11,000 tokens). Each tool description is capped at 1 KB and says what the tool does, when not to use it, what it returns, its errors and its sibling tools; the long form of each tool (the statistics behind a comparison, the fetch guards, every parameter's full explanation and what every output field means) is served once, on request, astoolGuideiniris://capabilitiesandGET /api/v1/capabilities. Each tool'soutputSchemain the list now names its top-level fields and their types only; every response is still validated against the full schema before it is sent, and the full shape of an evaluation stays published at https://iris-eval.com/response-schema-v1.json. No tool's arguments, behaviour or response changed. - iris-eval.com runs only the scripts it issued. The site's Content Security Policy allowed inline scripts, so any bug that let text reach a page as HTML could also have run it as script. Every page now carries a fresh per-request nonce on the scripts it renders, with
'strict-dynamic'for the code they load, and addsobject-src 'none',base-uri 'self'andform-action 'self'; the API and static files get a policy that runs no script. Pages are now rendered per request instead of prerendered, because a nonce has to be new on every response. The footer's comparison links now list every comparison page, from the same registry as the pages. - The playground judges with the server's full safety libraries and says which release it runs. It ran a reduced subset of the server's PII, credential and injection patterns, without the obfuscation fold, and labelled itself v0.8.0 nine releases later. It now carries every pattern, both injection tiers and the fold, a test fails when any of them differs from the server's, and the version label comes from the package version rather than a typed string. Its rate limit no longer switches off when the shared store is unreachable or unconfigured: the same 30-per-minute limit then applies per server instance, and a limited response carries
Retry-After. - The files written for language models list what the site and the server actually have.
llms.txtlisted eight of the fourteen comparison pages, andllms-full.txtdescribed nine of the twelve tools the way they worked several releases ago, gave a Docker command without the dashboard port or the API key the image requires, and said Node.js 20 was enough after 0.17.0 moved the floor to 22.13. Both now render their comparison list from the registry the comparison pages are built from, their tool list from the summaries the server sends ontools/listwith each tool's long form (what it does, when another call is better, the errors it returns) from the same guide the server serves iniris://capabilities, and the Node floor fromengines, and a test fails when either drifts. Every install snippet inllms.txt,llms-full.txtand/.well-known/mcp.jsonnow readsnpx -y @iris-eval/mcp-server@<current release>(and the Docker image:v<current release>), so a copied config keeps running the version it was copied for; the README's client configs gain the same-y, which a client that starts the server needs because it has no terminal to answer npx's install prompt. answers_the_askadvises at the shipped thresholds, and names refusals and copies of the ask. At the defaults it failed 6 of 10 correct paraphrased answers, because they reused few of the ask's words, and it passed every refusal (too short to measure) and every copy of the ask (all of its words). It now gates only once you set akeyword_overlaportopic_consistencythreshold of your own. A bare refusal and the ask handed back each fire it directly, before the brevity skip. A decline that goes on to answer is judged on its content. A gate that relied on this rule blocking at the defaults stops blocking: set a relevance threshold to keep it.- An inline custom rule can fail a verdict. A
custom_rulesentry takes the sameseveritya deployed rule has:highorcriticalgate, and absent,lowormediumadvise. Before this, an inline rule could never fail a verdict, whatever it found. The note for an advising custom rule now says to set a severity, instead of blaming "a threshold Iris ships". no_piino longer vetoes an email address the agent was given. A support answer that repeats thereturns@address from the policy text in its input used to fail exactly like a fabricated answer did, so the critical veto could not tell a right answer from a wrong one. An address that appears verbatim (case-insensitively) ininputis not counted, and the pass message says so. Email only, deliberately: an SSN or a card number repeated back from a ticket still fails. A gate ondetector_vetostops tripping on those answers.- The health endpoints no longer report
trace_count.GET /api/v1/healthand the transport'sGET /healthanswer without a key, and the count told any caller how much data the server held. They still run the same storage check, sochecks.storageand the 200/503 status mean what they did. A script that readtrace_countfrom health readstotalfrom the authenticatedGET /api/v1/traces?limit=1instead. - Stdio enforces the same request size limit as HTTP.
security.requestSizeLimit(default1mb) bounded HTTP bodies only, so a 2 MB output was refused over HTTP with413and evaluated over stdio. A stdio request over the limit is now answered with JSON-RPC error-32600naming its size and the setting, and never reaches a tool; the session stays open for the next request. An agent on stdio that sent more than 1 MB in one call gets an error where it got a verdict: send less, or raise the limit. ArequestSizeLimitthat is not a size ("lots") is now refused at startup by name, where HTTP used to treat it as no limit at all.
Fixed
- On a loopback bind, a request with no
Hostheader is refused. The DNS-rebinding guard on the dashboard API and the HTTP transport checkedHostagainst the loopback allowlist only when the header was present, so an HTTP/1.0 or hand-built request that left it out, or sent it empty, was served. Such a request now gets the same403 Forbidden: invalid Host headera foreignHostgets. Browsers, MCP clients,curland the container health check all sendHost, so none of them is affected. A client that deliberately omitsHostagainst a loopback server must send it. Binds beyond loopback are unchanged. - The website says what Iris does, in the numbers Iris measures. The homepage showed "<1ms" eval latency while /proof measured a median near 9 ms, and "0 lines of code to integrate"; several pages said every agent uses Iris automatically, and pricing pointed to a Discord server. The latency stat now reads the median from the published proof, the integration stat says one config block connects, the copy says what happens (every MCP client lists Iris's tools on connect, and Iris scores every run it receives), support points to GitHub Issues and Discussions, and the hero's test count is the full total.
- The published per-rule numbers no longer include the 24 real transcripts, so the transcript line is genuinely held out — and several published numbers moved. 85 labelled cases in 13 rule families were copies of, variants of, or modelled on those transcripts, and the per-rule rates the verdict is composed from were partly estimated on them; the /proof page then called the transcript line "the only out-of-sample one". The cases are gone (each family header lists them), 24 newly authored cases refill the seven families that fell under the 24-case floor, and four composed cases built on transcript-derived family cases now use authored ones. Moved:
no_silent_tool_failurerecall 68.0% → 63.6% (17/25 → 14/22), F1 0.810 → 0.778;ask_coverageprecision 75.0% → 73.3%, recall 85.7% → 84.6%, F1 0.800 → 0.786;grounded_in_readsn 32 → 30;no_tool_loopn 42 → 37;sentence_countn 30 → 24;expected_coveragen 29 → 24;keyword_overlapn 30 → 24;topic_consistencyn 31 → 24;min_output_lengthn 29 → 24;non_empty_outputn 29 → 24;no_blocklist_wordsn 32 → 26;cost_under_thresholdn 26 → 24;verbosity_ration 25 → 24 — with their intervals (every one inproof/RESULTS.md). No verdict on the composite corpus changed; the composer's probabilities and calibration moved slightly (test-split Brier 0.253 → 0.255 for the risk composer). The transcript line is now described as what it is: 24 scripted runs of one agent, 18 of them bad by design (a 75% base rate), held out of the accuracy numbers but not blind to rule design, and not a sample of production traffic.transcript-results.jsongainstotals.stagedToFail, and a test fails if a transcript-derived case returns to any family. - Regression alarms fire far less often when nothing has changed. The drift watcher set each rule's alarm line on its own, so an agent running 25 steady rules raised about 14 false
regression_alarms per 1,000 evaluations, most of them soon after the baseline settled. The false-alarm budget is now per agent — about one per 500 of its evaluations however many rules it runs — and the line allows for the baseline being an estimate. On the same simulation the rate is 1.5 per 1,000, and the median first false alarm moved from evaluation 104 to 473. A baseline now also needs a standard error of at most 2.5 points before its rule is watched (about 260 evaluations at a 20% fail rate), and a real 10-point rise is flagged later than before: a median of about 210 evaluations for a 20%-rate rule on a 25-rule agent. Each alarm reports how many streams its line was set for;docs/webhooks.mdexplains the method.
Supply-chain transparency
- SBOMs:
iris-npm-sbom.spdx.json+iris-docker-sbom.spdx.json(attached below). Both are SPDX 2.3 JSON, cover direct + transitive dependencies. - SBOM signatures: each SBOM has a companion
.sigstore.json(Sigstore bundle — signature + cert + Rekor entry) attached to this release. The release workflow runs this exact verification itself before it reports success. Verify with:cosign verify-blob \ --bundle iris-npm-sbom.spdx.json.sigstore.json \ --certificate-identity='https://github.com/iris-eval/mcp-server/.github/workflows/release.yml@refs/tags/v0.18.0' \ --certificate-oidc-issuer='https://token.actions.githubusercontent.com' \ iris-npm-sbom.spdx.json - npm provenance: published with
--provenance(verifiable vianpm audit signaturesor on the package page). - Docker signature: image signed with cosign keyless (Sigstore). Verify with:
cosign verify ghcr.io/iris-eval/mcp-server:v0.18.0 \ --certificate-identity='https://github.com/iris-eval/mcp-server/.github/workflows/release.yml@refs/tags/v0.18.0' \ --certificate-oidc-issuer='https://token.actions.githubusercontent.com' - Build attestation: both the npm SBOM and Docker image manifest carry GitHub-signed build-provenance attestations. Inspect with
gh attestation verifyorcosign verify-attestation.
What's Changed
- fix(no_pii): an email address the agent was given is not a leak by @irparent in #601
- fix(verdict): answers_the_ask advises at defaults and names refusals; inline rules take a severity by @irparent in #603
- fix(delete_trace): a deletion leaves an audit record the agent can read by @irparent in #604
- perf(tools): cut the tools/list payload from 104,567 to 44,285 bytes by @irparent in #606
- docs: comments, test names and changelog read for outside contributors by @irparent in #608
- fix: loopback Host guard, one request size limit, health without counts, verdicts name rule changes by @irparent in #605
- fix(website): llms files from their sources, the playground on the full safety libraries, a nonce CSP by @irparent in #607
- fix(proof, drift): hold the real transcripts out of every per-rule number; budget regression alarms per agent by @irparent in #609
- fix(website): public copy matches the facts by @irparent in #602
- release: 0.18.0 — honest numbers, a lighter footprint, nothing done silently by @irparent in #610
Full Changelog: v0.17.0...v0.18.0