Skip to content

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 01 Oct 14:40
· 762 commits to main since this release
27d6dcb

[0.5.0] — 2026-10-01 — Early Access

Added

  • The messagefoundry-toolkit wheel 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 generate now gives every ORM^O01 an order detail.

Removed

  • python-hl7 is no longer a dependency.

Fixed

  • A DATABASE connection's connect_timeout now 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_connections and max_connections_per_host before the TLS handshake, not after it.
  • BREAKING: an alert rule's control_action now needs a connection-scoped event_type.
  • BREAKING: a FHIR update or if-match no 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 needs fhir-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_match alert event type and the content_label rule filter are gone.

Added

  • BREAKING: adr-analyze moved to a separate messagefoundry-toolkit command, 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/login can 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 enforce refuses to start without verified-TLS log forwarding.
  • Administrators can now see which accounts are locked.
  • messagefoundry check-privileges reads the store principal's privileges and changes
    nothing.
  • An over-granted or unreadable store principal now raises a store_privilege_warning alert 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_tail inflates 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_at tells a client when an admin-issued temporary password stops working.
  • messagefoundry admin-set-notify-email sets 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-schema does 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 import now 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/status says 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_enabled on, a config that sets oidc_acr_values while
    oidc_required_acr_values names 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 -- PUT and DELETE /users/{user_id}/federated-identity now require the pair the
    caller saw.
  • BREAKING: [api].tls_client_cert_identities is now keyed by the issuing CA.
  • BREAKING: each [api].tls_client_cert_identities value 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_due takes class_id= where it took secret=.
  • 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_up now defaults to true.
  • BREAKING: [api].trusted_proxies now needs [api].tls_terminated_upstream or 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_explicit is removed and refused at load, in the file and as
    MEFOR_API_SERVE_UI_EXPLICIT.
  • BREAKING: [pipeline].require_rcsi_for_pooled is removed and refused at load, in the file and
    as MEFOR_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 no Host or with two.
  • BREAKING: serve now refuses to start when the credential reminders have no [alerts]
    recipient.
  • BREAKING — length_of_stay needs 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_hours and the
    bootstrap_admin_expiring alert 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/stepdown now 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-admin before the
    first start.
  • Changelog entries are now fragment files, assembled at release.

Fixed

  • provision-admin now refuses before it prompts on a fresh install, and resolves directory
    secrets the way serve does.
  • audit-anchor --json now reports a bad service settings file as JSON on stdout.
  • GET /dead-letters now 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-verify that 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 .log or .txt file on a local drive, and tray.log is
    scrubbed.
  • python -m tee anonymize-captures now 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_provider does 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 serve warning 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_lookup now 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_lookup that cannot reach its database now raises DbLookupError, 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
    on userAccountControl.
  • The Vault and OpenBao clients now read every reply through the strict, bounded reader.
  • The OAuth2 client-credentials token provider now keeps its DeliveryError contract, 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 --json now reports a missing audit database, or one with no audit_log table,
    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 ends PROCESSED, not ERROR.
  • A failed SMART token mint in fhir_lookup now raises FhirLookupError, 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 /connections row for an outbound with no traffic edge now reports 0, not null, when
    it measures zero.
  • A file destination now logs a WARNING when it cannot remove its .part temp file.
  • An MLLP listener now answers a store outage at intake with a NAK before it closes the
    connection.
  • db_lookup now refuses a result larger than the lookup's max_rows, and stops reading at the
    ceiling.
  • On Windows, the service account and the operator who runs provision-admin can 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 /users answers 409 username already exists when 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 random module.
  • serve now 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-admin now 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_app and create_managed_app no longer trust the request Host
    unless told the bind is loopback.
  • Behind a trusted proxy on a loopback bind, the /ui origin checks no longer trust the forwarded
    Host.
  • A set [api].trusted_proxies on a loopback bind now counts as an exposed console.
  • BREAKING: a tls_ciphers string 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 since BACKLOG #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_decompress now 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/negotiate no 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 most lockout_threshold of them.
  • BREAKING: the Http() inbound listener now refuses any Transfer-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_transit cipher refuse an aes128-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_token an 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_decompress now 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 403 for a session that must change its password is no longer always the exact
    string password change required.
  • BREAKING — /ws/stats refusals now carry an HTTP status that says why.
  • BREAKING — EngineClient() and the Windows tray now default to https://127.0.0.1:8765, and
    the tray pins the certificate a stock engine mints.
  • BREAKING — messagefoundry dryrun and messagefoundry check now 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_upstream without [api].tls_cert_file now 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/sessions routes.
  • 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 dcmread inflates, and fails closed.
  • BREAKING — every command now refuses to start a keyless audit chain, not only serve and
    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.