Repository navigation
v0.5.0
[0.5.0] — 2026-10-01 — Early Access
Added
- The
messagefoundry-toolkitwheel is now signed, attested and licensed like the engine wheel. - Both engine SBOMs now list the vendored defusedxml.
- A Windows-resolved engine SBOM ships with each release.
Changed
- A new default limit on the MLLP listener:
max_inflight_frames(32). messagefoundry generatenow gives every ORM^O01 an order detail.
Removed
- python-hl7 is no longer a dependency.
Fixed
- A DATABASE connection's
connect_timeoutnow bounds the SQL Server login. - The DICOM server (SCP) now answers a status that tells the sender whether to re-send.
- The DICOM server (SCP) now refuses a small deflated object that inflates past 16 MiB.
- A generated OBR now repeats its order's placer and filler numbers.
- AD sign-in no longer fails on Linux over the receive timeout.
Security
- On the stdlib event loop, an MLLP TLS listener now applies
source_ip_allowlist,
max_connectionsandmax_connections_per_hostbefore the TLS handshake, not after it. - BREAKING: an alert rule's
control_actionnow needs a connection-scopedevent_type. - BREAKING: a FHIR
updateorif-matchno longer puts the resource id in the request URL. - A connection event's or an alert's reason is masked until a per-item reveal
- A connection's start-failure reason is masked until the operator reveals it
- Each gated response model now gets a serializer over its own gated properties only.
- The AD hop follows no LDAP referral.
Changed
- The
[fhir]extra now needsfhir-core>=1.1.11, and annotated-types is no longer capped.
Fixed
- Strict validation no longer rejects an order message for carrying one order detail.
Removed
- BREAKING: the
content_matchalert event type and thecontent_labelrule filter are gone.
Added
- BREAKING:
adr-analyzemoved to a separatemessagefoundry-toolkitcommand, and out of the
engine wheel. - Turning the sign-in limiter or the account lockout off is now warned, not silent.
- A sign-in rate limit, lockout setting, PHI-read or admin-write limit, sign-in or admin-write
time floor, session cap or OIDC flow-cache cap looser than its shipped default is now a named
security loosening, not only an off value. - Under the shipped
[security].require_mfa, no local account can be locked by a stranger
before its holder has a way past the lock. - A site can add its own context words to the password screen.
- Six connection factories take a per-connection
tls_ca_file. - The anonymizer now scrubs eight event, visit, order and observation date fields, the county
and the patient location. - The anonymizer can refuse any field nobody decided.
- Secret classes other than the store DEK can now refuse to start on calendar expiry, if you opt
them in. POST /auth/logincan end the token it replaces.- The off-box log forwarder keeps what the collector does not take in a bounded on-disk spool.
- A PHI instance under
enforcerefuses to start without verified-TLS log forwarding. - Administrators can now see which accounts are locked.
messagefoundry check-privilegesreads the store principal's privileges and changes
nothing.- An over-granted or unreadable store principal now raises a
store_privilege_warningalert at
start. - A sign-in from an address the account has not used recently is now challenged and reported.
- A session signed in through the identity provider now steps up there, not with a password.
deflate_decompress_with_tailinflates a zlib stream that has other data after it.- The admin API and the web console now show who last wrote each channel scope.
- An operator can now record what an interrupted dual-control release did.
- A dual-control approve refused as too early now raises an alert and says when to retry.
- An administrator can create a directory (AD) account without a Windows SSO sign-in.
- A DAST pass now sends hostile bytes to live MLLP, raw-TCP and X12 listeners and checks the
engine's ingress rules. - Dual control now flags a release whose approver account is new or was just taken over, and an
Administrator grant pages. credential_expires_attells a client when an admin-issued temporary password stops working.messagefoundry admin-set-notify-emailsets a missing notification address on an enabled
Administrator, from the host.- On SQL Server and PostgreSQL the engine's runtime login no longer runs schema DDL, and
messagefoundry store provision-schemadoes it instead. - A row stranded in flight now pages.
Changed
- BREAKING: on SQL Server and PostgreSQL, the runtime login may only insert and read audit rows.
- BREAKING:
cert importnow judges a PKCS#12 MAC even when the bundle's bags are not
encrypted. - BREAKING: the
Http()listener answers 400 to a request that repeats its credential header. GET /cluster/statussays whether a stepdown on this node would send its lease release.- The DR backup no longer stages plaintext in the OS temp dir.
- BREAKING: every private-key loader now refuses a weak passphrase wrap.
- The username-in-password screen no longer carries the ASVS 6.2.11 label.
- BREAKING: with
[auth].oidc_enabledon, a config that setsoidc_acr_valueswhile
oidc_required_acr_valuesnames no non-blank value now refuses to load. - The Vault clients now narrow and verify the TLS leg to an
https://proxy, and refuse an
http://Vault behind one. - BREAKING: the Vault clients refuse a Vault address that is not
https://. - The tray's engine probe no longer goes through a web proxy.
- BREAKING --
PUTandDELETE /users/{user_id}/federated-identitynow require the pair the
caller saw. - BREAKING:
[api].tls_client_cert_identitiesis now keyed by the issuing CA. - BREAKING: each
[api].tls_client_cert_identitiesvalue is now the account's id, not its
username. - BREAKING: the PostgreSQL store now builds its own TLS context on the default path, so it narrows
the suites and can load a CRL there. AlertSink.secret_rotation_duetakesclass_id=where it tooksecret=.- BREAKING: an over-granted store login now refuses to start under
enforce. - Saving over a directory channel scope through the JSON API now needs explicit intent.
- The IDE's Start now provisions an administrator before it starts the engine.
- The SMART and OAuth2 client-credentials token providers now share one token hop and one
token cache. [auth].admin_new_ip_step_upnow defaults totrue.- BREAKING:
[api].trusted_proxiesnow needs[api].tls_terminated_upstreamor an operator
[api].tls_cert_file, and is refused at load without one. - BREAKING: scripted bulk admin writes now get
429. - BREAKING: the Windows config-source guard now refuses to load when it cannot finish reading an
ACL. - BREAKING:
[api].serve_ui_explicitis removed and refused at load, in the file and as
MEFOR_API_SERVE_UI_EXPLICIT. - BREAKING:
[pipeline].require_rcsi_for_pooledis removed and refused at load, in the file and
asMEFOR_PIPELINE_REQUIRE_RCSI_FOR_POOLED, at either value. - BREAKING: an administrator must give a notification address to create an account.
- BREAKING — the
Http()inbound listener answers 400 to a request with noHostor with two. - BREAKING:
servenow refuses to start when the credential reminders have no[alerts]
recipient. - BREAKING —
length_of_stayneeds a zone for admit and discharge times that carry no offset. - BREAKING — the engine no longer creates an account on its own.
- BREAKING —
[auth].bootstrap_expiry_hours,[auth].bootstrap_warn_hoursand the
bootstrap_admin_expiringalert event are gone. - BREAKING — the config loader refuses a connection name that does not match
^[A-Za-z][A-Za-z0-9_-]{0,255}$. POST /cluster/stepdownnow drains a node that has already self-fenced.- Documented: leader preference does not steer a planned failover.
- BREAKING — the
Http()inbound listener answers 422 to a body it refuses at ingress. - The install instructions now create the first Administrator with
provision-adminbefore the
first start. - Changelog entries are now fragment files, assembled at release.
Fixed
provision-adminnow refuses before it prompts on a fresh install, and resolves directory
secrets the wayservedoes.audit-anchor --jsonnow reports a bad service settings file as JSON on stdout.GET /dead-lettersnow says which channels a replay would act on, not only which rows fit on
the page.- A scheduled connection stopped by a pooled infra fault now stays stopped across its window.
- The scheduler no longer starts a connection the DR run-profile parked.
- A log-write halt no longer makes the scheduler restart and re-page every tick.
- A credential fault on a batching outbound now stops the lane and keeps the batch.
- A reload now keeps each connection's schedule in step with the new config.
- An FTP server that plainly says it is busy no longer stops an outbound lane.
- The SFTP and FTP source now waits for a file to stop growing before it reads it.
- An SFTP upload to a server that stops reading is now cut off, and a few SFTP errors that escaped
raw are now classified. - A DR backup or standalone
restore-verifythat will not fit now fails before it writes. - A trailing-slash path is now a 404, never a redirect to an
http://URL. - A store created by 0.3.2 now keeps its saved searches on upgrade, and user deletion works on it.
- A pooled stage no longer gains a processing slot when a claimer dies mid-bookkeeping.
- A lane that keeps killing its pooled claimer is now stopped, so the rest of the stage drains.
- HTTP and web proxy Digest auth now answer only SHA-256, and proxy Digest works.
- The message for a group- or world-writable trust anchor now names its own fix.
- The web console explains an uploaded-file action the engine refused, throttled or could not
serve. - Replacing a CRL file no longer clears its expiry alert while a running hop holds the old copy.
- A temporary password can no longer be rotated after its deadline.
- The test harness Monitor states when a temporary password stops working.
- The tray's View Log now opens only a
.logor.txtfile on a local drive, andtray.logis
scrubbed. python -m tee anonymize-capturesnow logs what the leak-check did not look at.- A sign-in that still owes its second factor can no longer evict a fully signed-in session.
- The session list hides, and the hourly reaper deletes, sessions past the idle timeout.
- The AD session reconciler now ends a session whose directory scope was withdrawn or narrowed.
- A config reload whose audit row fails now reports the swap it made, instead of a 500.
- Stopping upload retention waits for its audit rows, and engine shards share the upload quota.
- A store key that the pinned
[store].key_providerdoes not read no longer counts as a key. - Confirming TOTP enrolment no longer turns MFA on when its recovery codes are lost.
- A passkey sign-in refused for its stored key is now audited with its reason.
- The
servewarning for a public web console address with no declared proxy posture no longer
says the session cookie ships without Secure. - Operator text no longer says the OIDC username claim picks the on-prem account.
fhir_lookupnow reports a refused hop as a lookup error, re-checks the hop on its probe, and
refuses to pile up reads behind one that timed out.- A refused egress reply now names the connection, not its URL.
- A
db_lookupthat cannot reach its database now raisesDbLookupError, not a raw driver
error. - A refused channel-scope save in the web console now keeps the administrator's edits.
- A message with a blank line between segments is now accepted and recorded, not dropped.
- The AD session reconciler no longer signs out a small estate when its bind account loses read
onuserAccountControl. - The Vault and OpenBao clients now read every reply through the strict, bounded reader.
- The OAuth2 client-credentials token provider now keeps its
DeliveryErrorcontract, and caches
a token for at most one hour. - A FHIR or REST destination now retries a malformed reply from its partner instead of treating
it as an internal error. - A Loopback re-ingress now holds a non-HL7 reply to the 16 MiB engine ingress ceiling.
- The OIDC revocation refusal now stops startup before any connection starts, and
verify
reports it. - A DR backup no longer holds the store write lock while it copies the store.
- A restore-verify no longer leaves the decrypted store in the OS temp directory when its cleanup
is refused. - A failed federated sign-in now answers at the same fixed deadline as the other sign-in
paths. - A bad authorization code no longer hides the federated sign-in link.
- The Python engine client now ends the session a new sign-in replaces.
audit-anchor --jsonnow reports a missing audit database, or one with noaudit_logtable,
as JSON on stdout.Direct(...)now fails at construction on faults that used to fail every send.- A
Direct(...)message that cannot be built now dead-letters at once, with no body on the
error. - The shared redactor now scrubs FHIR JSON, DICOM tag dumps and XML, not only HL7.
- The shared redactor closes three more leaks.
- A dual-control release can no longer run without an audit row, or be recorded as failed after
it ran. - A dual-control release now records whether its operation finished, failed or was cut off.
- In the default pooled claim mode, a stage whose claimer task dies now recovers instead of
stopping. - Replay no longer re-queues a pass-through completion marker, so a replayed message that
delivered endsPROCESSED, notERROR. - A failed SMART token mint in
fhir_lookupnow raisesFhirLookupError, not a raw
DeliveryError. - A malformed reply to a SOAP or DICOMweb destination, or to the AI broker, is now a classified
failure. - A
GET /connectionsrow for an outbound with no traffic edge now reports0, notnull, when
it measures zero. - A file destination now logs a WARNING when it cannot remove its
.parttemp file. - An MLLP listener now answers a store outage at intake with a NAK before it closes the
connection. db_lookupnow refuses a result larger than the lookup'smax_rows, and stops reading at the
ceiling.- On Windows, the service account and the operator who runs
provision-admincan now each open
the SQLite store, in either order. - The DICOM C-STORE SCP no longer answers Success for an object the engine does not accept.
POST /usersanswers409 username already existswhen two creates race for one name.- On SQLite and PostgreSQL, adding a passkey to an account deleted mid-enrolment now says
no such user. - A missing or blank CRL path is now refused at load, naming the setting the operator wrote.
- A CRL file is now judged on every CRL it holds when a TLS context is built.
- An SFTP server that is slow to connect is now retried, not dead-lettered or treated as a bad
credential. - The FHIR parsers and the OIDC token exchange no longer put their input on the exception
chain. - At least 29 more refusals no longer chain the input they withhold, and the HL7 parse refusal
names no parser text.
Added
- The reset notice now states when a temporary password stops working, and the operator gets a
reminder before it lapses.
Security
- LDAPS to Active Directory would no longer fail closed on Python 3.15, and it narrows TLS 1.3.
- On Python 3.15 the engine stops offering SHA-224 TLS signature schemes.
- A refused combined sign-in no longer names which factor was wrong in its
auth.login_failed
reason. - The Auditor no longer sees lockout events.
- BREAKING: XML signature checks now refuse an RSA signing key under 2048 bits.
- The Python security scan now flags new calls into the
randommodule. servenow refuses to start when uvicorn lacks a hook the protocol header floor needs.- BREAKING: a directory account with no directory id no longer signs in or steps up by its
username. - BREAKING: a TOTP or recovery code no longer renews the step-up window of a directory account
with no directory id. - An expiring temporary password now reminds its holder and the administrator who issued it.
- A directory account is now told when the directory renames it.
provision-adminnow tells the holder of an account it takes over.- A caller who knows only a username can no longer keep a TOTP-enrolled local owner out through the
account lock. - A passkey ceremony behind a trusted proxy no longer takes its rp_id from the forwarded Host.
- BREAKING (embedders only):
create_appandcreate_managed_appno longer trust the request Host
unless told the bind is loopback. - Behind a trusted proxy on a loopback bind, the
/uiorigin checks no longer trust the forwarded
Host. - A set
[api].trusted_proxieson a loopback bind now counts as an exposed console. - BREAKING: a
tls_ciphersstring that carries an OpenSSL@directive is now refused. - BREAKING: the OAuth2 client-credentials token endpoint now takes the revocation guard the SMART
token endpoint has had sinceBACKLOG #1498. - BREAKING: a federated link on an account with no directory id no longer signs anyone in.
- A connection can now attest its hop secure, and the attestation is reported.
- BREAKING:
zip_decompressnow refuses any bytes before or after the archive. - Startup attestation now checks the web console, not just the engine.
- BREAKING: a CRL file can no longer add trust anchors.
- BREAKING: under
enforce, a trust anchor whose permissions or path the engine cannot read now
refuses to start, unless its SHA-256 pin matches. - BREAKING: the mTLS CA of every inbound MLLP, HTTP and DICOM connection now gets the same
checks as the auth anchors. - A config reload now refuses a trust anchor that the next start would refuse.
- BREAKING: a Windows SSO (Kerberos) sign-in through
POST /auth/negotiateno longer opens a
step-up window. - BREAKING: the OIDC and API client-CA trust anchors now load the bytes their check read, not a
second read of the file. - BREAKING: a federated (OIDC) sign-in no longer links itself to an account. An administrator
links it first, through the API. - The web console can now link, relink and unlink a federated (OIDC) identity.
- BREAKING: a federated (OIDC) identity now links only to a directory account that carries its
immutable directory id. - BREAKING: an administrator's save no longer moves the notification address as a side effect.
- BREAKING: an account with no notification address must set one at sign-in, whenever this
instance sends security notices. - Removing a passkey now always sends a notice.
- A dropped security notice is now logged.
- The web console's notification-address page now suggests the address already on the account.
- An approval can no longer be granted faster than a person could read it.
- A failed step-up re-auth or password change now counts toward the account lockout, and each
session gets at mostlockout_thresholdof them. - BREAKING: the
Http()inbound listener now refuses anyTransfer-Encoding, not only
chunked. - BREAKING: the
Http()inbound listener reads request framing more strictly. - BREAKING — every TLS context the engine builds now defaults to the approved AEAD TLS 1.2
suites, MLLP and DICOM included, and the engine's own signing key must be RSA-3072 or larger. - BREAKING: the AD LDAPS bind and the three Vault clients now offer only the approved AEAD TLS
1.2 suites too. - The Windows tray's health probe now offers only the approved AEAD TLS 1.2 suites.
- BREAKING: the default TLS suites no longer include AES-128.
- BREAKING: a trust anchor that another account can replace through its folder now refuses to
start. - BREAKING: an HTTP-family reply is now refused when its header block or its chunked body breaks
the HTTP/1.1 grammar. - BREAKING: SFTP now offers one cipher,
aes256-gcm@openssh.com. - BREAKING: the Vault key provider and the
vault_transitcipher refuse anaes128-gcm96
Transit key. - BREAKING: an HTTP-family reply whose status line or header block holds a bare CR is now refused
as its head is read. - A reply body with no length, or a length the peer never sends, no longer costs the whole byte
bound in memory.
Fixed
- A token endpoint that returns an
access_tokenan HTTP header cannot carry now fails that
mint, instead of dead-lettering every message for up to an hour. - The startup ERROR for an unusable bundled breach corpus now says what that corpus refuses.
- BREAKING — an HTTP-family reply with ambiguous length framing now fails before its body is
read. - BREAKING — an AD or OIDC sign-in that matches no scope-mapped group now withdraws the user's
channel scope, unless an administrator set it. - BREAKING:
deflate_decompressnow refuses any bytes after the end of the stream, and no longer
hangs on them. - The per-connection revocation attestation can now be set, in both directions (ADR 0173).
- The per-connection revocation attestation is now reported, like
cleartext_accepted(ADR 0173). - BREAKING — sign-in now checks a stored passkey with the same rule as registration.
- BREAKING: the Windows trust-anchor ACL check no longer reads what it cannot parse as
owner-only, and it knows more broad principals.
Changed
- BREAKING — the web console engine UI seam moved, so this engine no longer pairs with web
console 0.3.0. - BREAKING — the
403for a session that must change its password is no longer always the exact
stringpassword change required. - BREAKING —
/ws/statsrefusals now carry an HTTP status that says why. - BREAKING —
EngineClient()and the Windows tray now default tohttps://127.0.0.1:8765, and
the tray pins the certificate a stock engine mints. - BREAKING —
messagefoundry dryrunandmessagefoundry checknow refuse an oversized fixture
file. - The test harness's MLLP receivers, the IDE's Steps view sample, and
check's.expect
sidecars are now capped (ASVS 5.1.1). - BREAKING —
[api].tls_terminated_upstreamwithout[api].tls_cert_filenow requires
[api].plaintext_upstream_hop_acknowledged = true. - BREAKING — the password-policy refusal for a context word now names the list it checks.
- An opt-in refusal of backend hops on an unchanging credential, and an inventory of every such
hop. - BREAKING — an operator resend now meets the target inbound's ingress guards.
- BREAKING — a resend into a strictly validated inbound now meets its strict validation.
- BREAKING — a session that has not proved its second factor can no longer change the password of
an account that has one. - A new sign-in now ends the session it replaces.
- A console release now refuses a wheel whose engine requirement has no floor.
Security
- BREAKING — OIDC sign-in now bounds how old the IdP's authentication may be.
- BREAKING — the OIDC token endpoint and JWKS legs now carry the posture-keyed revocation
guard. - BREAKING — the
Direct()S/MIME envelope now encrypts its content with AES-256-CBC. - BREAKING — the inbound
Http()listener now settles a request's framing before it reads the
body, and refuses framing it would have to guess at. - BREAKING — an MFA-pending session on an account that has a second factor can no longer end
sessions through the/me/sessionsroutes. - BREAKING — a keyed store now refuses an unmarked value in an encrypted column, where 0.4.0
read it back as plaintext. - BREAKING: a keyed store now refuses a plaintext uploaded file on read until an operator runs
rotate-key, which seals it. - A lockout, and a sign-in that succeeds after failures, now write their own audit rows, so they
reach the user's security-events feed. - Refused WebSocket handshakes now carry the baseline security headers, and some responses
uvicorn writes on its own gain two of them. - Passkey registration now requires real CBOR integers where the COSE key needs them.
- On Windows, the engine now lets local users read the API certificate it mints, and never the
key. - The DICOM deflate guard now bounds exactly the bytes
dcmreadinflates, and fails closed. - BREAKING — every command now refuses to start a keyless audit chain, not only
serveand
provision-admin.
These notes show each entry's title only, to fit GitHub's limit on a release body. The full section is in CHANGELOG.md.