Repository navigation
v0.5.2
[0.5.2] — 2026-10-20 — Early Access
This engine pairs with web console 0.5.0, which is published after it under its own
webconsole-v0.5.0 tag. Its UI seam is 6d8b36eb7bb94bf0, so with the console on it refuses to
start beside console 0.4.0. Upgrade the two together, or set
[security].serve_web_console = false. A BACKLOG #N number cites the project's private planning
ledger, so it has no public page.
Added
- Federated sign-in can authenticate to the token endpoint with
private_key_jwt. messagefoundry admin-reset-totpreplaces a sole Administrator's authenticator seed from the
host.- The VS Code security editor can set the four per-tier retention acknowledgements.
messagefoundry checkprints an advisoryoauth-requestline.- An interop lab proxy checks what the engine signs with verifiers it did not write.
- The audit trail, a user's security events and the connection event log page past their
first window. - The approver queue says what a release would run and whether it can.
messagefoundry checkprints an advisoryfhir-update-if-matchline.- A start now writes a
config_loadedaudit row, andGET /config/provenancehas a baseline
from the start on. - A log forwarder that is absent or losing records now raises an alert and shows on
GET /status. - The test harness covers transports through pluggable drivers and sinks.
- Hostile-content scenarios for the test harness.
- The test harness covers raw TCP and X12
- The test harness covers the Http inbound and the REST, SOAP, FHIR and DICOMweb outbounds
- The test harness has a DATABASE family: a DatabasePoll inbound and a Database outbound.
- The test harness drives DICOM DIMSE end to end.
- The test harness covers the Email outbound, and proves a Direct round trip.
- The test harness covers REMOTEFILE over SFTP
- The test harness covers the TIMER, LOOPBACK and PassThrough inbounds
python -m harness --coverageis a gate.python -m harness --fuzzfuzzes a running engine.- Five more Atheris fuzz targets
- A script the remote-debugging guard refuses is now audited, and it raises an alert.
- A refused CRL reload now raises its own alert,
crl_reload_refused. - The
/config/reloadresponse carriesrestart_required, the settings trust anchors a
Changed
- The engine no longer says nobody can sign in when no Administrator exists.
- The MFA posture texts say which sign-ins the second-factor switches free, and no longer say
"admin". messagefoundry cert self-signednow calls its pair a placeholder, not a dev-only
certificate.admin-set-notify-emailandprovision-adminexit 2, could not start, when the store cannot be opened or the audit chain's key check refuses the command's audit row.- The keyless-backup refusal no longer calls
[backup].allow_unencrypteda setting for a
synthetic or no-PHI box. - A trust-anchor path that is set is checked even when nothing loads it, and the docs now say
so. - A client sign-in now ends the session it replaces inside the sign-in request.
[backup].allow_unencrypted = trueis now a reported loosening, with a guide entry.tray.logand the IDE's "MessageFoundry Checks" output now carry UTC time stamps.- ADR 0200 Amendment A is accepted.
- The shipped Kubernetes manifests set
enableServiceLinks: false. messagefoundry security showsays why a report is partial.[backup].destination,[dr].seed_archive,[dr].seed_dirand[dr].restore_tokenrefuse
a<scheme>://URL whose scheme has two or more characters, and the refusal quotes the scheme and no longer
the URL.servelogs the unread-MEFOR_*-variable warnings again once logging is configured,- On Windows, a secret reference written in another letter case still spares its variable.
- The development standards now describe review and planning as the process runs them.
- The
[dicom]extra now requirespynetdicom>=3.0.4,<3.1, where 0.5.1 allowed<4. - CI and load rigs sign in.
create_managed_appbuilt with noauth_settingsand withoutallow_no_authnow logs one- A TCP connection refuses
0x20(a space) as itsstartorendframe byte. - A cluster node refuses the connection flag toggle.
- Engine config writes record their own baseline through one helper.
- A config write whose audit row does not land is undone and refused.
- The API client waits longer for a sign-in's reply.
- A directory refusal at the second factor, an enrolment or a step-up names the fix for its
cause. - A File inbound refuses a negative
min_age_secondsat build. - An upload no longer runs the uploaded-logs retention sweep on its own request.
- Open-mode follow-ups: one check, one 503 text, and removed-key refusals that name the right fix.
- Breaking:
[dr].enabledwith[cluster].enabledis refused at config load - A released replay or purge whose COMMIT reply was lost is now documented as recorded
failed. - A refused CRL reload is reminded about while the refusal stands.
- A paged LDAP group search has one overall deadline, and its cap counts groups, not pages.
audit-verifyprints aFAILline and exits 1 when a key, key-provider or database error
stops the check while it reads the chain's rows- The repository's locks, and so the container image, move FastAPI from 0.143.0 to 0.143.1, and
pyproject.tomlnow requiresfastapi>=0.143.1. - The
[harness]extra now requiresPySide6>=6.6,<6.12, where 0.5.1 allowed any later
release. - Linux is not a supported v1 deployment target.
- BREAKING -- an unknown top-level section in
messagefoundry.toml, or a key written above the
first[section]header, now fails the start instead of loading silently.
Fixed
- An engine that restarts settles the dual-control releases it left
executing. - A shutdown cancelled partway through still stops the engine.
- A held config reload whose directory vanished, or that named a directory outside the reload
roots, is refused on release instead of answering 500. - The approval gate answers a mapped status during an audit or store outage, and pages.
- Each approval state change and its audit row are now one write.
- A repeat of an open dual-control request no longer files a second one.
- A held request is no longer released after dual control stops applying to it.
- An MLLP, HTTP or DICOM listener's CRL refusal now names the connection and its
tls_crl_file. - The admin-write
429now says how long to wait. - The JSON step-up refusal tells an
oidcsession to step up at the IdP. POST /me/reauth's own refusal of anoidcsession now carriesX-Step-Up-Via: idp- A 2xx reply with a body over the byte bound is no longer sent again.
- A reload refused over an unreadable inbound CA now audits
reason: "trust_anchor". - The feature map and the ADR index no longer call the anonymizer's output PHI-free.
- The anonymizer no longer refuses a legal empty segment, and its coverage line is harder to
misread. - The save-time upload prune now writes an
upload.prunerow for every file it removed, even
when the request is cancelled. - A cancelled upload save now removes a file that already landed.
- A cancelled upload save now pays its cross-engine-shard quota slot back.
- The upload retention stop logs an audit gap only when one exists, and names the files.
python -m tee anonymize-capturesreadsanon.tomlonce, before the first message.- The
intake_pausedalert keeps reminding while its probe fails, and its reminder no longer
slips an interval. - Alert state writes for one condition now reach the store in the order they were raised.
- Each engine process now raises and clears its own
intake_pausedalert. - A log spool that cannot be read now says so.
- On-disk log spool follow-ups.
- Session follow-ups from the session-cap work.
- A restart inside the temporary-password warn window no longer sends the expiry reminders
again. - A CRL expiry alert now says when its date belongs to a running hop's older copy.
- The secret-expiry warning no longer says a class "only alerts" where nothing alerts.
rotate-keyno longer seals a lone plaintext upload sidecar over a missing or non-text body.- An HTTP Digest challenge the engine will not answer now stops the outbound lane and keeps its
queue. - The intake pause WARNINGs now say what a DICOM inbound does.
- The keyless
servemessages name the opt-out you set. - A sign-in or PHI-read rate-limit window that is not a finite number no longer loads.
- A TLS list the linked OpenSSL refuses is now a named configuration refusal.
- A dry run or a failed config reload no longer moves the engine's record of where its running
config came from. - Three statements in the operator documents now match the code.
- More security documents now match the code.
- The Corepoint importer no longer treats a statement written off a
<Line>as run. - A slow upload no longer loses its cross-shard quota slot to a sibling engine shard.
- The auth rate limiter drops a key's aged bucket even when that key never returns.
- A replayed routed row whose Handler then sends nothing now settles.
messagefoundry validateandmessagefoundry graphsend aprint()in a config module to
stderr.- DR release, DR activation and a config reload now finish when the request deadline cuts the
caller off. - The
dr.releaseaudit row records whether the queue drained. - The config loader's
_-helper imports no longer reach the rest of the process, and work inside
a Router or Handler body. - Four more files no longer call the anonymizer's output PHI-free.
- Anonymizer follow-ups: a false refusal, a raw error, a missing count, and refusal texts
that changed. - An alert webhook that answers 2xx with an over-size body no longer counts as a failed send.
- A delivery whose 2xx reply body was dropped over the size bound now says so on the message.
- A permanent refusal that dead-letters now writes a WARNING.
- A RemoteFile destination with
encoding = Nonenow uploads as UTF-8. messagefoundry checknames the settings file it graded whenservemay read another.- An operator start or restart that a listener's start gate refuses with a configuration error
now answers 409 and is audited. messagefoundry connection upsertandremovename--service-configin a cleartext-listener
refusal- A directory reconcile pass no longer files a false rename conflict from a stale holder read.
cert self-signedrefuses an existingcert.pemand cleans up its key on failure.- The directory reconciler says which reconcile alerts it will not resolve.
- The DR backup and the subprocess sandbox follow the running config dir.
- A DR activation's trust-anchor preflights are now bounded, and its abort rows say more.
- A CA file that is not PEM is now a named refusal, and a DICOM suite refusal names its
connection. - Listeners that share one CRL file no longer each log the scan's held-copy warning.
- The undeclared-proxy warning names both ways to declare a proxy.
- A cluster convergence reload's audit rows name that reload.
- A start's
config_loadedrow holds up when a reload or a snapshot fault interrupts it. - The delta CRL refusal now gives both OpenSSL rules, and corrects the 0.5.0 entry's reason.
- A refused or malformed hop attestation is now reported plainly.
- A passive DR box binds no inbound listener.
- The approval routes answer a failed store read with 503, not a raw 500.
- Only a missing config directory is answered as
not_found. - A shutdown drain no longer cuts a reload's own audit row short.
- A cancelled resolve whose write fails logs one ERROR, not two.
- A CRL expiry alert no longer advises a restart into a CA-file CRL that would refuse peers.
rotate-keyfails, and writes no key-rotation stamp, when it skips an uploaded file.- The
upload-cipheralert and the store cipher's refusal of a plaintext upload no longer
promise thatrotate-keywill seal it. GET /approvalsnames a release the gate always refuses.- A failed approval request write is read back, and the write outlives its caller.
- The retry-cap dead-letter WARNING now follows what the store wrote.
- Engine shard names are no longer scrubbed out of two log lines and two refusals.
rotate-keycounts a plaintext upload body it sealed even when that upload's sidecar could
not be read.rotate-keyexits 2 when the Vault client for the store key cannot be built.- An embedded approval gate no longer fixes the default claim owner before its engine holds
its graph. - The approval queue warns about a request with unreadable params at most once per five
minutes per request - On Postgres and SQL Server, the engine-shard upload reservation now reads the database server's
clock. - A passive DR box delivers nothing.
- A passive DR box shows a broken critical feed at start, and an activation can fail one
outbound without refusing the takeover. - The start WARNING about no usable local Administrator no longer loses its first name to the
credential scrub. - The PHI-read and sign-in
429s no longer send a literalRetry-After. - A negative
[auth].admin_write_rate_limit_per_actoris refused at load. - A protocol header floor self-test that fails with an exception now says where.
- A removed key's refusal no longer says that no environment variable set it.
- A refused anonymizer salt now says that a random salt can be refused, and what to use
instead. - The
trusted_proxiesloosening names each range once, in list order within each wording, with
its other spellings, and an IPv4 entry's mapped twin can now make IPv6 ranges read as trusting every peer. - An integer setting refuses a bool or a fraction instead of truncating it.
- A float cap or timeout refuses a bool, a NaN, an infinity, or text that only rounds to zero.
- A connection whose target urllib's bypass list names goes direct cleanly.
- An
anon.tomlrule onMSH-1orMSH-2is now refused when the overlay loads. - A bad
anon.tomloverlay is reported more accurately, and earlier. - An
anon.tomloverlay that points a default field at a different surrogate kind now warns. provision-admintells the earlier holder before it writes the new--emailaddress.- A row that runs out its retries now writes a WARNING when it dead-letters.
python -m harness.load.rigadmin runpasses the session as one--token=VALUEargument.rotate-keyrefuses a Vault Transit key of an unsupported type with a clean exit 2.- Harness engine nodes and the durability-hook tests no longer leave processes behind.
- An X12 outbound acts on a TA1 only when its TA1-01 names the interchange it just sent.
Security
- A settings error no longer shows the mapping it refused.
messagefoundry check-privilegesnow probes the Vault tokens and the AD bind account.- SECURITY.md records one more limit of the sign-in time floors.
serveandsupervisenow test the protocol header floor at every start, and refuse on a
failure.- The harness HTTP sinks answer Python's own error pages in plain text, not HTML.
- Every answer the harness HTTP sinks write now carries the baseline security headers (ASVS
3.4.4 and 3.4.6, and thebase-uripart of 3.4.3). - The protocol header floor now covers uvicorn's sans-I/O WebSocket protocol, and uvicorn moves
past its<0.50cap. - uvicorn and websockets are pinned exactly, to
uvicorn==0.54.0andwebsockets==17.2. - websockets moves to 17.2 under the protocol header floor, and the floor refuses at start when
an attribute its two uvicorn workarounds use is gone. servenames a web proxy it may pick up from the environment.- The deployment documents now state what a proxy in front of the engine must do about request
framing. - The ECH egress sidecar contract states a framing rule.
servepins the API's HTTP parser to httptools (llhttp), and pyproject pins it exactly, to
httptools==0.8.0.gzip_decompresscaps the number of gzip members atmax_members, default 1024.- Every decompressor checks its caps in a trial pass before the real decompression.
- The File connector has a
max_decompressed_memberssetting, default 1024. - An SVGZ attachment is checked by trial passes before the pass that keeps it.
- A File inbound with
decompress=set warns whenmax_decompressed_bytesis off. - An
[api].trusted_proxiesentry that covers more than one address is now named as a security
loosening. provision-adminnow requires--email.- Three more admin routes refuse your own account: the admin session revoke, the role write and
the channel-scope write. - The live monitor socket and the bulk export check the caller again while they run.
- The VS Code extension's cached AI policy expires when it enables assistance.
- The audit export checks the caller again before each page.
- BREAKING: XML signature checks now refuse weak EC keys and every DSA key.
- Five more compares run in constant time.
- An IDE error from a
--jsonCLI call no longer quotes the CLI's output. messagefoundry dryrunreports an unexpected error that stops its per-message loop by class
only.messagefoundry dryrunsends aprint()in a config module, Router or Handler to stderr.- A custom role can no longer hold
messages:view_rawwithoutmessages:view_summary. - The IDE's Live Debug reveal now shows one message.
- The IDE's Live Debug no longer shows error text on a masked run.
- The captured-reply and outbound-payload bodies now need
messages:view_summaryas well as
messages:view_raw. cert importnow checks a private key inside an encrypted part of a PKCS#12 bundle.fhir_lookuprefuses a resource id that is only dots.- A bad OIDC authorization code can no longer hide the federated sign-in link, even when the
identity provider answers it with a 5xx. - An alert rule that can silence a credential reminder is now named as a security loosening.
- Resolving an interrupted approval now flags a resolver whose account changed after the
request. messagefoundry checkhas a new required leg,oidc-revocation.- More directory sign-in refusal rows record the client address.
- A directory revocation that never read the disabled bit no longer tells the holder an
administrator disabled the account. - The IdP step-up compares the IdP's clock with the IdP's clock.
- The first-seen sign-in address signal reads a per-account record, not the audit log.
- Signing in again ends the replaced session even when an elevation races it (ASVS 7.2.4).
- A federated step-up refuses an account demoted in the directory since sign-in.
- The first-seen sign-in address rows now say which directory leg signed in.
- The mid-session new-address signal now reports each new address once per session, until the
session re-verifies. - A federated step-up now records whether the browser's address moved mid-ceremony.
- Behind an ECH sidecar, a CRL no longer lifts the revocation refusal on a SMART or OAuth2 token
endpoint. - A bare CR in a web proxy's own reply to
CONNECTis now refused. - A CRL that reaches a REST, SOAP, FHIR or DICOMweb destination now lifts its revocation
refusal. - Four more verifying outbound hops are refused when they check no certificate revocation.
- A test connection on a REST, SOAP, FHIR or DICOMweb destination now re-checks the hop as a
send does. - A hop URL that names no host is now refused, in any posture.
- Two directory audit rows now commit in the same transaction as the change they record.
- The SQL Server store's
[store].ssl_root_certpin now takes effect. - Turning a credential reminder off, or shortening its lead, is now named as a security loosening.
- Two hop-policy flags must now be real booleans on a code-first declaration.
- Verifying a directory account's passkey now needs the directory to confirm the account.
- A directory account demoted in the directory since sign-in can no longer step up with its old
roles. - One account whose directory probe raises no longer ends the reconcile pass.
- Plain
anonymize()now refuses a line no rule can reach. - A loopback bind that trusts a proxy now counts as exposed.
- Saving a legacy directory scope over the JSON API now needs
expected_source: "ad". - An ungated config reload proves the audit log works before it swaps the graph.
- A config reload's audit row names the config that reload loaded, even when a second reload
overlaps it. - The tee anonymizer now applies a rule that names an MSH field.
- An
anon.tomlkeep that turns off a default scrub now says so. - A trust anchor that names an encryption header now refuses.
- A refused channel-scope save now leaves an audit row.
provision-adminrefuses to complete an existing roleless account that already holds a
notification address unless--emailis given.- A session whose temporary password has lapsed is ended, and can no longer finish the second
factor. POST /me/passwordends the session when it refuses a lapsed temporary password.- docs/SECURITY.md now describes the session-level refusal of a lapsed temporary password
- An unreadable
anon.tomlis refused without carrying the file. - An mTLS certificate identity for a directory account now asks the directory on every request.
- A Vault hop refusal now reaches the operator in its own words, and it is one type at both
phases. - A blank
acroramrsetting no longer counts as MFA. - The anonymizer's default rules now map eight more date fields.
- A
FieldRulebuilt in code now has its path checked. - A date-typed
OBX-5is now treated as a date. - The site-code pass no longer rewrites a filled date.
- A date scrubbed to empty now leaves a record.
- A six-digit date that reads two ways is now scrubbed to empty.
- The
dobkind keeps only a time after the date. - A JSON caller can read an account's federated binding before it changes it.
- A vault_transit store now needs a recorded attestation of its AES-GCM bound to start under
enforce. - On vault_transit, rotating the Transit audit key no longer reads as audit-chain tampering.
- The anonymizer's leak check now loads the publish guard from one fixed path.
messagefoundry checkreads an instance declared in the environment alone.- The off-box log collector is now an
[egress]destination. - The engine-shard supervisor now forwards its own log records off-box.
- A trust-anchor refusal and its log lines now quote the anchor path with
repr. - A connection's own keep-forever retention override now needs the audited acknowledgement.
- Per-connection
tls_ca_filefollow-ups. - The
tls_ca_fileof each connection that dials out is now checked. - Where a refusal lands follows ADR 0031.
- A pin nothing would check is refused at load.
- One gap remains: the hop still reads the file again by path when it is built, after the check.
- A credential reply is served
Cache-Control: no-storeby its response model, not by a step
each route had to declare. - The log-forwarding start gate now refuses a collector that is this host.
- A security notice the relay loses now leaves an audit row.
[api].expose_docs = trueis now a named loosening.- The attachment download's two 422 refusals are now audited.
- An identity provider that signs people in with no prompt can be exempted from the OIDC callback
floor byamr. - The docs now say which database driver key the engine checks.
- The session reconciler no longer asks the directory about an id-less row by its name.
- An empty password on the AD step-up re-bind is no longer counted as a wrong guess.
- The step-up re-bind tells a disabled directory account from an absent one.
- A reader whose permissions withhold audit rows is told so, and an OIDC-only auditor can
export. - The engine API refuses a request that carries more than one
Authorizationheader. cert importtakes an empty PKCS#12 passphrase whenMEFOR_PFX_PASSWORDis set to the empty
string.- BREAKING:
[api].trusted_proxiesrefuses a CIDR with host bits set, such as10.0.0.1/24. - A shortened dual-control dwell is now named as a security loosening.
- An alert rule's
control_actionno longer restarts a connection on an event that is not about
one. - An alert rule with no
control_targetno longer restarts a connection on the other side. - An LDAP referral now pages the directory reconciler instead of reading as an outage.
- An AD connect or receive timeout above its default is now a named loosening.
- A FHIR connection set to
update_url_form = "path"is now on the posture read-out. - A proxy URL that carries credentials is refused when they would reach the proxy in
cleartext. - On the generic ODBC dialect, no
odbc_paramskeyword can give the driver another host. - A loopback hop is never sent through a web proxy.
POST /dr/activateandPOST /dr/releaseask for step-up.POST /config/reloadreads the path as text before it touches it.- An archive named in a
POST /dr/activaterequest is confined to the new[dr].seed_dir. - The DICOM C-STORE server (SCP) applies
source_ip_allowlistwhen it accepts a connection, and
bounds its inbound TLS handshake. - A child process of the engine no longer inherits the engine's whole environment.
- Dual-control approval requests carry an integrity seal on a store that holds a key.
- Custom role definitions and both directory maps carry an integrity seal on a store that holds
a key. - Session rows carry an integrity seal on a store that holds a key.
- Each account's authorization record carries an integrity seal on a store that holds a key.
- A message's cells are sealed under a tag that binds the message's channel, and a saved search's
criteria under one that binds its owner. - A queue row's payload is sealed under a tag that binds the row's routing fields.
- A store that holds a key now requires every audit row keyed, and the audit chain numbers its
rows. - A store key in the Windows service's NSSM environment now refuses the start.
- A released config reload loads only the config its approval named.
- A start now alerts when its config differs from the one the store last recorded.
MEFOR_ALLOW_INSECURE_CONFIG_SOURCEis refused under[security].enforcement = enforce.- A mistyped
MEFOR_<SECTION>_<KEY>variable now refuses the load. - Four
[auth]switches are now reported loosenings whenfalse: - Three numeric
[auth]settings are now reported loosenings when set looser than they ship: - A
MEFOR_*variable that names no section and looks like a setting now logs a WARNING. - A file that holds a key is created restricted, and
protect-keyfails closed. - The engine refuses a route that declares no authorization.
- The route walk in
scripts/security/route_gates.pysees more. - The egress allowlist now denies by default everywhere, not only under
serve. - The raw-TCP and X12 listeners gain MLLP's per-host connection cap and frame deadline, and every
socket listener throttles its allowlist refusal log. - Windows SSO refuses an account that holds a federated binding, and every bind ends the
account's sessions. - A directory sign-in that newly grants Administrator now raises the
administrator_granted
operator alert. - The AD group maps take a full distinguished name only; a short name is no longer accepted.
- Group map keys and a user's groups are compared in one canonical DN form.
- A Windows SSO step names a client only when the Kerberos context that checked the ticket
finished. - BREAKING (embedders only):
create_managed_appdenies by default. - Six security signals now raise an operator alert, not only an audit row.
- An edit-resend can be proved after retention blanks its bodies.
[egress].allowed_recipient_domainsbounds where anEmail()destination may send.- A session token replayed from a second address no longer reads message bodies or calls paced
writes. messagefoundry checknow runs the four inbound TLS exposure gates.- BREAKING: an off-loopback DICOM server (SCP) no longer counts a too-wide
source_ip_allowlist
as a peer control. - A DR takeover hook that runs past
[dr].takeover_timeout_secondsis now killed, with the
processes it started. - Operator actions now commit their audit row in the same transaction as the change.
- Injection and bulk export take a step-up proof bound to the action.
- A repeated purge or reload still rejoins its open dual-control hold.
- Upload resend needs
messages:editbesidefiles:browse. - The VS Code extension's promote re-proves when the engine asks.
- The Windows service installer no longer defaults to the checkout.
- The config trust check reads the cache folder,
connections.tomland the code-set tables serverefuses to start when another account can write its settings file or value filemessagefoundry service installrequires--configinstall-service.ps1refuses an engine install that anyone but administrators can write.serveandsuperviserefuse to start as a Windows service without an absolute
--service-config.- Channel scope on the management API is now checked by type and by a route table, though list
routes still apply it by hand. - Every PyPI publish now runs in a job that only publishes, and names the
pypi
environment. - A release now refuses a tag whose commit is not on
main, or whose required checks did not
pass. - No asset is added to a GitHub release after it is published.
- Every remote pre-commit hook repository is pinned by commit, not by tag.
- A credentialed FTPS hop with either certificate check off is refused at construction, as SMTP
refuses it (vault BACKLOG #2636). - Refusals and warnings that name
MEFOR_ALLOW_INSECURE_TLSnow say it works only on an
instance at[security].enforcement = warn. - A scoped caller now gets 403 for every connection name it cannot reach.
- The anonymizer's default rules now map nine more guarantor, insurance and order fields.
- The support bundle and
GET /logs/tailnow also run the write-time credential scrub. - The engine process refuses a script injected through Python's remote debugging, and reports
the setting. serveandsuperviserefuse to start underenforcement = enforcewhen Python remote
debugging is enabled in their process- The engine service starts in isolated mode, with remote debugging off.
serveandsuperviselist the start-up code in their interpreter, and refuse what no
package records.- The container image's virtual environment is owned by root.
- The engine's Python children repeat its isolation.
- The Windows service installer limits the engine's token.
- The web console seam digest covers each console route's gate.
- A start warning when no Administrator can sign in without an outside service.
provision-adminruns whenever no enabled local Administrator has a usable password.icaclsandscno longer get the engine's whole environment.- BREAKING:
servealways requires sign-in, on every bind. - A sandbox worker that loaded edited config code is refused.
audit-verifyno longer reports a chain it could not check as a broken one.- One setup is weaker than before.
- A store key the settings name that cannot be resolved while the store opens, such as
- A gated JSON route refuses a caller with no session before it reads the request body.
- Every
messagefoundrycommand installs the remote-debugging guard, before it parses its
arguments. - Turning off a size cap on an HTTP, File or SFTP/FTP inbound now logs a warning.
- The POSIX config-source check trusts an owner that is root or the engine's own user, and
only root when the engine runs as root. - A production instance refuses
DEBUGlogging at run time, as it does at start-up. - BREAKING: an attachment the sender labelled
text/csvnow downloads astext/plainwith a
.txtname. - A FHIR
transactionorbatchconnection now screens every Bundle entry before it posts. FhirPeek.parsenow refuses a FHIR body in which a JSON object repeats a key.- Every log sink now escapes the C1 controls, the bidirectional controls and the Unicode line
and paragraph separators, not only C0 and DEL. - No setting can turn sign-in off, in a config file or in code (vault BACKLOG #2825).
- A start's
config_loadedaudit row names the graph that start loaded. - A DR activation keeps the graph the running engine serves.
- The DR seed marker's config digest no longer fails an activate or a release.
- An
Email()destination'ssenderis now held to the recipients' address rule. - A mail subject or alert template holding a lone surrogate is refused at load (vault BACKLOG
#2842). - Three domain lists and the mail address check now share one host-name rule.
- The FastAPI floor is now 0.140.0, superseded in this release by
fastapi>=0.143.1. - The engine now counts every refusal of a route that declares no authorization.
- The route walk in
scripts/security/route_gates.pysays what the refusal makes of each row. - The MLLP frame deadline now starts on the first byte of any kind, and
stop()clears the
per-host counts in afinally. - An MLLP or TCP delivery refuses a payload holding more than one message.
- A
Direct()destination and the[alerts]email sender now hold their addresses to the
Email()destination's rule and pass the SMTP envelope explicitly. - BREAKING: a start now refuses a CRL file that holds a CRL proven badly signed.
- The email address rule now checks the shape of the domain, not just its characters.
- An
Email()destination'ssenderdomain must now be in[egress].allowed_recipient_domains. - An administrator's password reset or MFA reset clears the account's known sign-in addresses.
GET /security/posturenow reports how often the deny-by-default refusal fired.- Four older edges of the route-gate walk and the deny-by-default refusal are closed.
- The anonymizer keeps a numeric
OBX-5only when it fits the grammar of its own type. - A release goes public only after every PyPI upload its run makes.
- Each Vault hop now refuses an off-box Vault whose certificate revocation nothing checks,
unless that hop is attested. - Follow-ups to the refused HTTP Digest challenge, and a 2xx reply that cannot be read is no
longer sent again. - The de-identification site-code loader reads one fixed token path and no longer searches
parent folders. - A de-identification rule on a
BHSorFHSfield now reaches the field the leak-check
counts. - A line that is a bare segment id now leaves a coverage record, and the anonymizer's
leak-check reads one header line. - A refused
FhirLookupno longer stops the engine from starting. - An LDAP search that does not end with success is never "no such account".
- A passkey registration staged before the account gained a factor can no longer finish.
- A refused audit write in one account's directory reconcile no longer ends the pass.
- On SQL Server, the session writes that follow a committed auth change now wait longer for a
busy pool, and the wait is still bounded. - A sign-in now ends the session its presented token became, when an elevation finished first.
- Sign-out and revoking one of your own sessions no longer report a revoke that missed (ASVS
7.4.1). safe_excno longer quotes the character or byte an encode or decode error failed on.- BREAKING:
[api].trusted_proxiesrefuses a zoned entry, matches an IPv4 entry in both
spellings, and two[api]lists split their env form on commas. - The new-address audit action is renamed
auth.new_address_reverify_required. POST /me/notify-emailnow asks the new-address question, as its console twin does.- A console page refused for a new address comes back with its path still encoded.
audit-verifyexits 5, not 0, when a keyless chain walks clean in a shell whose settings
require a store key.audit-verifyloads its settings asaudit-anchordoes, so a settings error is rendered by- Enrolling a factor on a directory account now needs the directory to confirm the account.
- The open mode is an app with no auth service, and the code now says so.
[inbound].ack_after = "delivered"is refused when the settings load.- The build check gates the same listeners a reload binds, from one copy of the skips.
- A DR activation on a running box now parks the feeds below the threshold.
- A DR activation applies the running graph, not the config dir on disk.
- Rows queued to a parked outbound are held, not dead-lettered.
- Start, stop and restart of a DR-parked outbound are refused with
409. - A second stop of a paused outbound no longer leaves it
stopping. - Two more domain checks at load.
- A cluster convergence reload now writes its own
config_reloadaudit row. - A clustered node converges on a sibling's reload that lands during its own start.
- A repeat reconciler revocation of one account is audited but not alerted, and only a verified
repeat stays quiet. - The engine names directory reconciler settings that leave the mass-revoke breaker unable to
trip. - Authoring docstrings and connection-editor help that name
MEFOR_ALLOW_INSECURE_TLSnow say it
works only on an instance at[security].enforcement = warn. - A directory probe that raises is now a probe fault, not a directory outage, and it pages.
- The MFA requirement's enrol-first gates now cover an account with an unrecognised provider.
- A lock notice the relay's full queue drops no longer holds the next one back.
- A narrowed dual-control list is now named as a security loosening.
- The sandbox worker's memory cap no longer fails innocent messages on Linux.
env(key, cast=bool)now reads a boolean strictly.- A directory reconcile pass that newly grants Administrator now raises the
administrator_grantedoperator alert. - A refused federated step-up records where it started.
- The administrator who resets an account's factors is now sent the expiry reminder for the
temporary password that reset issues. - An AD re-bind the domain controller ends busy is an outage, not a wrong password.
- The session reconciler counts a referral or a probe fault over the lookups it sent, and strikes
an id-less row on every pass. - Federated step-up and binding follow-ups.
- A request deadline can no longer leave an uploaded file without its
upload.createrow, or a
deleted one without itsupload.deleterow. - Each upload-quota reservation now has its own identity.
- A codec error no longer puts the input it failed on into a log line or a refusal, on at
least the paths below. - A crafted SPNEGO token is now an audited Kerberos refusal, not a 500.
- Three more routes now answer by channel scope.
- A refused Kerberos token is audited as a refused sign-in, and the row no longer quotes the
token. - The new-address step-up row records the address in its client column.
- A forged federated sign-in callback no longer uses up the real one.
GET /security/posturereads the[auth]limits off the attached auth service.- A released dual-control purge now writes its
connection_purgerow, and alert acknowledge,
resolve, suspend and resume commit their audit row with the change. - A refused OIDC callback that the floor exemption let through now carries the
callback_floor_exempt_amrmarker. - A misframed JWKS reply no longer hides the federated sign-in link, and a verified token
shows it again. - An AD group map key that starts or ends with a non-printing character is refused.
- A Windows sign-in whose name matches several directory entries can now succeed.
- A directory role change that takes Administrator from its last enabled holder now raises an
alert. - No escape sends a credential to a peer the engine has not verified (vault BACKLOG #3230).
- Approving a held action needs a fresh step-up.
- Edit-resend gives back its proof when the engine refuses before it writes.
- A Vault provider's error keeps the text of the Vault hop's own refusal only.
- The Vault client build no longer raises a parser error that quotes a proxy URL.
- An SVG or HTML attachment download refuses an
svgstart tag after leading text. - The admin routes that refuse your own account now also check the stored id, and each refusal
is audited. - A self-service revoke of one session that ends nothing is now audited.
PATCH /users/{user_id}now checks a changedemailthe wayPOST /usersdoes.- More of the routes by which a codec error could put the input it failed on into a log line or
a refusal are closed. - A failed inbound TLS handshake now leaves a record on the HTTP intake listener, the API and
the MLLP listener, on any event loop. - A sign-out's
auth.logoutaudit row records the client address it came from (ASVS 16.2.1). - The test harness's HTTP sinks no longer send a
Serverline. - The bulk export stops when the caller's channel scope widens mid-stream, not only when it
narrows. - A deliberate export stop no longer reads as a crash in the server log.
- Known gap, not fixed: a stopped export reaches the client as a complete-looking short
file. - BREAKING: a keyed store now remembers which encrypted columns it has keyed.
- BREAKING: the Direct transport now refuses DSA keys and every unlisted key type.
- The first-Administrator service now requires the notification address, as
provision-admin
does. - A credential in a proxy URL no longer reaches a second proxy in cleartext.
- On Python 3.15 with OpenSSL 3.4 or later, the apiclient and the tray probe stop offering
SHA-224 TLS signature schemes too. - A
proxy_usercredential bound for a proxy writtenhttps://is judged as cleartext. - A proxy credential on a request that goes direct is refused.
- A proxy credential sent over TLS that does not verify the proxy is refused, admitted or
not. - A trust-anchor pin may name its digest, and each
auth.trust_anchoraudit row names the digest
of its fingerprint. - A static
Proxy-Authorizationbesideproxy_useris refused at build. - The CRL signature texts now say only what the engine does.
- The account seal checks the TOTP code against the secret it checked, and names its rollback
limit. - A CRL's issuer name is matched to the hop's CA as OpenSSL matches it, for ASCII letter case.
pyproject.tomlnow requirescertifi>=2026.10.19, past
GHSA-6q48-6c7w-9gw2.- The
audit-pinjobmessagefoundry initgenerates now audits the Windows-only dependencies. - The harness X12 sink never echoes an ISA13 that is not nine digits into its TA1 (ASVS 1.1.2).
- The harness Compose tab frames through
frame_checkedby default. samples/send_mllp.pyprints the peer's ACK as printable ASCII.messagefoundry verify's live smoke frames throughframe_checked- More console lines, and the Markdown verify report, now escape a peer's text (ASVS 1.1.2).
samples/send_mllp.pyrefuses a file holding an MLLP frame byte (ASVS 1.1.2).- More of the tools that print a peer's text now escape it first, and harness labels fed by the
engine show it as plain text (ASVS 1.1.2). tls_check_hostname = falseis a recorded loosening now, never a silent one (ASVS 12.3.2).- A credential in a connection
url's query string is a recorded loosening now (ASVS 14.2.1). - The Vault TLS context now pins the approved key-exchange groups, as every other engine-built
context does (ASVS 11.6.2). - The off-box audit record stays valid JSON after the log scrub in more cases, and the tee adds
no escaping before redaction. - Many escaped characters in engine log lines are spelled differently.
- The repository's locks pin certifi 2026.10.19, past
GHSA-6q48-6c7w-9gw2. - BREAKING --
messagefoundry checknow fails settings the retention start gate refuses. - The Email destination refuses a body it cannot encode without naming any of it.
- The route-gate walk and the refusal count read the
RouteContextshape of FastAPI 0.143.1. - The engine API turns fastapi's built-in OpenTelemetry telemetry off.
- The federated bind, rebind and unbind audit rows say whether the account holds engine
factors. - The send-time proxy credential rule reads a header name however it is spelled.
- A purge on SQLite now overwrites the old bytes, and the WAL is truncated hourly, by default.
- No upload audit row carries the file name.
docs/PHI.mdnow says what a second length cut does to an earlier truncation note, and states
the custom-delimiter residual an operator could not read before.- The RemoteFile destination refuses a payload it cannot encode without naming any of it.
- Under
internal_error = stop, an unencodable RemoteFile payload no longer stops the lane. - BREAKING --
messagefoundry checkandsupervisenow refuse a connection's own keep-forever
retention override thatserverefuses. - A redacted
UnicodeErrorno longer prints a false position, and names a codec by a tighter
rule. - The Email destination refuses, at load, a subject whose encoded word decodes to a control
character. - A store-key rotation no longer resets the age of every other tracked secret.
These notes show each entry's title only, to fit GitHub's limit on a release body. The full section is in CHANGELOG.md.