Skip to content

Release Notes

MazeVault Platform Version History

Document Version: 1.0.61 Last Updated: 2026-11-13


Version 1.0.61

Release Date: Unreleased

Distribution Changes (action required)

  • Agent requires an https:// backend URL (check before upgrading agents) — An agent whose server_url (MAZEVAULT_SERVER_URL) uses http:// now stops at start with a configuration error unless the host is localhost or a loopback address: tokens, private keys and secrets travel over this connection. Agents with a remote http:// URL must be switched to the https:// address of the backend or gateway before they can update: an agent of this version or later refuses a self-update while its own URL would fail this rule and reports the refusal to the backend (update_error in its health status), but an agent older than this release still installs the update and then does not start, after which it can no longer be reached remotely.
  • API token scopes are enforced; CIDR-restricted roles apply (check scoped tokens before upgrading) — An API token (mvt_…) created with scopes now acts only within them on every permission-checked endpoint (before, scopes were checked for no token at all): a CI token with ["secret:read"] can no longer write or delete, and a scoped token can create further tokens only within its own scopes. A token without scopes keeps all of its owner's rights; the web interface, the Go SDK and the Terraform provider create tokens without scopes unless you pass them. Before upgrading, check the scopes of tokens used by the CLI, SDK, Terraform provider or the Kubernetes operator: SELECT name, scopes FROM api_tokens WHERE is_active AND deleted_at IS NULL AND scopes IS NOT NULL AND scopes <> '{}';. The client IP now reaches the permission check, so roles with a CIDR restriction apply to clients inside the range (they never applied before) and an ip_whitelist condition on an environment permission is evaluated. Behind a reverse proxy, set TRUSTED_PROXY_CIDRS to the proxy addresses: only from them is X-Forwarded-For used (the first address from the right that is not a trusted proxy); a request with X-Forwarded-For or X-Real-IP from any other peer gets no IP and CIDR-restricted roles do not apply to it. The same variable also enables the X-Client-Cert / ssl-client-cert header path of mTLS client certificates, so the proxy must strip those headers from client requests. A token created with an API token expires no later than that token, and a non-positive duration is rejected with 400. An environment permission with mfa_required still never applies, because sign-in tokens do not carry the MFA state yet; one with time_range is re-evaluated on every request.
  • Permissions come only from role assignments (action required) — The role in a user's sign-in token (users.role) no longer grants anything: a user has exactly the permissions of their role assignments (global, organization or project). Before, a user without any role assignment still got the permissions of users.role everywhere, and a user whose scoped assignment had been removed (or whose project or organization had been deleted) kept them until a re-sync. Users who had access only through users.role lose it after the upgrade, and SSO and LDAP users created without a matching group mapping get no permissions until a role is mapped or assigned. Before upgrading, list the affected users with SELECT u.id, u.email, u.role FROM users u WHERE u.deleted_at IS NULL AND NOT EXISTS (SELECT 1 FROM user_roles ur WHERE ur.user_id = u.id); and give each one a role in Access Control → Manage roles (or POST /api/v1/rbac/users/{id}/roles with role_id and an optional scope). The built-in admin accounts get their admin assignment automatically at startup. Agents, OAuth clients and service identities are not affected.
  • Agent version policy replaces the agent version environment variables — Which agent version each agent is offered is now set by the version policy (Deployment → Agents → Version policy). AGENT_VERSION, AGENT_MAX_ALLOWED_VERSION and AGENT_ROLLOUT_PERCENTAGE are deprecated: they seed an empty policy once on the primary's first start (later changes to them only log a deprecation warning). Pinned values become targets and the ceiling, so agents running a higher version are offered a downgrade to them — clear the targets/ceiling in the policy or set a minimum rollback version if that is not wanted. Gateways follow the primary's policy. The new agent rollback floor min_rollback_version limits how far an agent may be rolled back; it can be cleared with reset-min-rollback-version.
  • Images, Helm chart and Compose kit move to the MazeVault Harbor — Starting with this release, MazeVault is distributed only from registry.mazevault.com: images registry.mazevault.com/mazevault/mazevault-{backend,frontend,docs,ocsp,init-certs}:vX.Y.Z, the Helm chart oci://registry.mazevault.com/mazevault/mazevault-customer (version X.Y.Z), and the Compose kit (installer, upgrade/rollback scripts, docker-compose.yml, .env.example, SHA256SUMS) as OCI artifact registry.mazevault.com/mazevault/compose-kit:vX.Y.Z, downloaded with oras pull (run its scripts with bash, ORAS does not keep the executable bit). Each customer receives a pull-only robot account; there is no GitHub token and no raw.githubusercontent.com download any more. v1.0.60 and older remain on GHCR (ghcr.io/mazevault); existing installations keep their IMAGE_REGISTRY until it is changed explicitly (install-mazevault.sh --registry, or the chart's image.repository values).
  • The online installer uses the kit's docker-compose.yml — install-mazevault.sh no longer writes its own embedded copy of the compose file; it installs the docker-compose.yml shipped next to it in the Compose kit (as install-mazevault-offline.sh already did) and stops with an error when it is missing, so run it from the extracted kit. Online installations re-run with this release therefore pick up the kit's settings, e.g. MAZEVAULT_ENV_CANONICAL_ENFORCE (default false); MAZEVAULT_CUSTOMER_NAME, MAZEVAULT_CUSTOMER_EMAIL and MAZEVAULT_COMPANY_ID must be non-empty in .env (the installer always generated them). The kit's compose file gains the backend health check the installer's copy had, and the frontend starts once the backend is healthy. New switches for automation: MAZEVAULT_NO_SYSTEMD=1 skips the systemd service and all systemctl calls, and MAZEVAULT_COMPOSE_PROJECT overrides the Compose project name (default mazevault). A newly generated systemd unit now passes the project name (-p) to up and down; the unit is generated only when it does not exist, so an existing /etc/systemd/system/mazevault.service keeps its old command (delete it and re-run the installer to regenerate it).
  • Generated gateway stacks pull the backend from MAZEVAULT_IMAGE_REGISTRY (default the MazeVault Harbor) at the primary's own version, and require MAZEVAULT_GATEWAY_ENVIRONMENT when installed.
  • Customer Helm chart — External Secrets resources use external-secrets.io/v1 (External Secrets Operator 0.16 or newer is required); the backend runs plain HTTP behind the ingress (MAZEVAULT_TLS_ENABLED=false); the Redis access key is URL-encoded in REDIS_URL. The docs host moves to its own Ingress <release>-docs (same certificate), and the new global.ingress.docsAllowedSourceRanges limits it to the listed client CIDRs.
  • Agent writes only inside allowed directories (check before upgrading agents) — The agent now writes files the backend names (certificate_sync keystores, certificate_file_sync files, secret_sync targets, keytab_deploy keytabs, renewed certificates, file and JKS targets of the periodic sync) only inside cert_store_path and the new agent setting allowed_write_paths. Without the setting the defaults are /etc/ssl, /etc/pki, /etc/letsencrypt, /etc/nginx/ssl, /etc/nginx/certs, /etc/apache2/ssl, /etc/httpd/ssl, /etc/haproxy/certs, /etc/krb5.keytab, /var/lib/mazevault/certs and /etc/mazevault/certs on Linux, C:\ProgramData\MazeVault\certs on Windows. Paths must be absolute (a relative secret_sync target_path is now refused) and must not contain ..; symlinks are resolved before the check; the agent's own configuration, identity and state are never writable this way. Before upgrading an agent, add to allowed_write_paths in its agent.yaml every other directory its deployment targets, secret sync targets, keytab targets or renewed certificates use (for example /opt/tomcat/conf or C:\inetpub\app); a refused path fails the request with an error naming allowed_write_paths, except for the periodic certificate sync (file and JKS targets, install_target metadata), whose refusals appear only in the agent log (/var/log/mazevault/agent.log or the journal, C:\ProgramData\MazeVault\logs\agent.log on Windows) — search it for allowed_write_paths after the upgrade. A relative cert_store_path or allowed_write_paths entry now stops the agent at start with a configuration error. On Windows, junctions and mount points inside a write path are refused. keytab_renew ignores output_path and always generates the keytab into a private temporary file. A renewal operation_token is now accepted only as a plain name (letters, digits, -, _), and the certificate id the agent fetches after an enrollment is sent path-escaped. See Agent Deployment → Allowed write directories.
  • Agent deployment API — os_type: darwin and agent_mode: gateway|sidecar are rejected with HTTP 400; supported agents are Linux and Windows hosts.
  • Certificate authorities: the legacy external-CA configuration endpoints are removed — POST /api/v1/ca/{id}/external/configure, POST /api/v1/ca/{id}/external/test, POST /api/v1/ca/{id}/external/templates/sync and POST /api/v1/projects/{id}/ca/{caId}/external/configure no longer exist (a call now returns HTTP 404); external CAs are configured as organization CA accounts. Migration 000217 drops the external_ca_configs and external_ca_template_mappings tables, keeping any rows in mazevault_internal for its down migration.
  • Azure Key Vault integrations: the Default Credential Chain auth method is removed (check before upgrading) — The Auth Method step of an Azure Key Vault integration offers On-Behalf-Of, Managed Identity and Client Secret (interactive) and Client Secret and Managed Identity (background). The API, and so the Terraform resource mazevault_integration, answers 400 for default in interactive_auth_method, background_auth_method or config.auth_method on create, update and connection test. Migration 000211 sets client_secret in place of default on existing Azure Key Vault integrations and disables them with the last sync error Disabled by migration 000211: the Default Credential Chain auth method was replaced by Client Secret; choose Managed Identity or Client Secret and enable the integration; edit each one, choose Managed Identity or Client Secret (with the app registration's tenant ID, client ID and secret) and enable it again. List them before upgrading with SELECT id, project_id, environment, name FROM project_integrations WHERE provider IN ('azure', 'azure_keyvault', 'azure_key_vault') AND (interactive_auth_method = 'default' OR background_auth_method = 'default');. Its down migration restores the previous values from mazevault_internal.migration_000211_default_auth_method_backup. An Azure auth method the server does not know (for example a typo in config.auth_method) fails with unsupported Azure auth method, and an integration with no auth method and no client credentials fails with no Azure auth method configured; the server's own Azure identity is not used in their place. Rotation preflight now also blocks a rotation whose Key Vault propagation target or post-rotation Key Vault action points to a disabled integration. See Azure Identity & Permissions Configuration, section 9.2.
  • SSO: the OIDC / OAuth2 provider type is removed — Organization Settings → SSO no longer offers OIDC / OAuth2; it had no sign-in behind it. The API, and so the Terraform resource mazevault_identity_provider, accepts only entra_id and ldap as type on create and answers 400 for any other value, oidc included; the Terraform resource also refuses another type at terraform plan. Migration 000212 deletes existing oidc identity providers the way the API does (soft delete); its down migration restores them from mazevault_internal.migration_000212_oidc_identity_provider_backup. Sign in through Microsoft Entra ID (which uses OpenID Connect) or LDAP.
  • SSO: SAML 2.0 sign-in is removed — Organization Settings → SSO no longer offers SAML 2.0, and the endpoints GET /api/v1/auth/saml/metadata, GET /api/v1/auth/saml/login and POST /api/v1/auth/saml/callback no longer exist. The API, and so the Terraform resource mazevault_identity_provider, answers 400 for type: saml on create. Migration 000213 deletes existing saml identity providers the way the API does (soft delete); its down migration restores them from mazevault_internal.migration_000213_saml_identity_provider_backup. Users who signed in through SAML keep their accounts and sign in through Microsoft Entra ID or LDAP with the same email address.
  • SSO: GitHub sign-in is removed — The endpoints GET /api/v1/auth/sso/github/login and GET /api/v1/auth/sso/github/callback no longer exist, Organization Settings → SSO no longer offers GitHub, and the backend no longer reads GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET and GITHUB_REDIRECT_URI; remove them from your environment or secret store. The API, and so the Terraform resource mazevault_identity_provider, answers 400 for type: github on create. Migration 000214 deletes existing github identity providers the way the API does (soft delete), moves the users' GitHub identities into a backup and deletes pending GitHub sign-in states; its down migration restores the providers and identities from mazevault_internal. Users who signed in through GitHub keep their accounts and sign in through Microsoft Entra ID or LDAP with the same email address. GitHub secret sync integrations are not affected.
  • SSO: GitLab sign-in is removed — The endpoints GET /api/v1/auth/sso/gitlab/login and GET /api/v1/auth/sso/gitlab/callback no longer exist, Organization Settings → SSO no longer offers GitLab, and the backend no longer reads GITLAB_CLIENT_ID, GITLAB_CLIENT_SECRET, GITLAB_REDIRECT_URI and GITLAB_BASE_URL; remove them from your environment or secret store. The API, and so the Terraform resource mazevault_identity_provider, answers 400 for type: gitlab on create; SSO is configured with entra_id or ldap. Migration 000215 deletes existing gitlab identity providers the way the API does (soft delete), moves the users' GitLab identities into a backup, deletes pending GitLab sign-in states and drops the unused columns github_org, github_team_restrictions and gitlab_group_restrictions of identity_providers after copying their values into a backup; its down migration restores all of it from mazevault_internal. Migration 000216 then drops the table auth_identities, which held only the GitHub and GitLab sign-in links, after copying any remaining rows into mazevault_internal. Users who signed in through GitLab keep their accounts and sign in through Microsoft Entra ID or LDAP with the same email address. GitLab secret sync integrations are not affected.
  • Secret import endpoint removed — POST /api/v1/projects/{id}/secrets/import has been removed together with the preview External Migrations tab in Organization Settings. Secrets are created with POST /api/v1/secrets and updated with PUT /api/v1/secrets/{id}; export (GET /api/v1/projects/{id}/secrets/export) is unchanged.
  • SCEP enrolment requires a challenge password (security fix, check before upgrading) — PKIOperation enrolment at /api/v1/scep/<ca_id> used to sign every CSR with the internal CA without checking anything, so anyone who could reach the endpoint obtained a client certificate of your CA. It is now authorised by the CSR's challengePassword, which must match the new backend variable SCEP_CHALLENGE_PASSWORD (one value for all internal CAs; in Helm global.keyVault.secretKeys.scepChallengePassword). The variable is not set after an upgrade, so every SCEP enrolment is refused (signed CertRep FAILURE / badRequest, no certificate; the backend logs SCEP enrolment disabled at start and a warning per refused request). If you use SCEP (e.g. Intune, Jamf, sscep), set SCEP_CHALLENGE_PASSWORD to a strong random value and configure the same challenge in the SCEP clients/profiles; GetCACaps and GetCACert are unchanged. Setting the variable enables SCEP for every organization's internal CA, and anyone holding the value can enrol at any of them: in a multi-organization deployment set it only if all organizations trust each other, otherwise leave SCEP off. Certificates issued through SCEP earlier stay valid; review them and revoke any you do not recognise.
  • Terraform: changes the API cannot apply now plan a replacement or fail the plan (review terraform plan before applying) — Several Terraform attributes could be changed in place although the backend cannot change them: the apply either failed ("Provider produced inconsistent result", "Update not supported") or reported success and silently dropped the change. They now force replacement (destroy and create): mazevault_secret.project_id, key and environment; mazevault_api_token.name, scopes and duration; mazevault_shared_secret.ttl_hours and max_views; mazevault_group_mapping.group_name and role_id; every argument of mazevault_secret_link; project_id of mazevault_integration, mazevault_integration_group and mazevault_consistency_group; mazevault_hsm_device.provider_type. A replacement also replaces every resource that references the replaced one (for example the secret links of a replaced integration). Replacing a secret deletes its version history and writes the value from the configuration again: a secret with generate gets a new random value, and a value rotated on the server reverts to the configured one. Replacing an API token revokes the old token and issues a new value (consider lifecycle { create_before_destroy = true }), and a replaced share link is not revoked (shares cannot be deleted): it stays valid until it expires or is claimed, next to the new one. Two changes now fail at plan time instead, because a replacement would be destructive or cannot succeed: mazevault_project.organization_id (the API cannot move a project; a replacement would delete the project with every secret in it: create a new project and migrate instead), and display_name, description and permissions of mazevault_role while name stays (the API can neither change nor delete a role: change it in MazeVault, or give it a new name, which creates a new role). A role without description no longer shows a change after a refresh (also when an older provider version stored it as ""), a change of only the letter case of a project_id or role_id does not replace anything, a role adopted after "role already exists" must match the configuration, and role_id, organization_id and project_id read back in another letter case no longer show a change. mazevault_group_mapping now stores the planned values on update instead of the previous state. Keep environment of mazevault_secret non-empty: the backend stores an empty one as dev, and the next plan would replace the secret.
  • Certificates are always created in a project (API callers: check before upgrading) — Every API path that creates or imports a certificate now needs a project: POST /api/v1/certificates/import, /import/bulk and /import/file (including PFX) and a user's POST /api/v1/certificates/csr answer HTTP 400 project_id is required without project_id (the UI always sent one; adopting a discovered certificate and POST /api/v1/projects/{id}/csr already required it). Send project_id; in Terraform set project_id on mazevault_certificate, in the CLI pass mazevault certs request --project <id> (the flag replaces the required --agent). Agent mTLS enrolment, the agent's Kubernetes controller and gateway mTLS certificates are not affected. Certificates without a project left from older imports belong to no organization: only a global role sees and manages them, an organization or project role gets 403, and an import of the same certificate into a project is refused instead of taking it over. A global admin assigns such a certificate to a project with PUT /api/v1/certificates/{id} (project_id); from then on the project's organization manages it.
  • Multi-datacenter sync: the event payload format changes (upgrade every datacenter together) — Sync events between datacenters (POST /api/v1/sync/events, GET /api/v1/sync/events, GET /api/v1/sync/entities/{id}) now carry the entity data, including a secret's encrypted value, and acknowledgements (POST /api/v1/sync/ack) are stored. Before, events carried no data and every acknowledgement failed, so no secret, certificate or other entity was replicated and the same events were sent again forever. A replicated entity keeps its id on every datacenter, and a replicated update no longer clears a user's password hash or other fields the event does not carry. Run the same release on the primary and every gateway: a datacenter of an older release can neither send nor apply the new events. After the upgrade the primary sends the events it stored earlier as well (a datacenter skips events it has already received); a secret event stored before the upgrade lacks the value, so a secret missing on the receiving datacenter is not created from it and replicates with its next change. On an installation that synced before, each gateway has already stored the first batch (up to 100 events) without data and skips it now, so users, roles, organizations, projects and certificates in that batch do not replicate until they change: after upgrading every datacenter, save these entities again on the primary (a reconciliation does not help, both sides hold the same event checksums). A secret that reconciliation fetches from a primary of an older release, without its value, is stored as names_only. All datacenters must use the same encryption key (MAZEVAULT_MASTER_KEY), as before. API responses never contain the encrypted value, and the sync dashboard (/api/v1/sync-dashboard/events) lists events without their data. The Redis secret cache now holds the encrypted value of cached secrets (ciphertext under MAZEVAULT_MASTER_KEY, never the plaintext).
  • Go SDK: set Enabled and SyncOnSuccess when creating a rotation target (action required) — POST /secrets/{id}/rotation/targets treats an omitted enabled or sync_on_success as true, but the Go SDK request struct sends plain booleans without omitempty, so a caller that leaves them unset sends false and gets a disabled target that never syncs (before, the database default turned that false into true). Set both fields explicitly (Enabled: true, SyncOnSuccess: true) in code that creates rotation targets through the SDK.
  • OCSP responder: X-Forwarded-For is trusted only from TRUSTED_PROXIES; request limits (check the rate limit behind a proxy) — The OCSP responder's per-IP rate limit (RATE_LIMIT_ENABLED, RATE_LIMIT_BY_IP) no longer believes the client-supplied X-Forwarded-For header, which let a client bypass the limit with a different value per request: the limit keys on the peer address unless the peer is listed in the new TRUSTED_PROXIES (comma-separated IPs or CIDRs; Helm ocsp.config.TRUSTED_PROXIES, Compose kit OCSP_TRUSTED_PROXIES), and then on the first address from the right of the header that is not a trusted proxy. Behind a reverse proxy or load balancer set TRUSTED_PROXIES to its addresses, otherwise all clients share the proxy's bucket. A POST body larger than 64 KiB is refused with 413, a POST Content-Type with parameters (application/ocsp-request; charset=binary) is accepted, and GET / answers 400 Missing OCSP request.
  • OCSP responder: shorter response cache (CACHE_TTL default 1 minute) and CA registry refresh — The responder shows a revocation after at most CACHE_TTL, because the responder runs separately and is not told about revocations: the default (and the value in the Helm chart and Compose files) drops from 10 minutes to 1 minute; set CACHE_TTL higher only if a longer window is acceptable. Clients may still keep an earlier good answer until its nextUpdate (RESPONSE_VALIDITY, 1 hour by default). The responder now reloads its certificate authorities every CA_REFRESH_INTERVAL (default 5 minutes, 0 disables), so a new or renewed CA is served without a restart, and a failed reload keeps the previous CAs. An unsigned unauthorized answer (unknown CA) is no longer cached forever.
  • OCSP responder: key check, nonce echo and HSM report — The responder refuses to sign (answers internalError and logs the error) when a CA's private key does not match its certificate, instead of serving a response with an invalid signature. A request with a nonce extension (RFC 8954) gets the same nonce in the response (such responses are not cached); a nonce longer than 32 octets is answered with malformedRequest. Certificate authorities with hsm_enabled are reported when they are loaded (log line OCSP: CA … is unavailable and the gauge ocsp_unavailable_cas) while no HSM signer is configured; no HSM provider is implemented yet, so such a CA still answers internalError.
  • Go SDK and Terraform: deployments carry their organization, new import IDs, whole-day rotation workflows (action required for SDK callers and imports) — The backend takes the organization of an agent deployment from the create body and from ?organization_id= on list, but the Go SDK sent neither, so creating a deployment failed with 403 and reading one with 400; mazevault_deployment could neither create nor refresh a deployment. Breaking Go SDK changes: CreateDeploymentRequest has a new OrganizationID field (set it), and ListDeployments(organizationID) and GetDeployment(id, organizationID) take the organization. CreateRotationWorkflowRequest has a new Environment field: CreateRotationWorkflow stores it with an update right after the create (the create endpoint does not take it), and RotationWorkflowDetail.Resource.EnvironmentScope returns it. Terraform: mazevault_rotation_workflow.environment is now sent and read back (it was never sent, so every refresh showed a change), and ttl_hours must be a positive multiple of 24, because the backend stores whole days (24 h → 1 day). Before, ttl_hours = 36 was stored as 1 day without notice; such a configuration now fails at plan time, so change it to a multiple of 24. Import IDs: mazevault_deployment is imported as <organization_id>/<deployment_id> and mazevault_certificate_template as <project_id>/<template_id>; the plain ID used before is refused. After an import, mazevault_ca restores name and valid_years, mazevault_certificate_template restores type and validity_period (as <n>d), mazevault_integration restores project_id and environment, and mazevault_rotation_workflow restores environment and ttl_hours, so the next plan no longer replaces the imported object.
  • SSO: SCIM provisioning is removed — Organization Settings no longer offers SCIM provisioning, and its tab SSO & Provisioning is now called SSO. The SCIM 2.0 endpoints under /api/v1/auth/scim/{providerID}/ (Users, Groups and ServiceProviderConfig) no longer exist, so an identity provider that still pushes to them gets 404; remove the SCIM provisioning from your Microsoft Entra ID or Okta application. Migration 000299 drops the tables external_identities and external_group_memberships, which held the users and groups pushed through SCIM, and removes the SCIM token hash (scim_token_hash) from the identity providers' configuration; the identity provider remains the source of this data, and the down migration recreates only empty tables and columns. Users created through SCIM keep their accounts; they sign in through Microsoft Entra ID when their stored external ID is their Entra object ID or when they have signed in through Entra SSO before, otherwise they need the one-time re-link described under Entra ID SSO: Nonce Always Enforced, No More Linking by E-mail. The same migration drops the columns users.deactivated_at and users.scim_provider_id that migration 000223 of this release adds; on an installation that ran a pre-release build with SCIM deprovisioning, users that SCIM had deactivated are moved to the Archive and their API tokens are switched off.
  • Login page: the Google button is removed — The login page no longer shows a Google button; it had no sign-in behind it (the endpoint did not exist). google is no longer listed as a user provider value in the API schema. Sign in with a local account, LDAP or Microsoft Entra ID.

Removals (action required)

  • Rotation: scripts and direct agent URLs no longer run from the MazeVault server (check before upgrading) — A shell_script post-rotation action now runs only on a gateway: set gateway_id on the action (API or Terraform). Without it the MazeVault server refuses the action (shell_script runs only on a gateway) instead of running the script on its own host with its own environment; this includes rollback_script. Certificate post-rotation actions are not delegated to gateways, so a certificate shell_script action always fails now: replace it with an agent_command on a registered agent. The direct agent_url target of agent_secret_sync, agent_command and iis_recycle is removed: no MazeVault agent served those calls and they were sent unauthenticated, so the server now refuses such an action (direct agent_url targets are no longer supported) before sending anything, also when an integration's configuration only carries agent_url / gateway_url and no agent. Use agent_id of a registered agent (or an integration bound to an agent). Rotation preflight blocks a post-rotation action with a direct agent_url (agent_url_unsupported), the Entra rotation settings refuse to save one, and the certificate rotation tab no longer offers Direct Agent URL. Existing configurations are not changed; find the affected ones with SELECT 'secret' AS kind, id FROM rotation_configs WHERE post_rotation_actions::text LIKE '%agent_url%' OR post_rotation_actions::text LIKE '%shell_script%' UNION ALL SELECT 'certificate', id FROM certificate_rotation_configs WHERE post_rotation_actions::text LIKE '%agent_url%' OR post_rotation_actions::text LIKE '%shell_script%' UNION ALL SELECT 'template', id FROM rotation_config_templates WHERE post_rotation_actions::text LIKE '%agent_url%' OR post_rotation_actions::text LIKE '%shell_script%' UNION ALL SELECT 'entra_credential', id FROM entra_credentials WHERE extended_rotation_config LIKE '%agent_url%'; and update them before the next rotation. Gateway access is not yet restricted per organization: anyone allowed to edit a rotation's post-rotation actions can name any gateway, so limit who holds secret:rotate until that is fixed.
  • SSH Management Removed — The Enterprise Functions → SSH Management page and everything that served only it are removed: SSH key pairs, authorized-key deployment and revocation, SSH key discovery, the SSH CA and SSH rotation configs, the SSH Keys tab in Access Control, the SSH section in Reports, and SSH rows and links in rotation views. The /api/v1/ssh/* and /api/v1/users/{id}/ssh-keys endpoints, the agent endpoints /agents/me/ssh-discovery and /agents/me/authorized-keys/*, the ssh_key_pair rotation resource kind, the ssh_management license feature, the ssh:* permissions and the built-in ssh_admin role are gone. The Terraform provider no longer lists ssh_key_pair / ssh_key as rotation resource kinds. Export any SSH private keys you still need before upgrading: migration 000218_remove_ssh_management deletes all SSH key pairs (including their encrypted private keys), authorized-key deployments, discovered keys, issued SSH certificates, SSH rotation configs and the ssh_key_pair rotation history and approvals, and strips ssh:* from roles, environment permissions, API tokens and OAuth clients. The down migration recreates only empty tables.
  • SSH CA Key Retired and Its Private Key Wiped — Migration 000218 retires every version of the SSH CA system key (ssh_ca) and deletes its private key; the key stays visible as retired under System Keys, and Rotate on it is refused. The private key then exists only in database backups taken before the upgrade. Already-signed SSH certificates stay valid until they expire. Remove TrustedUserCAKeys /etc/ssh/mazevault_ca.pub (or your equivalent) from sshd_config on your servers.
  • Keys Already Deployed to Servers Stay There — MazeVault no longer deploys or revokes authorized_keys entries. Keys it deployed earlier, including any still waiting for revocation, are not removed from servers; clean them up manually. Upgrade agents as well: older agents keep working but log a 404 for their SSH discovery and authorized-key calls on every discovery cycle.
  • What Stays — Projects of type ssh, the SSH certificate type of CA providers (e.g. Smallstep), storing SSH keys as ordinary secrets or keys, audit logs and key custody events about SSH keys. Existing HSM devices with the ssh_keys scope keep it, but the scope is no longer offered for new devices.
  • Governance Import Removed — The Governance Import tab in Organization Settings → System Outputs is removed, together with the upload of audit-tool reports, their parsed policy requirements, the drift analysis against them and the drift trend. The /api/v1/governance/* endpoints (reports, analyze, drift, drift trend and the report JSON schema) return 404. Migration 000220 drops the governance_reports and governance_drift_snapshots tables, so uploaded reports and drift history are deleted; they exist afterwards only in database backups taken before the upgrade. The other System Outputs tabs are unchanged.
  • Unused certificate sync-config stub endpoints removed — GET/PUT /api/v1/organizations/{id}/sync-config, GET/PUT /api/v1/projects/{id}/sync-config and POST /api/v1/projects/{id}/sync-config/set-master are removed and answer 404. They answered success but never stored or returned anything, and neither the web interface, the SDKs, the CLI nor the Terraform provider called them; project sync settings stay at /api/v1/projects/{id}/sync-settings.

New Features

  • Agent packages (.deb, .rpm, .msi) — The agent can be installed with your own tooling from signed system packages: .deb and .rpm for Linux amd64 and arm64, .msi for Windows x64. Download them under Deployment → Deploy → Install from packages or with GET /api/v1/agent-packages (permission deployment:read); enroll with mazevault-agent enroll or the MSI properties SERVER_URL and BOOTSTRAP_TOKEN. After installation the agent keeps updating itself by the version policy. The MSI is not Authenticode-signed yet. The backend image bundles the packages of its own agent version; other versions come from the MazeVault Harbor (agents/agent-packages). See Agent Deployment → Installing from Packages.
  • Deployment token lifetime and use limit — A deployment can set how long its bootstrap token is valid (expires_in_days: 1–3650, 0 = never; default 30 days as before) and how many agents may register with it (max_uses, 0 = unlimited), in the UI, the API and the Terraform resource mazevault_deployment. The UI warns about a token without expiry or use limit.
  • systemd unit of the agent — The unit written by the install script and shipped in the packages now starts the agent only when /etc/mazevault/agent.yaml or the pre-seed file /etc/mazevault/enroll.conf exists.
  • Roles for Service Identities — A service identity can now be given roles: POST /api/v1/service-identities/{id}/roles, GET on the same path and DELETE /api/v1/service-identities/{id}/roles/{assignmentId} (permissions role:assign / role:read on the identity), the new Roles dialog on the Service Identities page, the Go SDK (AssignServiceIdentityRole, GetServiceIdentityRoles, RemoveServiceIdentityRole) and the Terraform resource mazevault_service_identity_role. The role applies in the identity's organization or in one of its projects only; a global scope or a scope in another organization is rejected with 400, and an identity without an organization gets no roles. As for users, you can assign or remove only a role whose permissions you hold yourself in that scope (else 403). A removal applies to the identity's next request. A role that happens to be named service no longer applies to every service identity through the token's role claim: an identity has only the roles assigned to it. Migration 000250 adds organization_id to service_identity_roles; rows written there outside the API before the upgrade are confined to the identity's organization: a row without a project gets the identity's organization, a row of an identity without an organization or whose project or organization belongs to another organization is deleted, and each kept or deleted row is listed as a warning in the PostgreSQL log. The permission engine also ignores any identity role row outside the identity's organization.
  • Terraform Provider: Onboarding a Fresh Installation — The provider now covers the organization setup that previously required the UI. mazevault_config_template follows the Configuration Management contract (template_type + JSON definition, validated by the backend at plan time; the former content/format arguments never worked against the API). New mazevault_project_template manages secrets/keys Project Templates, mazevault_entra_integration_scope selects the Entra ID applications a project integration manages (with Graph sync after apply), and the data source mazevault_entra_app_registration resolves the credential IDs for mazevault_entra_rotation_config. mazevault_ca_account gains a write-only credentials map for self-hosted CAs (Smallstep, ADCS, Vault, EST) and rejects secret-looking keys in the plaintext settings. mazevault_certificate gains key_storage_mode, key_integration_id and key_secret_name so server-side generated private keys can be offloaded to a secret manager (required in Orchestrator Mode). See Terraform Provider Guide → Onboarding a Fresh Installation.
  • Effective Certificate Chain: Intermediate CAs Export With Their Root — Exporting an intermediate CA (or any certificate) that was imported without its issuer chain now includes the issuing CA and root automatically whenever the issuer is present in the organization inventory. A new CertificateChainResolver is the single source of truth for the effective chain: a chain captured on the record at import or issuance is returned byte-for-byte and never extended; only certificates with no stored chain are completed from CA certificates managed in the same organization (including the internal CA and the record's explicit CA link), and a candidate is accepted solely when it cryptographically verifies the certificate's signature — a matching Distinguished Name alone never links two certificates. Roots are included, archived candidates are ignored, a revoked issuer is used only when nothing else verifies and is reported as a warning, and issuers signed with insecure algorithms are rejected with a warning. The same chain is used by the export API (include_chain=true, all formats), by the certificate detail (Chain tab now shows the provenance — Stored with certificate / Resolved from inventory / No chain — plus completeness to the root and warnings), by the agent certificate list, and by the Terraform data source, so every consumer sees identical material. Reading never writes to the database; the chain is persisted only when an import or adoption writes a certificate body that has no chain. The GET /certificates/{id} response gains chain_source, chain_complete and chain_warnings; the mazevault_certificate data source gains chain_pem, chain_source and chain_complete, and its certificate_pem attribute — previously mapped to a field the API never returned — is now populated from public_chain_pem. New Prometheus metric: mazevault_certificate_chain_resolution_total{source,complete}.
  • Discovered Certificates Keep Their Body; Adoption Yields a Deployable Certificate — Agent discovery now persists the public leaf certificate and the issuer chain it observed (migration 000199, columns certificate_pem / chain_pem on discovered_certificates; public material only, never a key). Adopting a discovered certificate therefore produces a managed certificate with a body that can be exported, deployed and verified; managed certificates adopted before this release receive their body automatically on the next agent scan through the existing thumbprint auto-link, and a reported chain is stored as-is (completed from the inventory only when the agent reported none). The discovered-certificate detail (GET /discovered-certificates/{id}) returns certificate_pem, chain_pem and chain_certificates; list responses deliberately omit the body so the Discovered Certificates page stays fast. Identity (serial, thumbprints) is now derived from the certificate DER instead of being trusted from the agent report.
  • Certificate Body From CA Synchronisation and From Manual Import — The body is now also kept for certificates synchronised from a CA listing (HashiCorp Vault, DigiCert, ADCS, SSLMarket and the local-inventory providers used for ACME / I.CA / PostSignum): ParseCertificateMetadata carries the normalized PEM into the discovered record, a re-run of the CA synchronisation back-fills discovered records that were created without it, and a managed certificate linked by thumbprint that has no body receives it the same way the agent path does. In addition, importing the public certificate alone (CRT/PEM/DER, or a PFX) now completes a managed certificate that has no body instead of being rejected as "already exists": the import is matched by thumbprint (or pinned with expected_certificate_id from the certificate detail), the body is stored together with the identity and descriptive columns derived from it (canonical serial, thumbprints, subject/issuer, validity, key and signature algorithms, key usage, SAN set, certificate type; an active status is refreshed, a revoked or pending one is never changed), and a chain-less record is completed from the inventory. Private key handling on such a record follows the approved rule: a key supplied together with the certificate is attached when the record has no custody; a key that matches an existing custody is an idempotent re-import; a key that does not match is rejected with an explicit error after the body was stored — the record is never left without a body because of a wrong key. A locally stored key that could not be verified at the time it was imported (the record had no body) is verified when the body arrives and, if it does not belong to the certificate, is flagged (metadata.private_key_mismatch, audit event certificate.private_key_mismatch) but never deleted. Bulk/file import items report message: "existing certificate record completed with the uploaded certificate body" for this case; the certificate detail shows a Complete certificate action for records without a body, and the discovered-certificate detail explains the manual path. One shared implementation (applyCertificateBodyToManaged) now backs discovery ingest, CA synchronisation, adoption and import, so every path yields the same fully described record (the previous re-scan path set key usage only in memory and did not persist it).
  • Agent File Deployment and Allowlisted Commands — Certificate rotation can now deploy to files on an agent host (Agent File (PEM) target, step agent_file_sync): the agent writes leaf, chain, key and an optional combined bundle (PEM or PKCS#12) according to the target's content mode (public_only, public_chain, full_chain_with_key, key_only), applies permissions and ownership, keeps the previous files as .BAK, installs the chain into the platform trust store when enabled, and runs the optional post-install command. Rotation steps that need a command on the host (service restart, WLST restart script, Oracle orapki) now execute through the new agent agent_command request, which runs only commands matching the agent's built-in allowlist or an operator-configured allowed_commands regular expression. Oracle database wallet targets are now deployed for real: a one-time-password PKCS#12 is staged under <wallet>/mazevault/, imported with orapki wallet import_pkcs12 (auto-login or password-protected wallets via wallet_password_ref), and always removed afterwards; the wallet and PKCS#12 passwords are flagged as sensitive so the agent redacts them in its audit log, its response and any rejection error, and the staging step carries no idempotency key so a retried rotation re-stages the file instead of replaying a stale result. update_tns_names and rac_nodes are refused explicitly rather than ignored. WebLogic targets now deploy through a real weblogic_deploy step: the leaf and key are installed into the custom identity keystore (JKS or PKCS#12, backup via backup_enabled), the issuer chain is optionally imported into the custom trust keystore (trust_store_path + new trust_store_type / trust_store_password_ref; each issuer is staged as its own PEM under <trust-store-dir>/mazevault/, imported with keytool -importcert -trustcacerts, an alias that already exists counts as success and the staged file is always removed), and the managed server or cluster is optionally restarted through the operator's wlst_script_path (restart_mode graceful / force / rolling). The trust-store password is flagged as sensitive so the agent redacts it everywhere, and admin credentials are never placed on a command line — the WLST script must use stored credentials; admin_url, server_name and cluster_name are rejected when they contain shell metacharacters, and a restart mode requires admin_url. Structured agent_command requests (keytool, orapki, WLST script) are executed by the agent directly with their argument vector, without a shell, so a password containing shell metacharacters can neither be reinterpreted nor echoed back by the shell; sensitive values are additionally scrubbed from the command output and error text before they leave the agent. Operator-authored post-install command strings keep their shell semantics.
  • Deployment Targets Are Validated When Saved — Creating or updating a certificate deployment target now validates it against what the system can actually execute, and returns 400 with the reason instead of accepting a configuration that would later fail or be skipped: agent-based targets (agent_keystore, agent_file, database, weblogic) require an agent; a database wallet target requires wallet_path, an Oracle provider (SQL Server, PostgreSQL, MySQL and MongoDB have no wallet — the error names the Agent Keystore / Agent File target to use instead) and update_tns_names=false; a WebLogic target requires keystore_path, a password reference for its trust store and a WLST script for any restart mode; web_server and load_balancer targets are refused with the supported alternative because no deployment mechanism exists for them yet. The workflow builder applies the same validation, so an enabled target that cannot execute fails the rotation with the target ID and reason instead of being skipped silently. Disabling or re-prioritising an existing target with an invalid legacy configuration is always allowed, so operators can take it out of the rotation; enabling it again requires fixing the configuration. The rotation target editor in the UI mirrors these rules: Database Wallet requires an agent and is labelled Oracle, the non-Oracle providers and the Update TNS Names switch are disabled with the guidance text, Web Server is shown as not available yet and Load Balancer is hidden from the picker for new targets (an existing one still opens in edit mode with the explanation, so it can be disabled or deleted — nothing was removed from the API or the code), the backend's validation reason is shown when a save is refused, and the target list gained an Enable / Disable action that changes only the enabled flag — the way to take a legacy target out of the rotation without deleting it.
  • Secret Rotation Backups: Retention, Cleanup, and Manual Recovery — Every secret rotation captures an encrypted pre-rotation backup and now tracks it through an explicit lifecycle (pending → released / restored / recovery_required → acknowledged). A daily cleanup job purges only terminal-state backups past their retention window, while backups whose automatic rollback failed are flagged recovery_required and are never auto-deleted. Operators can list backups, restore or reveal a pre-rotation value, and acknowledge a handled backup through new rotation API endpoints: GET /rotation/backups, POST /rotation/backups/{id}/restore, POST /rotation/backups/{id}/reveal, and POST /rotation/backups/{id}/acknowledge. Reveal and restore additionally enforce secret:read on the secret's environment inside the handler, and for externally-governed (orchestrator-mode / names_only) secrets no local value is ever stored or returned — the operator is directed to retrieve the value from the external provider under their own identity, so the API cannot become a side-channel around Key Vault access control. Retention is configurable via the new MAZEVAULT_ROTATION_BACKUP_RETENTION_DAYS variable (default 7). Two Prometheus metrics were added: mazevault_rotation_backups_purged_total and mazevault_rotation_backups_recovery_required.
  • Rotation Preflight Now Verifies Gateway and Agent Readiness Before Execution — The rotation preflight engine and orchestrator now evaluate the live readiness of every required execution target — gateways and post-action agents — before a rotation is allowed to start, instead of only checking configuration. A new ProbeGatewayByID method probes a specific gateway by ID, and a non-mutating EvaluateReadiness path assesses whether a rotation resource is ready without changing any state. The preflight engine distinguishes required from optional post-action agents, so a rotation that depends on an offline gateway or an unavailable required agent is classified and reported up front (feeding the ready / deferred / blocked / manual-review-required outcomes) rather than failing partway through. The rotation scheduler classifies gate errors more efficiently as part of this work.
  • Service Identity Grace-Period Revoker — A new background job hard-revokes client (service identity) secrets whose grace-period deadline has been reached, completing the "soft revocation" path that RotateClientSecret starts when a grace period is configured. Previously a rotated secret with a grace window relied on the old credential remaining valid, but nothing durably retired it once the window elapsed. The revoker runs periodically from the scheduler (default once per minute), materializes expired grace periods, and writes an audit record for each hard revocation.
  • Agent Lifecycle Management and Deployment Record Handling — Registered agents can now be managed through their full control-plane lifecycle from Deployment → Agents, and agent authentication is lifecycle-aware end to end. A new RequireActiveAgent route guard re-checks the agent's current status in the database on every JWT-authenticated agent request, so an agent that was unregistered, revoked, or deleted can no longer keep calling agent routes with an unexpired token; registration and the legacy hostname-discovery path no longer reactivate an inactive or revoked agent, and runtime consumers (rotation sync-target resolution, SSH key deploy/revoke, keytab rotation) refuse to route work through a non-active agent. Four new agent:admin endpoints back the console: GET /api/v1/agents/:id/lifecycle-impact returns an advisory dependency summary (managed certificates, project bindings, rotation targets, SSH keys, pending keytab work, discovery/config/token references) with best-effort warnings; POST /api/v1/agents/:id/unregister retires an agent — invalidating its mTLS identity and recovery tokens, deactivating project bindings, and stranding pending keytab work — or escalates to a security revoke when the reason category is compromised / lost_credential / stolen_host, requiring force past unresolved blockers; POST /api/v1/agents/:id/reregister retires the old identity and issues a single-use 24-hour replacement bootstrap token; and POST /api/v1/agents/:id/uninstall asks a connected, capable agent to uninstall itself. Remote uninstall is capability-negotiated at request time and supported for Linux packaged (systemd) installs running as root and Windows MSI service installs: the agent accepts first, then a detached copy of the agent binary (escaping the service cgroup via systemd-run on Linux, a DETACHED_PROCESS on Windows) performs the removal and reports the real outcome back over the health channel before self-destructing — on Windows the per-build MSI ProductCode is resolved at runtime from the stable WiX UpgradeCode and removed via msiexec, with an sc delete fallback for non-MSI service layouts. The agent's retirement is finalized only on a reported success; a reported failure keeps the agent active (uninstall_status = failed) for retry, and a missing report is surfaced as awaiting confirmation. Unsupported layouts (non-packaged/manual installs, macOS, offline) return a clear fallback to unregister plus manual removal. Deployment (bootstrap) records gained a soft-delete lifecycle — DELETE /api/v1/deployments/:id (gated by deployment:write, organization-scoped) invalidates the enrollment token and install script without affecting already-registered agents, and the public script/config endpoints then return 404. Deployment list responses use a dedicated token-free DeploymentResponse DTO with a computed usable flag, while the create response returns the bootstrap token exactly once via DeploymentCreatedResponse (the UI, Go SDK, and Terraform provider depend on it). The Deployment → Agents tab becomes an operational console (unregister with impact preview + reason, remote uninstall, re-register with one-time token, visible error states), and Deployment → Deploy gains delete, view-script, and clipboard-fallback actions. Finally, the agent module's own go test suite now runs in PR, dev, and release CI (previously only cross-compiled), and a filesystem-discovery policy-invalidation test that was root-fragile was made deterministic.
  • Rotation Templates Gain Policy Windows and Secret Custody Mode — Rotation configuration templates now carry lead_time_days (days before expiry to start a certificate renewal), grace_period_days (days the previous value stays valid for rollback), and secret_custody_mode (local, transient, or delegated). These fields are exposed through the rotation template API, the Go SDK, and the mazevault_rotation_template Terraform resource, and are aligned end to end with the persisted catalog (migration 000195). The rotation template API schema and its Swagger documentation were updated to describe the new fields. The template additionally exposes project_id (optional project scoping) and retry_delay_seconds, and on the Terraform resource organization_id is now Optional+Computed (server-resolved) with a single shared flatten path so create, read, and update cannot drift; these additions map to already-persisted columns and require no new migration.
  • Discovered Certificates: Search and Assignment Modes — The Discovered Certificates page adds a server-side search filter and explicit assignment modes for adopting discovered certificates into management, including a bulk-adopt endpoint (POST /discovered-certificates/bulk/adopt, gated by discovered_cert:bulk) alongside the single-item adopt path. The standalone Certificate Discovery route was removed and now redirects to Discovered Certificates.
  • Actionable Rotation Status on the Dashboard — The Dashboard now resolves rotation items to their actionable destination through a new navigation resolver, so rotation status entries link directly to the surface where they can be acted on.
  • Deployment Consolidated Under Enterprise Functions (PoC / Enterprise Gated) — Configuration Management, SSH Management, KeyTab Management, and Deployment now live together with Token Management under a single Enterprise Functions navigation group, which is a pure grouping header that only expands/collapses and never navigates on its own. Configuration, SSH, and KeyTab were already gated by their config_management / ssh_management / keytab_management license features; Deployment is now gated the same way through a new deployment_management feature granted to PoC and Enterprise licenses (active, grace, or expiring state). The /api/v1/deployments management group (create / list / delete) is enforced server-side with FeatureLicenseMiddleware, returning HTTP 403 for Small Business or unlicensed tenants, and the Deployment page renders an upgrade banner instead of the tabs; users still see the menu entry when they hold the matching RBAC permission. The unauthenticated, UUID-protected agent bootstrap endpoints (/deployments/:id/script, /config, /agent-binary) are intentionally left ungated so unattended agent installs keep working, and agent/gateway management endpoints are unchanged.
  • mazevault-agent enroll Command and Pre-Seed File — mazevault-agent enroll --server <https-url> --token <token> [--ca-file <path>] [--force] [--no-start] writes the configuration, copies a private CA file and enables the service in one step; --force gives the agent a new identity (certificate, key and recovery token) — the server keeps the same agent record when the hostname and the machine ID match — and does not delete ca.crt or the pre-seed file. For automated installs the agent reads /etc/mazevault/enroll.conf (Windows: C:\ProgramData\MazeVault\enroll.ini) on start when no agent.yaml exists. See the Agent Deployment Guide.
  • Certificate Import: Documentation Link and Note at Import Time — The certificate import dialog (Review & Configure step) now accepts an optional Documentation URL and a free-text Note next to the existing Tags field, so this metadata can be captured during import instead of only afterwards on the certificate detail. The values are validated with the same rules as the detail editor (the documentation link must be an http(s) URL of up to 500 characters; the note is limited to 256 characters) and are applied to every certificate created from the upload — including each certificate of an imported chain and every file in a multi-file import, matching the existing behaviour of Tags.

Improvements

  • Secret Manager Integrations Support Azure Key Vault Only — The AWS Secrets Manager, HashiCorp Vault and GCP Secret Manager secret-manager providers have been removed; Azure Key Vault is the only supported secret manager. Creating, connection-testing or syncing one of the removed providers is rejected, and their key/secret offload and rotation targets are no longer offered. Existing integrations and rotation targets of the removed types are disabled by a migration (their rows are kept so their owners can review and delete them) and can still be read, updated and deleted. — Certificates signed from an uploaded CSR are stored with key custody none; operators can now hand MazeVault the key afterwards from Certificate detail → Overview → Import private key (PEM key, encrypted PEM key, PFX/P12, or pasted PEM) with local or secret-manager custody, respecting Orchestrator Mode. No new endpoint was introduced: the dialog reuses POST /certificates/import/file, and the new optional expected_certificate_id form field (also available on POST /certificates/import) pins the request to that single certificate so unrelated material is rejected instead of creating or mutating another record. The multi-file import now pairs standalone .key files with their certificate through the backend preview-multi matching (new key_source_file in the preview response) and sends certificate and key in one request, so a separately uploaded key reaches an already-managed certificate. The duplicate/attach lookup is now SHA-256-thumbprint-first (serial + issuer remains a legacy fallback that refuses rows whose stored thumbprint differs), the custody check honors external custody (an externally stored key is never silently replaced by a local copy), a project-scoped import can no longer mutate a certificate owned by another project, attaching a key never changes the certificate's renewal settings or downgrades a richer stored chain, and the "already exists" error now explains how to add the key.
  • GitOps and HSM Integrations Hidden for 1.0 — The GitOps integration type (GitHub, GitLab, Azure DevOps) and the HSM integration type (AWS CloudHSM, Azure Managed HSM), together with the DevOps Variable rotation target, are no longer offered. Creating, connection-testing or syncing one is rejected, and they are removed from the integration wizard and rotation-target selectors. Existing integrations and targets of these types are disabled by a migration (their rows are kept so their owners can review and delete them) and can still be read, updated and deleted. HSM Devices (Organization Settings) and GitHub / GitLab SSO keep their own features and are unaffected. GitOps and HSM are planned to return as extensions in a later version. — Certificates signed from an uploaded CSR are stored with key custody none; operators can now hand MazeVault the key afterwards from Certificate detail → Overview → Import private key (PEM key, encrypted PEM key, PFX/P12, or pasted PEM) with local or secret-manager custody, respecting Orchestrator Mode. No new endpoint was introduced: the dialog reuses POST /certificates/import/file, and the new optional expected_certificate_id form field (also available on POST /certificates/import) pins the request to that single certificate so unrelated material is rejected instead of creating or mutating another record. The multi-file import now pairs standalone .key files with their certificate through the backend preview-multi matching (new key_source_file in the preview response) and sends certificate and key in one request, so a separately uploaded key reaches an already-managed certificate. The duplicate/attach lookup is now SHA-256-thumbprint-first (serial + issuer remains a legacy fallback that refuses rows whose stored thumbprint differs), the custody check honors external custody (an externally stored key is never silently replaced by a local copy), a project-scoped import can no longer mutate a certificate owned by another project, attaching a key never changes the certificate's renewal settings or downgrades a richer stored chain, and the "already exists" error now explains how to add the key.
  • Unused session secret removed from the deployment configuration — MAZEVAULT_SESSION_SECRET (SESSION_SECRET in the Compose kit's .env, global.keyVault.secretKeys.sessionSecret and the Key Vault secret mazevault-session-secret in the Helm chart) was required by the installers and the chart but never read by the backend. New installations no longer generate it, and the chart no longer reads it from Key Vault. Existing installations need no action: a leftover value in .env, Helm values or Key Vault is ignored and can be deleted.
  • Unused Console URLs and the Agent Discovery Results API Removed — The web interface no longer has the /settings placeholder page ("This feature is currently under development") or the old URLs that only redirected to pages in the menu: /certificates and /certificates/{id} (Certificate Dashboard), /password-requests (Password Requests), /notifications and /conflicts (Synchronization Problems) and /discovery/certificates (Discovered Certificates). No link of the application led to them; an old bookmark now opens the dashboard. The Discovery Results page of an agent (/agents/{id}/discovery), which no link led to either, and its API GET /api/v1/agents/{id}/discovery are removed as well (a call now returns HTTP 404): GET /api/v1/discovered-certificates?agent_id={id} (needs discovered_cert:read) returns the certificates an agent discovered. The agent, the CLI, the Terraform provider and the Kubernetes operator did not call the removed API. An invalid agent_id, project_id or managed_certificate_id on GET /api/v1/discovered-certificates now returns HTTP 400; it used to be ignored, so the call returned every discovered certificate.
  • Audit log archive destinations removed — The Organization Settings → System Outputs tab is now called Audit Log Streams and configures real-time streaming to Splunk, Elasticsearch and Syslog only. Archive destinations (AWS S3, Azure Blob Storage, File Share), the batch upload behind them, the POST /organizations/{id}/integrations/test-archive endpoint and the Prometheus metrics mazevault_archive_events_dropped_total and mazevault_archive_upload_errors_total were removed. Migration 000219_remove_audit_archive_destinations deletes the stored archive destination configurations, whose credentials were kept unencrypted. To keep audit history beyond the local retention, stream it to a SIEM. Remove alerts or dashboards that use the two metrics. Creating an organization integration now accepts only the categories notification and log_stream and answers 400 for any other, archive included.
  • Attach a Private Key to an Existing Certificate (CSR-Issued or Imported Without Key) — Certificates signed from an uploaded CSR are stored with key custody none; operators can now hand MazeVault the key afterwards from Certificate detail → Overview → Import private key (PEM key, encrypted PEM key, PFX/P12, or pasted PEM) with local or secret-manager custody, respecting Orchestrator Mode. No new endpoint was introduced: the dialog reuses POST /certificates/import/file, and the new optional expected_certificate_id form field (also available on POST /certificates/import) pins the request to that single certificate so unrelated material is rejected instead of creating or mutating another record. The multi-file import now pairs standalone .key files with their certificate through the backend preview-multi matching (new key_source_file in the preview response) and sends certificate and key in one request, so a separately uploaded key reaches an already-managed certificate. The duplicate/attach lookup is now SHA-256-thumbprint-first (serial + issuer remains a legacy fallback that refuses rows whose stored thumbprint differs), the custody check honors external custody (an externally stored key is never silently replaced by a local copy), a project-scoped import can no longer mutate a certificate owned by another project, attaching a key never changes the certificate's renewal settings or downgrades a richer stored chain, and the "already exists" error now explains how to add the key.
  • Uploaded CSRs Are Validated Against the Template Before the CA Is Contacted — Uploaded CSRs now fail fast at submission when they violate explicit template policy: the self-signature must verify, the key size must be in allowed_key_sizes when configured (previously enforced only by the internal CA, never by ADCS or external CAs), and the SAN entries must satisfy an explicit san_config (allowed_san_types, san_validation_pattern, max_san_count, and the new require_san flag that rejects CN-only CSRs). Defaults are permissive by design — templates without san_config accept every CSR as before — and system templates (agent/gateway mTLS) are exempt. The requested SAN set and key parameters of an uploaded CSR are now recorded on the certificate request for auditability.
  • ADCS Manager-Approval Requests Are Tracked Instead of Failing — Issuing a new certificate through an ADCS CA that requires manager approval no longer returns HTTP 500 while the CA keeps the enrollment: the request moves to submitted_to_ca and is finalized automatically by the external order poller once the CA approves. CA providers can implement the new optional IssuedCertificateFetcher interface to return the issued leaf for an order ID; ADCS implements it, fixing a latent defect where the poller would have stored the CA chain as the leaf.
  • Certificate Request Lifecycle After Issuance Failures — An auto-approved request whose issuance fails is now marked failed with the error recorded (instead of lingering as approved without a certificate), and a manually approved request whose issuance fails is returned to pending so the approver can retry; a later successful issuance clears the recorded error. The request status enum documents the failed state and the UI shows it.
  • Canonical Certificate Serial Encoding — certificates.serial_number is consistently stored as the decimal big-integer string already used by OCSP, revocation and import lookups. ACME and HashiCorp Vault issuance, external order finalization and the built-in ACME server fast path previously wrote or queried hexadecimal serials, which broke import de-duplication and the ACME server's indexed lookup for those certificates. Vault revocation and status calls translate the canonical serial back to Vault's colon-separated hex format, so both legacy and normalized rows keep working. An idempotent identity backfill (BackfillCertificateIdentity) re-derives serial and thumbprints from the stored DER; it runs in the background on primary startup and is also part of POST /admin/certificates/backfill, which now reports identity_normalized.
  • Template Editor No Longer Flips "Allow Additional SANs" — allow_additional_sans on the certificate-template create/update API is now presence-aware (omitted keeps the stored value; an explicit false is honored). Previously an update that omitted the field — which the template editor UI never sends — reset it to false.
  • Integration Group Scope Resolution for RBAC — A new IntegrationGroupScopeMiddleware resolves a standalone integration-group ID to its owning active project before project RBAC is evaluated, and rejects requests for groups whose project is archived or missing. This lets integration-group endpoints be authorized consistently with the rest of the project RBAC model.
  • SSH Authorized-Key Revocation Lifecycle — Agents can poll for pending authorized-key revocations (GET /agent/authorized-keys/pending-revocations) and confirm completion (POST /agent/authorized-keys/:id/confirm-revocation), and operators can revoke an authorized key (POST .../authorized-keys/:id/revoke) and retry a failed SSH key deployment (PUT /ssh-keys/:id/deploy). A stopped or partially failed rotation execution can be resumed through POST /rotation/executions/:id/resume. The SSH key rotation confirmation message was also clarified.
  • Rotation Runtime Targets Only Route Through Active Agents — When a rotation delegates work to an agent (for example keytab or SSH deployment), the runtime now resolves the linked agent and refuses to route through an agent that is inactive, revoked, or soft-deleted, returning a clear error instead of proxying to a dead target.
  • Rotation Template Numeric Field Validation — Rotation template numeric fields (rotation_interval_days, lead_time_days, grace_period_days, max_retry_attempts, retry_delay_seconds, timeout_minutes) are now validated as non-negative integers across the backend API, the frontend, and the Terraform provider, with structured application errors instead of opaque failures. The retry_delay_seconds field — already persisted and exposed through the API, SDK, and Terraform resource — is now also editable in the rotation-template UI form, so it can be configured and viewed from the console. Bulk certificate adoption surfaces user-friendly messages for partial failures.
  • Graceful Backend Shutdown on SIGTERM — The API server now drains in-flight requests through http.Server.Shutdown when it receives SIGTERM — with a drain deadline kept below the Kubernetes termination grace period — and then stops the log-stream and audit-archive background services before exiting, instead of being force-killed mid-request during rollouts. http.ErrServerClosed is treated as a normal shutdown.
  • SSH Authorized-Key Ownership Enforcement — Listing, deploying, revoking, and removing SSH authorized keys (and listing key pairs) now consistently enforce key-pair ownership — the acting user must own the key pair or hold ssh:admin — matching the ownership checks already applied to get, export, and delete. A non-admin can no longer enumerate another user's key inventory or deploy/revoke/remove keys they do not own, and producer-side revocation and removal, plus the agent's revocation confirmation, now write request-ID-scoped audit records.
  • Rotation Resume Requires an Authenticated Actor — POST /rotation/executions/:id/resume now rejects a request without an authenticated principal with HTTP 401 before the orchestrator runs, and records both successful and failed resumes with full request-ID audit context.
  • Agent Recovery Token Stability (no re-registration lockout) — Agent re-registration no longer rotates a still-valid recovery token on every call. A fresh recovery token is minted only when the agent bootstraps/reinstalls with a deployment token or has no usable token on record; an agent that authenticates with its current recovery token keeps it. Combined with atomic, crash-safe agent-state persistence — the rotated token and identity are staged and committed in memory only after the durable write succeeds, and the temp-file replacement is now atomic on Windows as well (MoveFileEx with replace-existing) rather than only on POSIX — an agent that crashes or fails to persist a rotated token can no longer be permanently locked out. This is a behavioral hardening only — no schema change and no API contract change.
  • Agent Registration Hardened (behavior change) — Agents now send a hashed machine ID. A deployment token can no longer re-register an agent that belongs to another organization (the request is rejected). Two machines with the same hostname but different machine IDs now become separate agents instead of overwriting each other (agents of this version; older agents keep working unchanged). An agent that lost its local state can still re-register with its original deployment token after the token expired, but only on the same machine and within the same organization. An agent's recovery token identifies it even when its machine ID changed (for example after a rollback to an older agent version), and a record of an older agent without a machine ID in another organization no longer blocks the hostname for an agent of this version.

Bug Fixes

  • Destroying mazevault_shared_secret revokes the share link; shared secrets are importable — New DELETE /api/v1/share/{id} deletes a share (only the user who created it; anyone else gets 404). The Terraform provider calls it on destroy and replacement, so the old link stops working. Import ID is now <share_id>/<secret_id>.
  • Number and list fields keep what you type; form labels are announced; template and project names are required — Number fields (e.g. priority, lead days, validity, intervals, password lengths) no longer snap back to their default when cleared, so typing 14 stores 14 instead of 3014; an emptied field keeps its last valid value instead of saving null. Comma-separated list fields (Elasticsearch and Actuator endpoints, policy OIDs, visibility-rule scopes) keep typed commas and spaces, and typing a visibility rule ID no longer loses focus. Form labels, the audit-log filters and the compare-environments selects now have accessible names. The project-template wizard and Create Project refuse an empty name, and the template's Environments step has an explicit "All environments (including future ones)" choice: templates stored for all environments keep that on save, picking specific environments stores just those, and picking none now means no environment instead of all of them.
  • Agent reports failed install steps, resends lost task answers and stops promptly — A certificate sync whose post_install_command failed no longer records the step as completed (the answer carries post_install_error), and a Linux trust directory that takes no CA certificate is reported as a failed trust-store install instead of success. A task answer the agent could not send because the connection dropped is kept and sent again after it reconnects; this takes effect once the backend matches answers across connections, which a later backend update adds. Stopping or restarting the agent no longer waits 10 seconds for the task connection, an agent whose mTLS client key does not load stops with a configuration error instead of crashing later, and keystore passwords are passed to keytool and openssl through the environment instead of the command line, where every local user could read them.
  • The UI offers only actions your role allows; login errors and one-time secrets handled correctly — Buttons and pages now follow the same permission as the server: Password Requests needs agent:admin (menu and page), license activation and renewal need billing:write, rotation templates are edited and deleted with rotation:write, Add User on a project needs role:assign, assigning templates and project settings need project:write (without it the settings are read-only), integration changes need integration:write, and the Entra ID dashboard needs integration:read and entra:read (Sync needs entra:write). Pages no longer load data before checking access. Without agent:admin, the certificate deployment-target editor offers the agents bound to the certificate's project. An encryption failure after MFA is no longer reported as an invalid MFA code, and a failed LDAP or MFA login shows its own error. A cancelled password-request approval no longer keeps the typed password, a revealed managed token is masked until you click Show, and a newly created client secret or API token is dropped when its dialog closes.
  • Dialogs keep your input on errors; saves no longer drop project tags, webhooks, descriptions or CA URLs — The incident-contact dialog and the new-environment form stay filled when saving fails, a failed ignore of a discovered certificate and a failed first load of domain validation are shown, and managed-token errors without a server message name the failed action. Saving project settings keeps the project's tags and notification webhooks, editing a service identity keeps its description, and clearing the OCSP or CRL URL of a CA removes it. The rotation-history button of an Entra credential shows that credential's history, Bind Agent shows the agent's OS, managed tokens can be deployed to AWS Secrets Manager, and an environment group mapping is created for the identity provider you choose (Identity Provider field). The system certificate upload no longer offers backup and reload options, which the server never applied. Moving a rotation target earlier or later now works when targets share a priority, a certificate with two post-rotation actions of the same type no longer saves the edited one twice, and changing a secret rotation target's type no longer sends the old type's settings. Usage and Billing shows real project and secret usage (needs org:read), and a dismissed license expiry notice stays dismissed after a reload. Rotation templates and keytabs are deleted only after an in-app confirmation, the MFA settings follow edits of the raw JSON, a gateway can be assigned as DR standby to an environment another gateway is primary on, the agent list is in English and shows "Never" for an agent that has not reported yet, and the certificate details no longer stay on "Loading...".
  • Creating records without optional fields no longer fails — A rotation template or an integration group created without its optional lists (e.g. notify_before_days, mappings) and an approval policy created without conditions answered 500; they are now stored with empty values (existing approval policies with conditions [] are converted to {}). Switching an organization to intermediate or proxy PKI mode now really registers the upstream CA account (pending validation), and recorded rotation replay metadata without a metadata field is no longer dropped.
  • PostgreSQL session listing and drain work; rotation failures show superseded runs and linked incidents — Listing and draining the sessions of a PostgreSQL integration answered 500 for every TCP session, so password rotation with session drain could not finish. On the dashboard, a rotation failure followed by a successful run was never marked as superseded and never linked to its open rotation-failure incident.
  • Temporary access and agent–project bindings work beyond the first record — Temporary access grants (including break-glass) and agent–project bindings were stored without an id of their own, so only the first one in the whole system could be created: the second grant and the second binding failed with an error, and revoking a grant failed too. Each record now gets its own id; a record created earlier keeps working.
  • Rotation Delivery and Cleanup Steps Fail Instead of Reporting Success (check rotation configs before upgrading) — Several rotation steps reported success without doing their job; they now fail the step, so the rotation fails visibly (and is compensated) instead of being published with a target still holding the old value. post_rotation_sync still updates every reachable Key Vault but fails when a configured integration cannot be resolved (deleted or disabled, gateway resolution error), has no vault_url or no usable credential; the rollback of a rotation run on the MazeVault server then also restores the previous value in the vaults the failed step had already updated, also when a vault write itself failed (a step delegated to a gateway is not covered yet; before, a failed step was never compensated, and post_rotation_sync compensation found no secret key in rotations run on the MazeVault server, so it restored nothing). certificate_to_keyvault fails for a provider it does not support (for example gcp_secret_manager) instead of "simulating" the push. spring_actuator fails when any configured endpoint did not refresh (HTTP error, unreachable, TLS). The Kubernetes pod trigger now honours label_selector: it restarts every deployment, statefulset and daemonset matching the selector and fails when nothing matches; its service account needs list and patch on deployments, statefulsets and daemonsets (API group apps) in the namespace, even if only deployments match. The cleanup step has no cleanup implementation, so a configured target is now rejected as not supported instead of being reported as cleaned: true. Sync now on a secret's deployment target (POST /api/v1/secrets/{id}/rotation/targets/{targetId}/sync) now delivers the secret's current value to that target immediately and answers 200 with the target carrying the real result (last_delivery_status success or failed with last_delivery_error); before, it only set the status to pending, which never changed. Database targets get the admin credentials of their integration as in a rotation, and the delivery has the same time limit as a rotation step (timeout_seconds, default 5 minutes). A target that cannot be synced (role other than distribution, or a target type without a delivery step) is refused with 409 and its delivery state is left unchanged; so is a target whose integration, agent or target project lies outside the secret's project or organization, checked as for a rotation step. The web interface shows a failed sync with its error. The manual sync runs on the MazeVault server, not on a gateway: it fails for a target reachable only through a gateway, and in an environment that routes rotations through a gateway, a target the server can reach is written by the server.
  • Entra post-rotation delivery completes — Delivering a rotated Entra client secret to its post-rotation targets no longer ends as a failed execution (with rollback and a propagation-failure notification) after all targets succeeded, and a target with a long name (e.g. a webhook with a long URL) is no longer silently left out of the delivery: if a delivery step cannot be stored, the delivery fails and the failure notification is sent.
  • Naming Convention: Parsing and Provisioning Work With the Default Convention — POST /api/v1/naming-conventions/{id}/parse and /provision answered 500 for every name of the default convention (variables prostředí, číslo), and /cross-validate reported every valid default name as violating its allowed characters. Both now work; an empty non_prod_prefix no longer makes every environment non-production.
  • Gateway failover keeps one active gateway per environment — When a failed primary gateway sent heartbeats again while its DR gateway was serving the environment, the health monitor reactivated the primary, so two gateways were active (the check looked for the wrong DR role); the primary now stays failed until you run failback, also when the DR gateway serves the environment through its supported environments. The gateway health endpoint now really queries the database, so a gateway whose database is down reports degraded instead of healthy. Routing now picks the gateway with the lowest failover_priority across single- and multi-environment gateways (a single-environment gateway used to win regardless). The DR failover runbook now states the real thresholds: a gateway without a heartbeat for 2 minutes stops receiving work after its fast retries (about 5 minutes) and is marked failed only after 7 days; to fail over sooner, deactivate the primary first.
  • Agent File Sync Keeps Backups Private and Edits Files Safely — Before a secret sync overwrote a config file, the agent saved its backup (.bak.<time>) readable by every local user, even when the file itself was private; backups are now created with owner-only access, and updated or restored files keep their original permissions and are replaced atomically via a temporary file. A sync into an empty YAML file or a JSON file containing null no longer crashes the agent. For .env files a secret value containing a line break is rejected instead of injecting another variable, and export KEY= lines are updated in place (the old secret no longer stays behind). On Windows, files inherit the permissions of their directory.
  • Discovery Results Can Be Adopted or Ignored and Consistency Warnings Resolved — POST /api/v1/discovery-results/{id}/adopt and /ignore failed with an error on every request, and POST /api/v1/consistency-warnings/{id}/resolve always returned 500. Both now work and record the signed-in user as the actor; the same fix makes project environment changes record who made them.
  • Post-Rotation Actions Run for Gateway Certificate Rotations and Record an Aborting Failure — A certificate rotation delegated to an environment gateway now runs the configuration's post-rotation actions (webhook, application reload, notification) after the workflow succeeds, as the local path already did; before, they never ran. During a certificate renewal the post-rotation actions run exactly once, in the renewal completion; the post-renewal deployment no longer runs them a second time. A secret post-rotation action with on_failure: abort that fails now stops the remaining actions and marks the execution failed verification / operator action required, stores post_actions_completed, marks the rotation config failed and opens a rotation-failure incident, as continue and warn already did; before, the rotation looked fully successful.
  • Rotation resume waits for a running rotation, a finished execution is not run again, a partial rollback stays visible — Resuming a failed rotation execution (POST /api/v1/rotation/executions/{id}/resume) now also takes the rotation lock of its configuration and is refused while another rotation of the same secret or certificate runs, so the two can no longer generate and write two different new values. A pre-built execution (Entra ID post-rotation delivery) runs only while it is pending; a second run of a completed or failed execution is refused instead of executing every step again (for Entra ID, another credential). When a step fails and some compensations fail too, the execution keeps the status partially_rolled_back instead of being overwritten with failed, so executions that need manual repair are visible and can be resumed; Rotation Problems lists them (and they can be acknowledged there), and the dashboard and compliance report count them as failed rotations.
  • ACME Server Serves Real Clients and Revocation Reaches the CA and Inventory — The built-in ACME server (/api/acme/*) no longer answers every POST with 403 CSRF token mismatch: its protocol routes are exempt from the browser double-submit check because every request is JWS-signed (new-nonce changes no state), so cert-manager, certbot and other clients without cookies are served. Certificates issued through an internal CA account are now stored with that CA (ca_id), so revoke-cert revokes them at the internal CA instead of failing with 500 certificate not found; migration 000260 fills in ca_id for certificates issued that way before. After the CA has revoked a certificate, the inventory certificate is marked revoked too (with revocation date and reason), not only the ACME order. When the order's CA account cannot be loaded, revoke-cert now fails with 500 serverInternal and leaves the order valid instead of reporting a revocation that never reached the CA.
  • Key Vault RBAC Access Shows Only the Signed-In User's Own Roles — GET /api/v1/azure/keyvaults/discover, POST /api/v1/azure/keyvaults/check-access and the rotation preflight's Key Vault write check counted every principal's role assignment on the vault as the signed-in user's, so a user with Key Vault Secrets User was reported as able to write and delete secrets whenever, for example, MazeVault's managed identity was Secrets Officer on the same vault. Effective access and the returned role_assignments now contain only the user's own assignments on the vault and the scopes above it (resource group, subscription, management group), including those granted through Entra groups; assignments on a single secret below the vault no longer count as vault-wide access, and other principals' assignments are no longer returned.
  • Service Identities Pass the Device Check — The zero-trust device check on /api/v1/secrets, /api/v1/rbac and /api/v2 refused every request of a service identity (it tried to record the identity as a user's device). A service identity now passes the device check; its role assignments decide what it may do, and the anomaly check still applies.
  • ACME: External Account Binding, Wildcards and Health Check — ACME accounts with eab_kid / eab_hmac_key now send the RFC 8555 §7.3.4 external account binding on registration, so CAs that require it (ZeroSSL, Sectigo, Google Trust Services) accept the account instead of answering externalAccountRequired; an incomplete or non-base64url binding fails before any request. Wildcard authorizations are recognised by the wildcard flag the CA sends (RFC 8555 §7.1.4), so the default auto policy validates them with DNS-01 on the base domain instead of failing with "no http-01 challenge found". The CA account status check now contacts the ACME directory on every check instead of reporting a cached result during an outage.
  • Terraform Handles Objects Deleted Outside Terraform the Same Way for Every Resource (check SDK callers) — When an object managed by Terraform was deleted in MazeVault, terraform plan failed with a "not found" error for most resources instead of planning to recreate it, and terraform destroy failed for most resources instead of finishing. Now every resource follows one rule: a 404 on refresh removes the resource from state (the next plan recreates it), a 404 on destroy counts as already deleted, and any other error (401, 403, 5xx, network) is reported and keeps the resource in state. mazevault_hsm_device and mazevault_shared_secret used to drop the resource from state on any read error, so a temporary backend failure planned a new HSM device; they now report the error. In the Go SDK, GetApprovalPolicy, GetDeployment, GetConfigTemplate, GetGroupMapping, GetRole, GetOrgProjectTemplate and GetSyncRule return a 404 *APIError (IsNotFoundError(err) is true) for an id that does not exist instead of (nil, nil); code that checked the result for nil must check IsNotFoundError(err) instead.
  • Terraform Plans Nothing After a Refresh When Optional Attributes Are Unset or Empty — When an optional attribute was left out of the configuration, Read stored the backend's empty value or its default ("", false, viewer, daemon, agent, pull, incremental, manual_resolution, continue), so the next terraform plan showed a change after every refresh. For mazevault_user (role, and email written with upper-case letters, which the backend stores in lower case), mazevault_user_role (project_id, environment) and mazevault_deployment (agent_mode, deployment_type, gateway_url) that change replaced the object on every run (a deployment replacement issues a new agent token). Read now keeps an unset attribute unset and a configured "" as "", a value the backend fills in by default no longer counts as a change, and "" and an unset value no longer force a replacement. The same applies to config_template, consistency_group, identity_provider, integration_group, keytab, rotation_config, secret_rotation_target and sync_rule, and to the optional fields of post-rotation actions (on_failure the backend sets to continue included). mazevault_user.role, mazevault_deployment.agent_mode/deployment_type and mazevault_sync_rule sync_direction/sync_mode/conflict_strategy now show the server's value when not configured; removing one of them from the configuration keeps the current value. Updating an integration group or consistency group without a description, or a rotation workflow without target_environment, no longer fails with "Provider produced inconsistent result after apply". The first apply of mazevault_ca and mazevault_rotation_config no longer fails with "unknown value after apply" when the server leaves account_id, key_algorithm, last_error or target_environment empty, and a last_error the backend cleared leaves the state. Post-rotation actions removed in MazeVault now also leave the state at refresh (rotation_config, rotation_workflow, certificate_rotation_config, entra_rotation_config), so terraform plan shows the drift and puts them back. Changing auto_update of an existing mazevault_deployment now fails at plan time with an explanation (the API cannot change it) instead of an inconsistent result after apply. After the upgrade, a state written by an older provider version can show a one-time in-place change from "", false (auto_update) or 0 (post-action order) to unset; it does not replace anything and does not come back after one apply.
  • Entra ID certificate credentials can be rotated — Adding and removing a certificate credential of an Entra app registration (certificate rotation, staged secondary, deleting the credential) called Microsoft Graph addKey/removeKey without the required proof of possession, so Graph rejected every call. MazeVault now signs the proof with the private key it stores for one of the app's currently valid certificates; when it holds none (e.g. a certificate uploaded outside MazeVault or an expired one), it updates the app's keyCredentials instead, which needs no permission beyond what MazeVault already requires (Application.ReadWrite.OwnedBy for apps it owns, or Application.ReadWrite.All). MazeVault serializes these updates per app, also across replicas, and checks the result afterwards. A change someone makes to the same app's certificates in the Entra portal at the same moment is not covered by this and can be lost, so do not edit an app's certificates in the portal while MazeVault rotates them. Graph errors also keep their HTTP status: 429/502/503/504 answers are retried by MazeVault's own backoff and 401/403/404 are reported as authentication, permission and not-found errors instead of unknown.
  • Entra ID credential lifecycle reports Microsoft Graph failures — When Graph refused to remove an old credential after its grace period (for example 403), MazeVault still marked it cleaned up and expired although it kept working in Entra, and never retried; it now stays in its grace period, the removal is retried on the next hourly run, and a critical entra.credential.cleanup_failed notification names the error category (auth, permission, …); it is sent on the first failure and then at most once per 24 hours per credential (migration 000249 adds entra_credentials.cleanup_failure_notified_at). The credential is marked cleaned up without a notification only when Graph returns the app and the key is no longer on it (for example removed in the portal); an app the integration can no longer see (deleted, ownership removed, another tenant) keeps the credential in its grace period, and the notification says so. The rotation dry run's entra_connectivity check now reads the app registration through Graph instead of only building a client, so a revoked integration secret or a missing permission fails the dry run (for gateway-delegated integrations the check is unchanged). A sibling credential stuck in rotating for over 30 minutes without an open execution is released by the next rotation again (the check used updated_at, which every sync refreshes; it now uses rotation_started_at, like the watchdog; credentials stuck since before migration 000175 are still left to the watchdog). The expiry monitor now sends the 7-day critical notice: to a credential first seen with under 7 days left, and once to a credential already warned at 30 days when it crosses 7 days (a monitor run missed at that moment skips it). The connection test of the managed_identity and workload_identity auth methods now requests a Graph token (within 5 seconds) instead of only building the credential, so it fails off Azure or with a wrong client ID.
  • Agent no longer crashes on certificates without a name and with a short serial number — A certificate without CN or SAN whose serial number has fewer than 8 digits made the agent panic in the sync loop when it built the file name; it now uses the whole serial.
  • Kubernetes Operator Overwrote Secrets It Did Not Own — When a Kubernetes Secret with the name of a MazeVaultSecret already existed, the operator wrote the MazeVault value into it even if it belonged to someone else. It now writes only into a Secret that carries its controller owner reference to that MazeVaultSecret (set on every Secret the operator has created, so Secrets created before the upgrade keep syncing); any other Secret, including one that only has the app.kubernetes.io/managed-by: mazevault-operator label, is left untouched and the resource gets the condition Synced=False with reason SecretConflict. The conflict is detected before the value is fetched from MazeVault. Rename or delete the conflicting Secret to resolve it; sync then resumes on the operator's next retry (retries back off up to about 17 minutes), or right away when the MazeVaultSecret is changed. When spec.secretKey changes, the Secret now holds only the new key; the value under the previous key is removed.
  • Kubernetes Operator: MAZEVAULT_URL, error handling and status — MAZEVAULT_URL may now be the plain server URL (https://mazevault.example.com); the operator adds /api/v1 itself (a URL that already ends in /api/v1 keeps working; before, only the full API URL worked and the bare URL ended in 404). A 404 or 403 answer of the backend (the backend answers 404 for any lookup error and 403 for a policy denial) sets Synced=False with reason FetchFailed and is retried at the refreshInterval instead of at the error backoff; other errors still back off. The backend's response body is no longer copied into the Synced condition message or the operator log, only the HTTP status. The status of MazeVaultSecret gains observedGeneration (also on the condition); a failed sync never moves lastSyncTime.
  • OCSP Responder Answered No Standard Client and No Backend Certificate — The standalone OCSP responder (mazevault-ocsp) indexed CAs by a hash of the whole public key structure instead of the RFC 6960 issuerKeyHash (hash of the public key bit string), so requests from standard clients (OpenSSL, browsers, Go x/crypto/ocsp) got unauthorized; and it read certificates.revocation_reason, which the backend fills with a reason name or an empty string, as a number, so every certificate created by the backend got internalError. It now matches standard requests, reports good/revoked from the revocation date, takes the RFC 5280 reason code from the certificate's revocation entry, no longer serves soft-deleted CAs, refuses to sign with a CA certificate outside its validity period (internalError, logged) and logs every CA row it cannot load.
  • Role Assignments Created Through the Go SDK or Terraform Were Global (review them) — The Go SDK (and so the Terraform resource mazevault_user_role, which uses it) sent project_id and environment at the top level of the role assignment request, while the backend reads them from the nested scope object, so every role assigned this way was granted globally instead of being limited to the project or environment. The SDK now sends the scope in the scope object (AssignRoleRequest.Scope, which replaces the top-level ProjectID and Environment fields: SDK users must adapt). Assignments created through the SDK or Terraform before this release are global: review them (Users → roles, or GET /api/v1/rbac/users/{id}/roles) and re-create the scoped ones.
  • Go SDK Sign-In and Token Refresh Did Not Work — Login and RefreshToken called /auth/login and /auth/refresh, while the backend serves /api/v1/auth/login and /api/v1/auth/refresh, so both ended with 404. The SDK client also had no cookie jar, so the refresh_token cookie set at sign-in was never sent back and RefreshToken could not succeed. Both now call the real routes, NewClient keeps the cookie (a client that replaces HTTPClient must set its own Jar to refresh), and LoginResponse.User.MustChangePasswd is read from must_change_password, so a forced password change is reported.
  • Go SDK Calls Only Routes the Backend Serves — ListProjectCSRs called /api/v1/projects/{id}/certificate-requests, which does not exist, and now calls /api/v1/projects/{id}/csr, so the Terraform data source mazevault_project_csrs works. The backend has no route to create a certificate template in a project or to delete an organization, so CreateProjectCertificateTemplate (now deprecated: create the template with CreateOrgCertificateTemplate and assign it with AssignCertificateTemplateToProject) and DeleteOrganization now fail fast: they send no request and return an error wrapping the new ErrNotSupported (check with errors.Is); terraform destroy of mazevault_organization recognizes it and removes the resource from state with a warning, the organization stays in MazeVault.
  • Go SDK Reads the Backend's List Responses — ListKeytabs failed with a JSON error (it expected a bare array, the backend answers the paginated {"data": [...], "total", "page", "limit"} envelope), and GetRotationResourceHistory and ListSyncRules failed the other way round (they expected {"executions": [...]} and {"rules": [...]}, the backend answers a bare array); GetSyncRule is built on ListSyncRules and failed with it. All three now decode what the backend sends, so the Terraform resource mazevault_sync_rule can be read, imported and refreshed again and the data source mazevault_rotation_resource_history returns the history. ListKeytabs and ListManagedTokens (and so the data source mazevault_tokens) now read every page instead of only the first one (25 keytabs, 50 tokens). ListAuditLogsResponse.Total held the length of the returned page, not a total; the audit-log endpoints return no total, so it is now always 0 and deprecated — page with limit/offset until a page holds fewer than limit entries. The unused ListRotationResourceHistoryResponse and ListSyncRulesResponse types are deprecated.
  • Go SDK Sends and Reads the Backend's Field Names (check SDK callers and the first Terraform plan) — Many Go SDK models used JSON names the backend does not know, so values were silently dropped or never filled. They now match the backend DTOs: approval policies (required_approvers, plus trigger_type, conditions, allowed_approver_roles/users, auto_expire_hours), config templates (template_type and a JSON-object definition instead of format/template/variables), keytabs (keytab_data and notes instead of keytab_base64/description, so ImportKeytab no longer fails with 400), certificate templates (cert_type, validity_days), group mappings (provider_id, group_external_id, group_display_name, source, …), rotation workflows (rotationIntervalDays/postActions read, post_actions sent), rotation executions and rotation history (error_message), shared-secret metadata (has_password, source_type) and the create response (link), project settings (id), project CA expiry (not_after), the renewal queue (scheduled_for, created_at), sync rules (key_transform object), CA accounts (auto_discover, auto_renew, renewal_lead_days, sync_interval_minutes) and managed tokens (metadata). UpdateRotationConfigRequest gains Environment (without it a manual trigger fails the preflight), UpdateRenewalPolicy can now switch auto_approve and key_reuse_enabled off (both are always sent), and GetRotationResourceHistoryWithLimit reads more than the backend's default 20 executions. Go code keeps compiling: fields without a backend counterpart are kept but deprecated, and most are no longer sent. Code that relied on the old (wrong) wire names, e.g. a hand-written fake server, must use the backend names. CreateRenewalPolicyRequest now always sends auto_approve and key_reuse_enabled: a partial UpdateRenewalPolicy that reuses the struct without setting them switches both off, so fill in the current values first. Likewise CreateSyncRuleRequest always sends key_transform: an UpdateSyncRule that reuses the struct without KeyTransform clears the rule's key transform. Terraform: mazevault_approval_policy deprecates environments and approvers_group_id (kept in state, not sent); mazevault_config_template sends format as template_type and content as definition (a JSON object, use jsonencode); mazevault_group_mapping sends group_name as group_external_id; mazevault_sync_rule sends key_transform_template as key_transform.template; the data sources mazevault_project_cas, mazevault_project_certificate_templates, mazevault_renewal_queue, mazevault_rotation_executions and mazevault_rotation_resource_history now return the CA expiry, template type and validity, queue time and failure reasons, and mazevault_rotation_resource_history gains an optional limit. Action required on upgrade: values that were silently dropped so far start to take effect on the first terraform apply — mazevault_approval_policy.required_approvals (the backend default was used until now), mazevault_sync_rule.key_transform_template (key names in the target are renamed by the template on the next sync; removing the attribute now clears the transform), mazevault_rotation_workflow.post_rotation_actions (the actions now run after each rotation) and mazevault_keytab.description (stored as the keytab notes). Review the plan before applying. mazevault_group_mapping has a new required provider_id (the backend rejects a mapping without it); add it to every mapping — setting it on an existing mapping does not re-create the mapping only when it equals the mapping's current provider (read from the backend); a different value re-creates it. mazevault_config_template validates format and that content is a JSON object at plan time.
  • Go SDK Escapes Query Values, Calls the Registered Collection Paths and Reports Real Consistency Status (check GetConsistencyStatus callers) — GetSecret and ListRotationResources inserted the key, kind and environment scope into the query unescaped, so a value with &, #, + or a space sent a different request (another key, a cut-off value); every query value is now escaped. Collection calls (/api/v1/secrets/, /projects/, /organizations/, /users/tokens/, /certificate-templates/) now use the path the backend registers, with the trailing slash, instead of relying on its redirect (two requests per call, and a proxy that turns the 307 into a 301 would change a POST into a GET). GetConsistencyStatus reported warning with MissingCount = number of groups for every project that had any consistency group; it now reports the worst last-check status of the groups (healthy, warning, error), one issue per unresolved warning (<group>: <key> in <environment>: <issue>) and MissingCount = warnings of type secret_missing. The Terraform data source mazevault_consistency_status returns the same values, so a project whose groups have no findings is now healthy.
  • Terraform: share_url Is Sensitive, Environment Adoption Respects the Plan, validity_period Is Validated (review terraform plan) — mazevault_shared_secret.share_url is now sensitive: the link gives access to the secret and was printed in plan output and CI logs; outputs that expose it must be marked sensitive = true (or use nonsensitive()). mazevault_environment adopts an existing environment with the same name or slug (the backend refuses the duplicate) only when the configured is_production and incident_auto_escalation match it; before, it took over the existing flags and reported success, so a production environment stayed production although the configuration said otherwise. The apply now fails and names the differing flags: import the environment or change the configuration. mazevault_certificate_template.validity_period must be a whole number of days, written as <n>d (e.g. 90d, now accepted) or in hours (e.g. 2160h); a value the provider could not parse (e.g. 90d before, 1y) used to be silently left out, so the template got the backend's default validity, and a duration that is not whole days (e.g. 36h) was truncated to whole days (1 day); both now fail at plan time (90d is accepted). mazevault_integration restores project_id and environment from the server on refresh.
  • Terraform Provider: Drift and Destroy Handling — mazevault_secret, mazevault_project, mazevault_certificate and mazevault_renewal_policy deleted outside Terraform are planned for re-creation instead of failing the refresh with 404. mazevault_rotation_config no longer fails with "unknown value after apply" (status, last_error, target_environment) and no longer shows a perpetual diff for an omitted notification_emails or schedule. Destroying a mazevault_certificate issued through an external CA account, which MazeVault cannot revoke, archives the record (with a warning) instead of failing.
  • Onboarding: Master Key Status Lost After Organization Setup — Generating the self-signed TLS certificate during organization setup saved the organization with a stale in-memory copy and cleared master_key_encrypted, so the bootstrap state reported the master key as not generated. The organization is reloaded before the TLS step.
  • License Changes Did Not Reach the Primary at Validation — The primary read the license server's /api/v1/validate answer under field names the server does not send (licenseType, expiresAt instead of license_type, expiration_date), so the validation at startup and then daily never updated the stored license. A license type changed on the license server (for example POC to Enterprise) or an extended term now reaches the primary at the next validation; restart the backend to apply it at once.
  • Project CSR approvals showed empty subjects and ignored the status filter — The CSR tab of a project now shows the requested common name, organization, organizational unit and country of each request (it read fields the API does not send, so the approver saw only the PEM). GET /api/v1/projects/{id}/csr now honours the optional status query parameter (for example ?status=pending); without it all non-archived requests are returned as before. Approve and Reject are offered only for pending requests.
  • DigiCert: issued certificates were never picked up, DNS validation was refused — The DigiCert CertCentral connector read the certificate ID of an order from a top-level certificate_id, while CertCentral returns it as certificate.id, so an asynchronously issued DigiCert order never became available for download and discovery found no DigiCert certificates. It now reads the nested ID. A certificate template with DCV method dns is now sent as dns-txt-token instead of dns-txt, which CertCentral rejected.
  • ADCS: web enrollment and CES (WCCE) enrollment work against real servers — Web enrollment posted the request to certrqxt.asp, which only shows the request form, so ADCS never processed it; it now posts to certfnsh.asp, and a request waiting for manager approval keeps its request ID ("Your Request Id is 77."), so the approval poller can pick the certificate up. CES (WCCE) enrollment now reads the issued certificate from RequestedSecurityToken/BinarySecurityToken and the CA request ID from RequestID as MS-WSTEP defines them, and polls a pending request with QueryTokenStatus instead of Renew; the response form accepted before still works. Username, password and template name are XML-escaped in the SOAP request, so a password with & or < no longer breaks enrollment, and the CSR is sent as DER, as MS-WSTEP requires (before, the PEM text was sent). The CA certificate and chain downloads accept the PEM form certsrv sends for Enc=b64.
  • Linux Agent Could Not Run on Hardened or RHEL 8 Hosts — Two independent defects stopped the Linux agent on customer hosts. (1) The release agent binary was built with cgo enabled, so it was dynamically linked against the build machine's glibc and failed on RHEL/Rocky/AlmaLinux 8 with /lib64/libc.so.6: version `GLIBC_2.34' not found; the agent is now built with CGO_ENABLED=0 -trimpath and is statically linked, running on any glibc. (2) The deployment install script placed the binary under /var/lib/mazevault/bin; on hosts that mount /var noexec (CIS/STIG baselines) systemd failed with status=203/EXEC in an endless restart loop. The installer and the rpm/deb package now install the binary to /opt/mazevault-agent/bin (owned by the agent user, listed in ReadWritePaths so self-update keeps working, SELinux bin_t). The installer was reworked for hardened hosts: set -euo pipefail, preflight (noexec check of the binary directory via findmnt, SELinux detection, required tools), no curl -k fallback any more (with a MazeVault CA the download uses it and falls back to the system trust store, never to insecure), the HTTP status and the server's error body are printed when the download fails (e.g. 503 agent binary downloads not configured), a curl failure after HTTP 200 (truncated transfer) counts as a failed download, and the signed binary is staged as mazevault-agent.new and executed as mazevault-agent (version) before it replaces the installed binary and before the unit is written, so a noexec mount, an SELinux denial or a glibc mismatch stops the installer with a clear message, leaves a previously installed agent untouched and never causes a restart loop, agent.yaml is written 0640, systemctl restart is used so a re-run updates a running agent, hosts installed with the previous layout are migrated (old binary removed, ExecStart drop-in overrides removed, other drop-ins kept), and exit codes are 2 preflight / 3 download or verification / 4 service not running (status and journal printed with tokens and UUIDs redacted). When the server has no CA configured, the installer no longer writes an empty ca.crt and the generated agent.yaml leaves server_ca_file empty, so the agent validates the server with the system trust store; the Windows installer likewise skips writing and importing an empty CA. The agent's self-update and the remote-uninstall runner now choose a directory that is writable and not on a noexec mount (binary directory, state directory, then the temp directory) instead of assuming /tmp is executable, and report the rejected directories when none qualifies. Remote uninstall recognises script-installed hosts (no rpm/deb package): the unit, drop-ins and the agent directories are removed directly instead of a failing rpm -e, and stopping the service, removing the unit file and removing /opt/mazevault-agent are required steps whose failure is reported to the server instead of a false success, /opt/mazevault-agent is removed only when the binary actually lives there, and the root requirement is explained in the error. New optional variable MAZEVAULT_CA_FILE (release kit, installers, check-config.sh, Helm values, documentation) names the CA embedded into install scripts; the Compose kit mounts CUSTOM_CERT_DIR into the backend as /custom-certs for it. Operators who worked around 203/EXEC with a drop-in pointing to /usr/local/bin only need to re-run the install script.
  • Compose Kit Did Not Start: Database Without TLS, Docs Without HTTPS — With the default MAZEVAULT_ENV=production the backend refuses a database connection without TLS, but the kit's bundled PostgreSQL ran without TLS (sslmode=disable), so the backend never became healthy; the docs container only listened on port 80 while the kit published and health-checked HTTPS. The kit's PostgreSQL now uses TLS with the certificate from the certs volume and the backend and OCSP responder connect with sslmode=require; the docs container serves HTTPS on the kit's docs port (DOCS_PORT, default 8088) when a certificate is mounted and plain HTTP on port 80 otherwise (Helm unchanged). Existing Compose installations pick this up when the new kit's installer is run, which installs its docker-compose.yml and keeps the existing .env and data volumes (upgrade-mazevault.sh alone does not replace docker-compose.yml).
  • CRL Download in DER Returned an Empty Body — GET /api/v1/ca/{id}/crl (default format=der) answered 200 with DER headers but no content; only format=pem returned the CRL. The endpoint now returns the DER-encoded CRL, decoded from the stored PEM.
  • CRL Numbers Repeated and the Served CRL Could Be Stale — CRLs regenerated after a revocation took their number from a separate counter, so CRL numbers of a CA repeated and went backwards (RFC 5280 §5.2.3), and GET /api/v1/ca/{id}/crl could keep serving an older CRL after a newer one was generated. All CRLs of a CA now share one increasing number, continuing after the highest number issued so far; it is allocated atomically in the database, so concurrent regenerations on several replicas never reuse a number.
  • Built-in Roles Lost Permissions Granted by Upgrades — On every start the backend reset the permissions of built-in roles to an older hardcoded list, so permissions added by upgrade migrations disappeared after the first restart: report:read/report:write, keytab:read (User, Secret Manager), dashboard:write, agent:admin, audit:read, config:read and project:write (Certificate and Secret Manager), and token:read. Customer changes to built-in roles were overwritten the same way. The backend now only creates missing roles and never changes existing ones; migration 000207_restore_system_role_permissions restores the intended permissions. It also grants token:read/token:write/token:delete to Project Admin and Secret Manager (migration 000193 had granted them to roles that no longer exist). The migration only adds permissions and never removes any, so existing role assignments and customer edits stay in place.
  • Agent Keystore Deployment Sent a Bundle the Agent Could Not Read — The orchestrated agent_keystore_sync step handed the agent a single exported_data blob, while the agent's CertificateSyncBundle expects certificate_pem, private_key_pem and chain_pem; every JKS / PKCS#12 / Windows-store rotation therefore failed on the agent with failed to decode certificate PEM, and the CA chain never reached the trust store. All deployment consumers (Kubernetes secret, Key Vault, agent keystore, agent file, Oracle wallet) now split the export with one shared helper — first certificate = leaf, remaining certificates = issuer chain, key separate — that also accepts the base64 form produced by a gateway round-trip, and the keystore step sends exactly the fields the agent deserializes (including post_install_command, which was nested in the wrong place before). This is a backend/gateway-only fix compatible with already-deployed agents.
  • agent_command Rotation Steps Timed Out Against Every Agent — The step forwarded an HTTP request through a proxy message type the agent never implemented, so service restarts and other command steps waited 30 s and failed. The step now uses the agent's agent_command request (allowlist-enforced) and honors timeout_seconds.
  • Agent File, WebLogic and Oracle Wallet Targets Could Not Execute — agent_file_sync, agent_file_backup and weblogic_restart were generated by the workflow builder but never registered (unknown step type), and the registered weblogic_deploy / oracle_wallet_deploy steps returned executed without contacting any host. These targets now run through real, registered steps (see New Features); the folded-in step types weblogic_restart and agent_file_backup are refused with a hint to rebuild the deployment target instead of reporting false success.
  • Web Server and Load Balancer Targets Were Skipped Silently — The workflow builder had no case for web_server and load_balancer targets, so a rotation with such a target reported success without deploying anything. They are now refused when saved and fail the workflow build with an explicit reason when already present (see Deployment Targets Are Validated When Saved).
  • Database Wallet Targets Could Be Created Without an Agent — The UI marked Database Wallet as not requiring an agent, while the deployment runs orapki on the database host through the agent; such targets could never execute. The UI and the API now require an agent for this target type, and target updates sent as JSON documents are serialized explicitly on every database driver (GORM does not run field serializers for map-based updates; the Postgres driver happened to encode the struct, SQLite rejected it).
  • Windows windows_root Target Imported a Private Key Into the Trusted Root Store — The Windows keystore installer built a PFX with the private key for every store and used the raw keystore type (windows_root) as the store path, which is not a valid Windows store name. windows_root and windows_ca targets now install public certificates only (leaf into the designated store, chain classified into Root / CA), windows_my receives the leaf + key PFX without the chain, and keystore types are mapped to the real My / Root / CA store names. The backend side matches this: the agent_keystore_sync step no longer requires a private key for windows_root / windows_ca (a CA certificate without a key can now be deployed to a trust store) and never includes private_key_pem in the bundle for those stores even when the export carries a key; JKS / PKCS#12 / windows_my still fail fast without a key. The UI value Windows Cert Store (keystore_type: windows) never matched anything the agent accepts; the editor now offers Windows — Personal (My), Trusted Root CAs and Intermediate CAs explicitly (no path, alias or store password for Windows stores), saved targets with the legacy windows value are canonicalized to windows_my at execution time, and agent_keystore targets are validated at save (JKS / PKCS#12 need keystore_path; unknown store types are refused).
  • Legacy Records Could Export the Wrong Certificate as the Leaf — Certificates imported before leaf_certificate_pem existed keep raw_certificate_pem exactly as uploaded, which may be a "root, leaf" bundle; the new chain resolver initially read that raw field before the leaf-first public_chain_pem, so such a record would have exported, displayed and deployed the CA certificate as the leaf (and reported an empty, complete chain). The leaf is now selected by the record's stored SHA-256 thumbprint wherever it sits in the stored material; without a thumbprint the pre-existing order (dedicated leaf field → first block of public_chain_pem → raw upload → DER) applies. Covered by a regression test with root-first raw bundles for both end-entity and CA records.
  • Discovery Merged Multiple End-Entity Certificates From One File — The agent reports one record per end-entity certificate found in a PEM file but sends the whole file as the body; the backend normalized the bundle with a "first non-CA block" heuristic, so a file holding two end-entity certificates (old + renewed, server + client) collapsed both reports into the first certificate and the second was never recorded. The reported fingerprint now selects the leaf inside the bundle, sibling end-entity certificates are excluded from that leaf's chain, and the heuristic is used only when no block matches.
  • Windows Personal Store Deployment Needed a Keystore Password That Does Not Exist — The Windows My store has no keystore password; the agent only needs one to wrap the temporary PFX it imports and deletes on the host, and PowerShell refuses an empty value. Targets of type windows_my without a configured password therefore failed at install time. The agent_keystore_sync step now generates a one-time random PFX password when none is configured (it is never logged or persisted, and an operator-provided password is still honored), so no meaningless secret has to be provisioned for Windows stores. On the agent, the PFX password is handed to PowerShell through the process environment instead of being interpolated into the script, so a password containing quotes or $ can neither break the import nor be echoed back in an error. When the deployment runs through a gateway, the one-time password is derived from the step's idempotency key, so a retried attempt presents the identical bundle and the agent replays the completed install instead of rejecting it as a different operation. Windows store names are resolved by one shared rule on the agent: My and any plain LocalMachine store name discovered or configured on the host (for example WebHosting, the IIS default on current Windows Server) are identity stores that receive the PFX; Root, CA, AuthRoot, TrustedPublisher and Disallowed are trust-anchor stores that receive public certificates only — a private key is never imported into them, including through the renewal and legacy metadata paths; a bundle that carries a private key for a trust-anchor store is refused with an explicit error instead of being installed half-way (the renewed key would otherwise be lost silently). Names that could alter a PowerShell script are refused. Works with already-deployed agents (the backend change alone fixes the install).
  • Inventory Issuer Candidates Were Not Identified by Thumbprint — The chain resolver's issuer lookup did not load thumbprint_sha256 for candidate CA records, so the thumbprint-based leaf selection introduced above did not apply to them and a legacy intermediate stored root-first was never offered as an issuer (the chain stayed incomplete — safe, but the completion did not happen). The column is now loaded and a regression test covers a root-first intermediate candidate.
  • Private Key Import Accepted Any Key for a Certificate Without a Body — POST /discovered-certificates/{id}/import-key skipped the key/certificate match when the managed certificate had no stored body (the shape every adoption produced before this release), so a mismatched key could be stored silently and only fail at deployment. The import now rejects such a request with an explicit error until the body is present.
  • Attaching a Key to a Body-less Managed Certificate Left It Without a Body — The import update path attached the key but did not store the leaf PEM / DER on records that had none; it now attaches the body, keeps a richer stored chain, and completes a missing chain from the inventory.
  • Certificate Import Took Identity From the First PEM Block — Bundles ordered root, intermediate, leaf (as emitted by some tooling) were recorded with the root's common name, serial and thumbprint while the stored body was the leaf. Identity and metadata now derive from the normalized leaf regardless of block order.
  • Multi-File Certificate Import Dropped Separate Key Files — Uploading a certificate file together with a separate .key file silently discarded the key (the UI claimed it had been paired during preview) and imported the certificate alone, so re-importing a managed certificate to add its key always failed with "already exists". The key is now paired on the backend and imported with its certificate.
  • Secret Rotation No Longer Fails on the Backup Foreign Key — Every secret rotation aborted in the background saga with insert or update on table "rotation_backups" violates foreign key constraint "rotation_backups_execution_id_fkey". The pre-rotation backup was created before the rotation execution row existed and never set execution_id, so the non-null foreign key rejected the insert and no rotation could complete. The backup is now created inside the execution (after the runtime probe, before the first mutating step) with a valid execution_id, and organization resolution is validated up front. The regression was invisible because the SQLite test harness does not enforce foreign keys and the constraint exists only in the SQL migration; a foreign-key-enforced regression test now covers the real insert path.
  • Rotation Template Migrations Handle the New Policy Fields Correctly — The rotation template persistence path and its migrations were corrected so the newly added policy fields (lead_time_days, grace_period_days, secret_custody_mode) round-trip consistently between the API, SDK, provider, and the stored catalog.
  • Rotation Template Update No Longer Panics on Partial Edits — The rotation template create/update request decoder dereferenced typed-nil pointers, so any request that omitted an optional field — the common case for a single-field edit such as changing only the interval — panicked and returned HTTP 500. The decoder is now presence-aware: omitted fields are skipped, an explicit 0 or false is preserved as an intentional value, and project_id can be set, changed, or cleared (with an explicit null) without affecting other fields.
  • Retired Agents Can Re-Enroll With an Authorized Replacement Token — Re-registration for an unregistered or revoked agent was rejected outright, so a replaced host could not come back even with the single-use token issued by reregister. Re-enrollment of a retired agent is now allowed only with a purpose-issued replacement token — distinguished by a new purpose discriminator on deployment tokens — while stale deployment tokens and recovery tokens still cannot resurrect it; on success the agent's retirement markers are cleared and the single-use token is consumed.
  • Remote Uninstall Fails Cleanly and Reports Runner Startup Failures — Remote uninstall now rejects a host with no supported package manager (apt-get/rpm) up front as unsupported, instead of reporting success after best-effort cleanup while the package remained installed. If the detached uninstall runner fails to spawn, the agent now reports that terminal failure back over the health channel — reconciling the already-accepted operation — instead of only logging it locally.
  • Keytab Rotation Fails Closed on Agent-Status Lookup Errors — A database error while checking a keytab deployment target's agent status is no longer treated as "inactive" (which silently skipped the deploy while still reporting the rotation completed). The rotation now fails closed on a lookup error and only skips a confirmed-inactive agent.
  • Rotation Backup Acknowledge Restricted to the Recovery-Required State — POST /rotation/backups/{id}/acknowledge now transitions only a backup that is actually in recovery_required state, returning 404 for an unknown id and 409 for any other lifecycle state, instead of silently mutating a backup in any state.
  • Terraform mazevault_deployment Import Restores the Organization — The deployment resource Read now repopulates organization_id from the server, so an imported deployment can be destroyed; previously the delete call sent an empty organization and was rejected with 400.
  • Helm Customer Chart Uses Redis Instead of Silently Falling Back to Memory — The customer Helm chart defaulted externalRedis.connectionQuery to abortConnect=false, a StackExchange.Redis (.NET) option that the Go backend's Redis client rejects (redis: unexpected option), so the backend fell back to the in-memory cache, gateway state could not use Redis, and the OCSP responder crash-looped on startup. The default is now empty, and the chart now refuses to render when connectionQuery contains abortConnect — customers who set it in their own values must remove it. The backend and OCSP responder also no longer log the Redis password when a Redis URL fails to parse or connect: URLs in connection errors are redacted and URL parse errors no longer echo the raw URL.
  • Certificate Note Rejected Below the Displayed 256-Character Limit — Saving a certificate Note failed for text well under the advertised 256 characters whenever it contained non-ASCII characters (for example Czech diacritics), and the detail view gave no feedback at all. The backend enforced the limit in UTF-8 bytes while the UI counter, the request validator, and the VARCHAR(256) column all count characters, so a 2-byte character such as ř consumed two units of the quota (≈240 characters was the effective ceiling for typical Czech text). The same byte-based check affected certificate tags (64) and the documentation URL (500). All three limits are now enforced in Unicode characters on every layer, the Note editor counts and truncates by code points (an emoji counts as one character), and the full 256 characters can be saved regardless of script. In addition, the Tags / Documentation / Note section no longer swallows API errors: validation and permission failures are shown inline next to the field, the editor keeps the draft open for correction, and in-flight saves cannot be submitted twice. Certificate import now applies the same tag normalization (trim, de-duplicate, 64-character / 50-tag limits) as the detail editor. The PUT /certificates/{id} request body (UpdateCertificateRequest) is now described in the OpenAPI specification. No schema change is required.
  • Managed Tokens Could Not Be Saved in Orchestrator Mode With Azure Key Vault — Creating or renewing a managed token (POST /tokens, POST /tokens/{id}/renew) in Orchestrator Mode failed with 500 failed to offload token value to external provider whenever the offload target was Azure Key Vault. The generic key-offload path named every value it had no certificate metadata for mazevault-keys/key-<unix-timestamp> — a path-style identifier designed for HashiCorp Vault that Key Vault rejects because secret names may only contain [0-9a-zA-Z-] (400 BadParameter: Method PUT does not allow operation 'key-…'). The same identifier was used for keytabs and for private keys imported through discovery (mazevault-keys/<cn>-<serial>, with dots from the CN), so those offloads failed on Azure as well. The identifier is now generated per provider: Key Vault receives a flat, readable, collision-free name (mazevault-token-<token-name>-<id>, mazevault-keytab-<principal>-<id>, mazevault-<cn>-<serial>) that is validated against the Key Vault contract before any request is sent, while HashiCorp Vault and AWS Secrets Manager keep the historical mazevault-keys/… layout unchanged. The token value itself is never altered — it is base64-encoded for transport exactly as before and round-trips byte-for-byte (covered by a test with shell metacharacters, whitespace and Unicode). Key Vault's hard per-secret limit (25 600 bytes of the encoded payload, ≈19 200 raw bytes) is now checked up front and reported as a 400 with the exact limit and the size of the submitted value, instead of a generic 500 from Azure; an explicit secret name that violates the Key Vault pattern is also refused as a 400 rather than being rewritten silently. Several latent defects in the same path were fixed together: the token service passed the tenant under the wrong metadata key (org_id instead of organization_id), so the offload integration was chosen system-wide — the first enabled secret-manager integration of any organization — and token reveal / delete resolved the integration without any tenant scope; integration lookup is now scoped project → organization and never widens to another tenant's vault (an explicitly selected integration_id that is missing or disabled is rejected instead of being replaced); token renewal deleted the previous external value before the new one was written, so a failed offload left the token without any value — the order is now write → persist → retire, a renewal that fails leaves the previous value intact, and a metadata insert that fails removes the value it had just offloaded; a stored Azure reference carrying a version segment would have created a new secret named after the version GUID on update — the bare secret name is now extracted correctly; the asynchronous secret-offload worker queried a non-existent is_active column on project_integrations (enabled is the real flag); key-custody events for tokens and keytabs now reference the owning record, key type and acting user instead of a random UUID; and a certificate serial shorter than eight characters no longer panics the name generator. No database migration, API or configuration change is required.
  • Windows Agent Installed From the Generated Script Failed to Run as a Service — The install script ended with an unknown install command and the agent could not run as a Windows service. The agent now runs as the service MazeVaultAgent (automatic start, automatic restart on failure) from C:\Program Files\MazeVault Agent\, with data and logs kept in C:\ProgramData\MazeVault\; the agent gained the install and uninstall subcommands. Re-running the script upgrades in place and migrates the earlier layout; it refuses on a host with the MSI package. C:\ProgramData\MazeVault\ (bootstrap token, agent private key, pre-seed file) is now readable only by SYSTEM and Administrators; earlier installs left it readable by every local user, so re-run the script on existing Windows hosts.
  • MongoDB Credential Check Accepted Any Password With a Connection URI — For a MongoDB integration configured with a connection URI, verifying a rotated user's credentials authenticated with the user from the URI (usually the admin) instead of the rotated user, so a wrong new password passed and an old one was never rejected. The check now authenticates as the rotated user against the configured auth database (default admin), keeping the URI's hosts and options. Host/port connections also failed to connect at all (must have a / before the query ?); the built URI is fixed.
  • Oracle Credential Check Accepted Any Password With a Connection String — The same defect affected Oracle integrations configured with connection_string: the credential check connected as the user in the connection string instead of the rotated user. It now replaces the connection string's user and password with the rotated user's and keeps the rest of the string.
  • MSSQL Credential Check Ignored the Rotated User in Windows and Azure AD Modes — With auth_mode windows, verifying a rotated user's credentials signed in with the backend's own Windows identity and ignored the user and password; with azure_ad it tried an Azure AD sign-in for a database user that has a password, so a correct password could be rejected. The check now always uses SQL authentication with the rotated user, which is how rotated users are created; the admin connection keeps its configured mode.
  • CLI Exited 0 on Errors and Ignored --profile and --config — Every mazevault command printed its error and still exited with code 0, so scripts and CI could not detect a failed call (for example mazevault secrets get against a server answering 500). Commands now exit with code 1 on any error: missing or unknown flags and commands (message on stdout, as before) and failed API calls, configuration or keychain errors (Error: … on stderr). --profile/-p was never read, so the CLI always talked to the server of current_profile; it now selects the profile's server and its stored tokens for that run. --config/-c was overridden by ~/.mazevault/config.yaml; the given file is now read (as YAML). Check scripts before upgrading: a --profile that is not in the configuration now fails with exit code 1 (profile '<name>' not found) instead of silently using current_profile, and a --config file that does not exist fails instead of falling back to the defaults. Stored login tokens are now bound to the server that issued them and are never sent to another server (before, --config with a profile of the same name pointing elsewhere sent that server the token); tokens stored by an older CLI do not record their server, so run mazevault auth login once after upgrading.
  • CLI secrets get, list and delete, wrong password and expired sessions — mazevault secrets get, secrets list and secrets delete called routes the server does not have and never worked. get now reads the secret from GET /api/v1/secrets?project_id&key&environment, list from GET /api/v1/projects/{id}/secrets?environment=…, and delete finds the secret's id in that list (exact key and environment; no match or several matches delete nothing) and calls DELETE /api/v1/secrets/{id}. The JSON/YAML output of secrets list names the change time updated_at (was last_modified). A wrong password on mazevault auth login (also LDAP) now reports login failed: 401 Unauthorized instead of unauthorized: please login again. When the stored refresh token had expired, the CLI crashed with a stack overflow; it now stops with session expired, please login again. A refreshed session stores the new expiry and a rotated refresh token; a login or refresh response without expires_in no longer marks the token as already expired. The CLI still cannot refresh a session (the server reads the refresh token only from its cookie), so log in again when the session expires.
  • OCI Vault Integration Could Not Create Secrets and Listed Deleted Ones — Creating a secret in OCI Vault (for example from a sync rule) always failed, because the request did not name the master encryption key that OCI requires. The OCI Vault integration config now has key_id, the OCID of the vault's master encryption key; set it on every OCI Vault integration that should create secrets (without it, creating a secret fails with a clear error and nothing is sent to OCI). Reading, listing, updating and deleting secrets do not need it. Listing and name lookup now return only ACTIVE secrets, so a secret deleted through MazeVault (30 days in PENDING_DELETION in OCI) no longer appears in the list or the external-secret import, and a name resolves to the active secret rather than a deleted one with the same name.
  • Database reconcilers: MongoDB session drain, PostgreSQL passwords, Azure SQL sign-in and MSSQL options — Draining the sessions of one MongoDB user ended every operation in the cluster, of all users; it now ends only operations run as that user in its auth database (auth_database, default admin). The PostgreSQL reconciler could not connect when the user name or password contained URL characters such as #, / or ?; every value is now URL-encoded. The MSSQL reconciler signed in to Azure SQL with auth_mode: azure_ad as a SQL login and failed; it now uses Microsoft Entra authentication: with a token (service principal, managed identity, DefaultAzureCredential; with use_msi a client_id now selects a user-assigned managed identity, before the system-assigned one was always used) and, in password mode, through the public client application in client_id, which that mode now requires. The MSSQL option keep_alive: true prevented every connection; keep_alive is now the keep-alive period in seconds (true keeps the driver default of 30 s), and connection_timeout, login_timeout (used when connection_timeout is not set) and keep_alive saved from the web interface are no longer ignored. keep_alive_interval has no effect.
  • PostgreSQL database rotation rolls back and reconciles correctly — A failed rotation of a PostgreSQL integration configured with host, port and database (the form the UI creates) now restores the previous password; the rollback used to connect only through a connection string. Rolling back a sqluser:<role>:<user> rotation (PostgreSQL and Oracle) restores the password of that user instead of the integration's username; rolling back ensure changes no password and reports that role changes must be reverted manually, and a missing host is an error instead of a connection to localhost. The ensure operation drops an undesired role that the integration admin created (PostgreSQL 16+ records the creator as a member, so such roles were kept), and malformed desired_json, undesired_json, grants_json or aad_members_json is rejected with invalid <field> by the PostgreSQL, Oracle and MongoDB connectors (as MSSQL already did) instead of reporting reconciled without doing anything.
  • PKCS#11 HSM devices stay usable after re-instantiation and list their keys — A second PKCS#11 HSM device on the same module, or the same device after an edit, a failed health probe or the 5-minute provider refresh, failed with CKR_CRYPTOKI_ALREADY_INITIALIZED and stayed unreachable until the backend restarted; the same happened to a CA configured for a PKCS#11 HSM after its first use. The module is now initialized once per backend process and shared, and a replaced device provider closes its HSM session. A CA on a PKCS#11 HSM keeps one HSM session while its HSM configuration is unchanged (before, issuance, renewal and scheduled runs opened a new session each time and never closed it). While a device is logged in to a token, another device on that token is accepted only with the same PIN: a wrong or missing PIN reports the device unreachable, and a changed PIN takes effect once all sessions on the token are closed (for example after a backend restart). Concurrent operations on one PKCS#11 device no longer crash the backend. The device key list and key details now read the key size from the modulus, so keys on a standard token (e.g. SoftHSM2) are listed instead of being skipped.
  • Azure Key Vault watch reports new secret versions — Watching an Azure Key Vault integration for external changes reported only created and deleted secrets; a new version of an existing secret (a value changed directly in Key Vault) was never reported, so no drift warning was raised. The watcher now uses the last-updated time from the secret list and, only for a changed secret, lists its versions (no values are read, the list permission is enough) and reports the update when the current version changed; a tag-only edit is not reported. A key reference that is not a secret id of the configured vault is now rejected. An end-to-end-encrypted value that cannot be decrypted is now an error instead of being returned as the encrypted envelope, and the remote identifier recorded for certificate and post-rotation syncs to Key Vault no longer contains a double slash when vault_url ends with /.
  • Integration wizard lists agents, applies organization defaults and shows backend errors — In the integration wizard, "Key Vault is behind Private Endpoint" now lists the project's agents (it called an endpoint that does not exist and showed "Failed to load agents") and lets you pick an active agent (only the non-existent status online was selectable before). The database step applies the organization's security defaults only after the organization environments are loaded, so an environment such as prod marked as production gets the production defaults. Errors from saving an integration, testing a connection, previewing Entra applications or saving a secret deployment target show the message returned by the backend instead of "Request failed with status code 400".
  • Agent discovery finds new certificates on every scan and shares its password cache safely — After its first filesystem scan the agent treated the scanned roots as done and skipped them in every later hourly scan until it was restarted, so certificates added in the meantime were never discovered; every scan now walks all roots again. A scan interrupted by a kill or crash still resumes after the restart; a cancelled scan starts its walk again, keeping its per-file progress (unchanged files are not parsed again). Keystore passwords approved in MazeVault and those found during a scan are now kept in a cache that is safe for concurrent use, so an approved password arriving while a scan or a renewal reads the cache no longer crashes the agent with concurrent map writes. The search for password hints across the local disks now runs at most once per scan for the filesystem and the JKS scanner instead of once per keystore, and the look-up of a matching private key next to a certificate skips files larger than 1 MiB. A key_vault_secret_id read from a JSON file next to a keystore is now fetched only from an https://….vault.azure.net URL; the host's managed-identity token is never sent to any other host.
  • Agent-Local Certificate Renewal Installs Only a Matching Certificate, Renews Discovered PKCS#12 Keystores and Seals Its Pending State — A PKCS#12 renewal with reuse_key now completes; before, the key read from the keystore was mislabelled and the install always failed. The agent refuses an issued certificate that does not match the private key it prepared, so a wrong certificate no longer replaces a working file or keystore. A PKCS#12 or JKS keystore found by the file scanner (type filesystem with keystore_type) is now offered for agent-local renewal by the backend and accepted by the agent. The pending renewal under <state_dir>/pending-renewals/ is now encrypted with a key derived from the agent's mTLS key (client_key_file) instead of holding the private key and keystore password in clear; an agent without an mTLS key keeps the pending renewal in memory only, so after a restart between prepare and install the install answers "renewal operation not found or expired" and the renewal has to be started again. A pending file written in clear by an older agent is sealed again when it is first read, or, without an mTLS key, moved to memory and deleted. The renewal task payload is unchanged.
  • Entra sync conflicts can be listed and resolved — GET /api/v1/entra/{integrationId}/sync-conflicts and POST /api/v1/entra/sync-conflicts/{id}/resolve did not work: every pending conflict was refused as already resolved (500). A conflict now resolves once. Resolving an unknown conflict answers 404, and a conflict that is already resolved answers 409, also when two resolves race. The resolution request values stay local_wins, remote_wins and manual. They are stored as use_mazevault, use_entra and use_merged. The conflict JSON now uses the stored fields: field_name, mazevault_value, entra_value, status (pending, resolved, ignored), resolution, resolved_value and resolution_notes. It replaces field, local_value and remote_value.
  • Several zero-knowledge secrets per environment; deleted and archived secret names can be reused — Creating a second zero-knowledge (v2) secret in the same project and environment failed with a server error, and a secret name that had been deleted or archived could not be created again. Uniqueness now applies only to live secrets: one live secret per name, and one live zero-knowledge secret per blind index, in a project environment (database migration 000276). Restoring an archived secret whose name (or blind index) has since been used again now answers 409 Conflict and leaves it archived; archive or delete the newer secret first. The migration refuses to run if live secrets already share a name or blind index in one environment, and lists them; archive or delete the extra ones and restart. Rolling the migration back refuses while rows exist that the old index would reject (for example several zero-knowledge secrets in one environment).
  • Switches that default to on keep an explicit off — Saving false for a flag whose default is on used to store true: CA account auto_renew and auto_discover (an SSLmarket account set to manual no longer places paid renewal orders), the template flags auto-archive, incident notifications and auto-renewal, project environment exists and compare_enabled, rotation resource and rotation target enabled (a target created disabled is no longer deployed to), and custody events of failed key actions (now counted as failures). Records written before the upgrade are not changed: review CA accounts you switched to manual earlier, because they may still have auto-renew on.
  • ECDH key exchange works, integrations backing secrets can be deleted, Entra promotions appear in rotation history — Migration 000274 creates the session_keys table, which no migration created, so every key exchange failed; it also widens rotation_executions.config_type to 50 characters, so promoting a staged Entra secondary to primary is now recorded in rotation history and reports (the 22-character type did not fit and the failure was ignored; the record is now written in the promotion transaction, so a failure rolls the promotion back). Migration 000275 recreates the secrets.external_integration_id foreign key with ON DELETE SET NULL: deleting an integration that secrets use as their source (typically names_only secrets in orchestrator mode) failed on the foreign key; it now succeeds, those secrets are reported as orphaned (and marked missing in orchestrator mode) and lose the reference. A reference to an integration that no longer exists (possible only where the foreign key was never created) is logged and set to NULL by the migration.
  • Vault and internal CA issue certificates within the template and CA validity — A Vault CA account without mount_path now uses the default pki mount also for certificate status and listing (before, status checks went to a wrong path and reported certificates as revoked), and Vault signing uses the template's validity instead of the account's default_ttl. The internal CA caps a certificate's validity at the CA's own expiry, refuses to sign once its own certificate has expired, rejects signing without a certificate template with an error instead of crashing, and returns the chain up to the root for a subordinate CA. Re-created Vault providers no longer leave token-renewal routines running.
  • Server TLS certificate: mismatched key pair is rejected, replacements are audited — Uploading a system certificate (POST /api/v1/system/certificate) with a private key of another certificate, or with an unreadable certificate, now answers 400 instead of 500, and every successful replacement writes the audit event SYSTEM_CERTIFICATE_REPLACED (who, when, subject, serial and expiry; never the key). The self-signed certificate created during setup covers the host of the organization's web URL (before, the whole URL was put in the certificate and matched no host) and no longer erases the master key generated in the same bootstrap step.
  • Certificate exports are audited; template auto-archive runs; CA status sync uses the account settings — Every certificate export (GET /api/v1/certificates/{id}/export, including exports with the private key) now writes the audit event CERTIFICATE_EXPORT with the user, format, whether the key was included and the result (failures included, also when the private key cannot be read or retrieved); the password and key are never recorded. Expired certificates are archived hourly when their template has "auto archive expired" set, after "archive after days" (the option had no effect before); each automatic archive writes the audit event ARCHIVE_CERTIFICATE, and restoring such a certificate makes it usable again. The periodic status sync with external CAs uses the CA account's settings (for example an SSLmarket sandbox or custom URL) instead of the provider's default endpoint.
  • Renewal cancel keeps finished renewals; PFX import keeps a single CA certificate; Cloudflare DCV cleanup reports failures — POST /api/v1/renewal-queue/{id}/cancel no longer rewrites a completed renewal to cancelled (it answers 409) and answers 404 for an unknown id instead of success. Repeating a renewal hand-off repair that already succeeded no longer fails. Importing a PFX through the OpenSSL fallback keeps the chain when it contains a single CA certificate (before, that certificate was dropped). Removing a DNS-01 _acme-challenge record from Cloudflare reports an error when Cloudflare refuses it (for example a token without DNS edit permission) instead of silently leaving the record behind.
  • Notification and monitoring providers report real delivery results — A Dynatrace event or metric rejected by the API (for example 401 with an expired token) and an SMS rejected by SMSEagle (HTTP 200 with an error text such as Invalid login or password) are now errors in the log instead of counting as delivered. Microsoft Teams webhooks created through Power Automate Workflows answer 202, which is now a successful delivery. Zabbix alerts were dropped and metrics were sent in the zabbix_sender format over HTTP: both now go through the Zabbix API method history.push (Zabbix 7.0 or newer) at ZABBIX_URL (the frontend URL; /api_jsonrpc.php is appended when missing), authenticated with an API token in the new variable ZABBIX_TOKEN (keep it in the secret store); alerts go to the trapper item mazevault.alert (type text) and metrics to trapper items named by their key, all on the host ZABBIX_HOSTNAME, so create those items (if an item restricts "Allowed hosts", include the Zabbix frontend's address). The unused mock webhook package is removed.
  • E-mail notifications are well-formed and the weekly report skips inactive channels — SMTP messages now carry the From and Date headers (RFC 5322); some mail servers rejected or spam-filed messages without them. A subject is RFC 2047 encoded when it contains line breaks or non-ASCII characters, so a resource name such as a secret key or a discovered certificate's common name can no longer add headers (for example Bcc) to an alert e-mail; an address containing a line break is refused. HTML e-mails (weekly report, share links) use correct MIME-Version / Content-Type headers. The weekly report is sent only to report-enabled channels whose integration is active.
  • Report exports: CSV cells cannot run as formulas, the Terraform export stays valid — A CSV export cell that starts with =, +, -, @, a tab or a carriage return (for example a certificate common name =HYPERLINK(…) or a project name) now gets a leading ', so Excel or LibreOffice shows it as text instead of evaluating it. The Terraform export of a project or organization gives a secret key that exists in several environments a separate resource per environment (db_password, db_password_prod) instead of a duplicate resource address, escapes quotes, backslashes, line breaks and ${ / %{ in names, keys and environments, and turns any character other than letters, digits and _ in a resource label into _.
  • Reconnecting agents stay connected; rescans keep the operator's decision on discovered config files — When an agent reconnected, closing its previous WebSocket removed the new connection as well, so the agent showed as not connected and certificate sync, secret sync and agent commands failed with agent not connected until it reconnected again. The old connection now leaves the new one alone. A configuration file that was ignored or adopted from config discovery no longer returns to pending (and an adopted one no longer loses its link to the configuration file) when the agent scans it again.
  • Sync rules: updates are validated and schedules are honoured — Changing a sync rule's target_environment or sync_direction (PUT /api/v1/projects/sync-rules/{ruleId}) now applies the checks of creating one: a pull or bidirectional rule needs a target environment, a reserved or invalid environment is rejected with 400, and the environment is stored in its canonical form. Scheduled syncs follow the rule's sync_schedule (a cron expression such as */5 * * * * or @daily); before, every enabled rule ran when it had not synced for an hour, whatever its schedule. A rule without a schedule, or with one that is not a valid cron expression (logged as a warning), still syncs hourly.
  • Configuration file versions no longer keep extracted secret values in the placeholder copy — When a discovered configuration file was adopted with secret extraction, the secrets were stored in the vault but the version's placeholder copy (the content with ${VAULT:…} references) still contained the values in clear (encrypted at rest like all version content). The placeholder copy now has the ${VAULT:<name>} reference in place of each extracted value. Once after the upgrade, in the background after the server has started, the primary rewrites the placeholder copies of existing versions that still equal their original content (log line config version placeholder copies regenerated; the run is recorded in system_configurations as config_version_placeholders_regenerated_at and not repeated). A copy that already has placeholders is never rebuilt. A version whose secret can no longer be read, or whose secret was rotated since, keeps its copy (logged as config version placeholder copy not regenerated). The original content of each version is unchanged.
  • Gateway registration establishes the E2EE session — POST /api/v1/gateways/register now also answers session_id and server_public_key (additive) when the gateway sent a valid P-256 public_key: the gateway derives the session key from it, requests its mTLS certificate and encrypts its heartbeats from the first one. The primary stores the session key encrypted with MAZEVAULT_MASTER_KEY, so encrypted heartbeats still open after a backend restart and on every replica. Older gateways ignore the new fields. A gateway can now hold more than one session (each gets the next key version). Sessions are not renewed yet: 23 hours after registration the gateway sends its heartbeat unencrypted again, still with its API token. POST /api/v1/gateways/csr now signs only a CSR for the session's own gateway (CN gateway-<id>, optionally the DNS name gateway-<id>.mazevault.local, no other SANs) and only while the gateway is active; any other CSR is refused with 403.
  • Azure Key Vault: external values, gateway sync links and certificate key usage — Reading a secret value that MazeVault stores in Azure Key Vault (key offload) returns it exactly as stored; a plain value that happened to be valid base64 (for example password1234) was returned decoded. Secrets synced from Key Vault through a gateway are now linked to their integration like a direct sync, so they show as synced from the vault instead of local_only. A Key Vault certificate request run directly on the primary keeps the template's key usage (it was dropped and the vault issued its default key usage). The gateway task format is unchanged.
  • HSM devices: routing defaults and Azure Key Vault / SoftHSM key operations — An HSM device created without environments or applies_to now serves every environment and scope, as documented (it was never selected, so hsm_generator rotations failed with "no HSM device configured"); existing devices are fixed without a migration. Azure Key Vault HSM devices create ECDSA keys on the curve of the requested size (P-256, P-384 or P-521; other sizes are refused, before every key was P-256), return the public key and RSA key size correctly, and keys listed by the device can be used by name. The built-in software HSM signs with RSA-PSS and restores RSA and EC keys it wrapped itself.
  • Kubernetes secret rotation step targets the configured cluster — When MazeVault runs inside Kubernetes, the kubernetes_secret rotation step used MazeVault's own in-cluster credentials even when the step configured a kubeconfig, so it wrote the secret into MazeVault's own cluster. The configured kubeconfig now always wins; in-cluster credentials are used only without one. Check rotations with a kubeconfig that ran on an in-cluster installation: the secret in the target cluster was not updated.
  • Rotation inputs: Entra extended configuration and HSM delegated custody — An Entra credential whose extended rotation configuration is not valid JSON no longer makes its rotation preflight fail with HTTP 500: preflight reports invalid_extended_rotation_config (manual review), and the credential is not rotated automatically until the configuration is fixed. An hsm_generator step in delegated custody mode no longer passes an empty password to the following steps; a database or secret step after it now fails with new_password not found instead of setting an empty password.
  • Client errors answer 4xx instead of 500 — Validation, not-found and state errors of the API no longer surface as 500: an unknown role in a role assignment, a weak password, an integration URL refused by the SSRF guard, an invalid environment, an EAB credential without a project, a CSR that is not PEM, an unknown report type and a malformed JSON body answer 400; unknown ids (archived items, gateways, organization logos, Terraform export, secrets, provider schemas, a template of another organization) answer 404; approving a request or renewal that is no longer pending, copying a secret onto an existing key and activating DR while the primary is active answer 409; an invalid agent bootstrap token answers 401. The response never contains database or constraint text. Revoking another user's API token answers 404 instead of 204 (nothing was revoked). Error bodies of these endpoints now use the common {code, message, request_id} shape.
  • Keytab rotation trigger and format conversion work — POST /rotation/resources/keytab/{id}/trigger reaches the keytab executor instead of answering 400. POST /format/convert converts the certificate's public certificate and chain (requires certificate:read on the certificate) instead of returning a placeholder.
  • SRP sign-in no longer reveals which accounts exist — POST /auth/srp/init answers an unknown email or an account without SRP like a real account (200 with a stable salt); the verify step then fails with 401 as for a wrong password. Before, it answered 404 or 400. /auth/srp/init and /auth/srp/verify carry the same rate limit as /login (429 when exceeded).
  • Password length counts characters; LDAP sign-in reuses accounts regardless of e-mail case — The 8-character minimum and 128-character maximum of local passwords now count characters instead of bytes, so a 7-character password with accented letters is refused and a long one with accented letters is accepted. An LDAP sign-in whose directory returns the mail attribute in mixed case (common with Active Directory) now signs in to the existing account instead of creating a second one; new LDAP accounts are stored with a lowercase e-mail. Licence region restrictions (allowed_regions) are not enforced while no GeoIP database is configured, instead of treating every public address as an unknown country.
  • Shared links: expired links are deleted, wrong passphrases are limited — Opening a shared link that has expired or used up its views now really deletes it (the delete was rolled back, so the encrypted value stayed in the database). A passphrase-protected link is burned after 5 wrong passphrases; until then each wrong passphrase answers 403 as before, afterwards the link answers 404 even for the correct passphrase. The count is kept in the new column shared_secrets.failed_attempts (migration 000298).
  • External-only secret values fail loudly; config extraction handles repeated keys — Reading a names_only secret (value kept only in an external provider) when neither key offload nor a sync rule can resolve it, or reading any secret without a local value in Orchestrator Mode without a sync rule service, now fails with an error instead of returning an empty value. Extracting secrets from a configuration file that has the same key twice (for example password in two INI sections) now stores both values; the second gets the placeholder …/password_2 instead of aborting the extraction.
  • Secret password history is salted and no longer returned by the API — The history used to refuse reusing one of the last 10 values now stores each value as a salted PBKDF2-SHA256 hash instead of a plain SHA-256, so equal values cannot be linked across secrets. The history is now actually stored with each value change (it was never saved, so the reuse check never refused anything); updating a secret to one of its last 10 values is refused with 400 (PUT /api/v1/secrets/{id}, the Terraform provider and other user writes). Re-sending the current value is not a reuse: it is applied as a metadata-only change without a new version, so a Terraform metadata or ttl_hours change keeps working. System restores and syncs are never refused: compensation of a failed rotation, rollback from a rotation backup, sync pulls, sync conflict resolution, cross-project sync and the Key Vault pre-rotation sync. Existing entries keep working for the reuse check until newer values replace them. Secret responses no longer contain password_history_hashes.
  • Licence client understands the licence server's answers — A heartbeat answered with a revoked or expired licence (plain 200 with LICENSE_REVOKED / LICENSE_EXPIRED) now marks the backend licence REVOKED / EXPIRED instead of leaving it ACTIVE and failing to decrypt. The hourly usage metrics are sent with a licence validation request (POST /api/v1/validate) instead of the non-existent /api/v1/metrics, which answered 404 for every organization. The licence server does not use these metrics yet, and the hourly requests count against its validation rate limit. Enrollment rejected by build authentication now logs the targeted hint (check BUILD_AUTH_SECRET, or the system clock / NTP).
  • Archive restore of unknown items fails; audit retention can delete — Restoring a project, secret or certificate that does not exist now answers 404 and writes no RESTORE_* audit event (it answered success and audited the restore). The audit retention purge can now really delete events older than the retention period (at least 1 day): the immutability trigger is lifted only inside the purge transaction, and a failed purge is logged instead of being ignored. No job runs the purge yet, so audit events are still kept indefinitely.
  • Default organization is the first one created — Features that use the default organization (and incidents of rotation resources without a project, such as platform system keys, including approval-continuation incidents) now use the organization created first. Before, it was whichever organization sorted first by its random id, so on an instance with several organizations another tenant could receive a platform key's rotation-failure incident and its notifications. A service identity without a single project now gets its incidents in its own organization. Check after upgrade (instances with several organizations): routes without a project judge organization-scoped roles in the default organization, which may now be a different organization than before; verify which administrators hold organization roles there. Agent bootstrap tokens (POST /agents/bootstrap) now create the deployment in the organization the agent:admin permission was checked in; for a global administrator without an organization context that is the default organization, and the request answers 400 only when no organization exists. The agent-bootstrap server command takes an optional --organization-id; without it the token goes to the default organization, which the command prints on stderr, and agent discovery returns the agent-mtls template of the agent's own organization.
  • Agent-proxied integrations can be saved (check the migration log after upgrading) — Saving an integration that goes through an agent failed, because project_integrations.agent_id pointed at users instead of agents. Migration 000272 retargets it to agents. Every existing agent_id that matches no agent (it could only hold a user id) is set to NULL, so that integration has no agent bound until you set one, and each such row is logged as a NOTICE in the migration log. After upgrading, check the migration log for these notices and re-bind the listed integrations to their agents.
  • Microsoft Graph: A Wrong Client Secret Was Sometimes Retried — The retry layer of the Entra ID (Microsoft Graph) client looked for an HTTP status anywhere in the error text, so a random trace or correlation ID containing e.g. a503 made an invalid_client (wrong secret) answer look retryable and the token request was sent again. A status number is now recognised only as a whole word; a wrong secret fails after one token request.
  • Terraform: mazevault_shared_secret shares a secret by its ID; Go SDK CreateSharedSecret requires Value — Creating the resource always failed with 400 because the backend needs the value to share. The provider now reads the value of the secret named by secret_id when the share is created and sends it; the value is never written to Terraform state, and share_url stays sensitive. A secret whose value the server cannot read (zero-knowledge, names-only or external) fails with a clear error instead of sharing nothing. In the Go SDK, CreateSharedSecret returns an error before any request when Value is empty; SecretID stays deprecated and the caller passes the value.

Security & Dependency Updates

  • API tokens of a deleted user stop working — Deleting a user (it moves to the Archive) now switches off the user's API tokens (mvt_…), and a token of an archived user is refused even while still marked active. Before, such a token kept authenticating with every role the user had when deleted, until it expired. Restoring the user from the Archive does not switch the tokens back on; the user creates new ones. Tokens of users archived before this release are refused too, without any action.
  • Rotation and sync steps stay inside the rotated resource's project — Before each rotation step and post-rotation action runs (also when a rotation is resumed, delegated to a gateway or runs an Entra post-rotation delivery), MazeVault checks that every integration it names belongs to the project of the rotated secret, certificate or Entra application, that an agent_id is an agent of its organization and that a target_project_id is a project of its organization, and that a secret (secret_id, password_secret_id), certificate (certificate_id) or Entra credential (credential_id, by its application's project) the step reads or writes belongs to the same project and an org_id is its organization; otherwise the step does not run and the rotation fails (a post-rotation action is recorded as failed). A sync rule whose integration is in another project now fails instead of running, rotation preflight reports another project's integration as missing, and a secret link to another project's integration is left out of the generated rotation workflow. The API already refused integrations, an agent_id or a target_project_id outside the scope on save; secret_id, password_secret_id, certificate_id, credential_id and org_id are not checked on save and are refused when the step runs. This covers hand-written workflows and configurations saved by older releases. Certificates without a project and Entra deliveries started before the upgrade are not narrowed. Managed token deployment targets likewise accept only an integration, agent or target_project_id of the token's organization (otherwise POST /api/v1/tokens/{id}/targets answers 400), and an existing target outside it fails on the next sync instead of receiving the token.
  • Audit log and archive stay inside the organization — GET /api/v1/audit-logs/ and POST /api/v1/audit-logs/search (also the aggregations) now list only events of the projects and organizations where the caller holds audit:read; before, any audit:read holder saw every organization's events. Audit events get an organization (migration 000237: column audit_events.organization_id, filled on write from the event's project or the resource it names, existing events backfilled); events that belong to no organization (sign-ins, user accounts, system keys) are listed only to a global role. The archive (/api/v1/archived/*, the Archived Items page) likewise lists only the caller's projects, secrets, certificates, discovered certificates and certificate requests; deleted users have no organization and are listed only to a global role. Restoring or permanently deleting an archived item (/projects|secrets|certificates/{id}/restore|permanent, /archived/…/{id}/restore|permanent) is now checked in the item's own organization (403 for another organization's item, 404 for an unknown id); deleted users can be restored or deleted only by a global role. Log streams (Splunk, Elasticsearch, Syslog) now receive only the events of the organization that configured them; before, every organization's stream received every organization's audit events.
  • Log streams: security events without an organization reach the organizations of their user (BREAKING for installations with several organizations) — Events that belong to no organization (sign-ins, MFA, password and user changes, role assignments, break-glass and temporary access) are streamed to the log streams of every organization in which the user the event is about holds an unexpired role, on the organization or on one of its projects. That user is the subject the event names (the signed-in user, the user whose MFA, password, roles or access changed, the break-glass target), never the user who performed the action; a user with roles in two organizations therefore appears in the Splunk, Elasticsearch or Syslog of both. With exactly one organization every log stream still receives every such event. With several organizations an event whose subject is not a user with an organization role (a failed sign-in with an unknown user name, changes to users who hold only global roles, removal of a role assignment, API token creation and revocation, instance settings, scheduled system jobs) is streamed to no destination; read these in the audit log (/api/v1/audit-logs, global role) if your SOC relies on them.
  • Agent enrollment needs an organization internal CA or an instance CA (BREAKING) — Agent mTLS certificates are no longer signed by another organization's internal CA: agent enrollment fails until the organization initializes its internal CA (Organization Settings → Project Settings → Certificate Authorities → Initialize Internal CA) or a global administrator creates an instance CA (POST /api/v1/ca/initialize). Check this before upgrading if agents enroll in an organization without its own internal CA (see Certificate Authorities, CA Accounts and Templates Stay in Their Organization).
  • POST /api/v1/ca/initialize needs a global role (BREAKING) — Creating an instance CA now requires a global role; an organization role gets 403. Automation that creates it with an organization-scoped token must use a global one.
  • Weekly-report recipients are set per organization (BREAKING, check after upgrade) — Each organization now has its own weekly-report e-mail recipients, and they receive only that organization's report. An organization admin (report:read/report:write in the organization) reads and changes them with GET/PUT /api/v1/organizations/{id}/reports/settings, sends the organization's report with POST /api/v1/organizations/{id}/reports/trigger and previews it with GET /api/v1/organizations/{id}/reports/preview; a global admin can do this for any organization, and a project role gets 403. Changing them is audited (REPORT_RECIPIENTS_UPDATED). System Outputs → Weekly Expiry Report edits the recipients of the selected organization. The Monday run sends every organization its own report, and report-enabled channels now get it also when the organization has no e-mail recipients. Upgrade: on an instance with one organization, the existing recipients move to it automatically. With several organizations they stay as instance-level recipients that receive nothing (no organization's report goes to them) until a global admin adds them to the right organizations; the Weekly Expiry Report tab lists them for a global admin and clears them. GET/PUT /api/v1/reports/settings (global role) now read and change only that leftover list, and POST /api/v1/reports/trigger (global role) sends every organization its own report. Expiring certificates without a project, which the instance-wide e-mail listed as unassigned, now appear in no organization's report: assign them to a project.
  • Orchestrator Mode: key offload without a project is refused (BREAKING) — Importing or discovering the private key of a certificate without a project, and exporting, deleting or reusing at renewal such a key, now fails until the certificate is assigned to a project (see Offloaded Keys Stay in the Caller's Organization and Vault).
  • Project Roles, Incidents, Terraform Organization Export and Agent Password Requests Stay in Their Organization — A project role (as the project's Access Management tab assigns it) no longer acts with the rights of the instance's default organization on routes without a project (managed tokens and keytabs lists, downloads and changes, certificate templates, CSR approval, the renewal queue and incident lists, creating service identities) when its project belongs to another organization; such a caller gets 403 there and passes project_id of its project where the route takes it. Routes that take the project only in the request body (POST /api/v1/secrets/, POST /api/v2/secrets, POST /api/v2/secrets/compare and /migrate, POST /api/v1/certificates/csr, POST /api/v1/certificates/import, /import/bulk, /import/file and the import previews) are judged in that body project (for /migrate in each secret's), so project roles of every organization keep using them, and organization roles of an organization other than the default one, which were refused there before, can now use them too; an import without a project is still judged in the default organization. GET /api/v1/incidents/{id} and POST /api/v1/incidents/{id}/resolve are judged in the incident's project or organization: another organization's incident answers 403, an unknown id 404. GET /api/v1/terraform/export/organization/{id} needs org:read in that organization (403 otherwise, 400 for a malformed id). /api/v1/admin/password-requests lists only the requests of agents of the organizations in which the caller holds agent:admin (a project role lists none, an agent without an organization needs a global role), and /approve and /reject of another organization's request answer 403 (unknown id 404) and change nothing.
  • Project roles act only inside their project on keytab, token, CSR, renewal-queue and incident routes (BREAKING for API callers with project roles) — A project role now acts only on its own project's records also in its project's own organization; before, on routes without a project it had the rights of the whole organization there. GET /api/v1/keytabs, GET /api/v1/tokens, GET /api/v1/renewal-queue/ and GET /api/v1/incidents/ list only the records of the projects and organizations in which the caller holds the read permission (a project role: its project's records, no organization-level ones; organization and global roles see what they saw before). /api/v1/keytabs/{id} (read, update, delete, download, history, check-compliance), /api/v1/tokens/{id} (read, update, delete, value, renew, targets) and POST /api/v1/certificates/csr/{id}/approve, /reject and /archive are judged in the record's project or, without one, its organization: another project's or an organization-level record answers 403 and nothing changes, an unknown id 404. POST /api/v1/keytabs and POST /api/v1/tokens with a project_id need the permission in that project, which must belong to the organization the record is stored in; without project_id they need it at organization level. GET /api/v1/keytabs/dashboard without project_id, POST /api/v1/service-identities/ and POST /api/v1/certificate-templates/ need the permission at organization level. API and SDK callers that hold only a project role get 403 on these creates without project_id and see only their project's keytabs, tokens, renewal-queue items and incidents; the KeyTab and Token Management pages show such a user only its project's records, and importing a keytab or creating a token there (the forms send no project) needs an organization role.
  • Upgrade: plan a maintenance window for large audit logs — Migration 000237 backfills audit_events.organization_id for all existing events and holds an ACCESS EXCLUSIVE lock on audit_events while it runs. On large audit tables this takes minutes, during which audit writes (and the API requests that write audit events) wait and reading the audit log is blocked too; the backend does not finish starting until the migration is done, so raise the startup and liveness probe timeouts accordingly. The update rewrites every attributable row, so the table temporarily needs up to about twice its size in free disk space. Plan the upgrade in a maintenance window.
  • Gateways reach only their own tasks — The gateway task endpoints (GET /api/v1/gateway/tasks, GET /api/v1/gateways/{id}/tasks, POST /api/v1/gateway/tasks/{id}/claim, PATCH /api/v1/gateway/tasks/{id}) now act only for the gateway authenticated by X-Gateway-ID and its token. A gateway_id in the query, path or body that names another gateway gets 403, and a status update for another gateway's task gets 404; before, a gateway with its own valid token could list (with decrypted payloads), claim and complete the tasks of every other gateway. Deployed gateways already send their own id in both places and keep working; gateway_id on GET /api/v1/gateway/tasks is now optional.
  • IIS and post-install commands can no longer be injected (agent and backend) — The agent's built-in post-install allowlist entry Import-Module WebAdministration now matches only that exact command; previously anything appended to it (; …, &&, |) was executed on the agent host. The agent also refuses every post-install command that contains a line break (built-in and allowed_commands patterns alike), because the shell runs each line as its own command, and on Windows agents every post-install command containing (, ), {, }, @, $, a backtick or any non-ASCII character, because PowerShell evaluates such expressions inside arguments before the program starts and treats Unicode spaces, curly quotes and dashes like their ASCII counterparts (structured agent_command requests run without a shell and are not affected). On Windows, the agent also passes the certificate store, fingerprint and CA certificate names to PowerShell as environment values when it renews a certificate from the Windows store or checks the trust store, so a crafted store name or CA certificate name can no longer run code. The agent passes IIS application pool, site and host names to PowerShell as environment values instead of pasting them into the script, and logs a successful post-install command by program name only (wallet and keystore passwords no longer reach the agent log). The backend's IIS recycle step accepts only pool names made of letters, digits, spaces and . _ - ( ), so agents of earlier releases are protected too. Action required: a post-install command of the form Import-Module WebAdministration; Restart-WebAppPool … is now refused — use Restart-WebAppPool -Name "<pool>" alone (PowerShell loads the module automatically; the built-in pattern covers pool names of letters, digits, _, - and .), otherwise — e.g. for .NET v4.5 Classic — add a matching allowed_commands pattern on the agent; a multi-line post-install command must be put on one line; an IIS recycle step whose pool name contains other characters fails validation and must be corrected. Agents and backends of the previous release stay compatible.
  • Connectors verify TLS by default (BREAKING, check before upgrading) — ADCS CA accounts now verify the server certificate unless Verify SSL was saved switched off (verify_ssl: "false"); before, every ADCS connection, which carries the service account password, skipped verification for accounts created in the UI, even with the switch on. The PostgreSQL rotation step (postgres_db) without ssl_mode now connects with sslmode=verify-full instead of disable, and also reads sslmode as the integration wizard stores it. Audit streams (syslog over TLS, Elasticsearch) now trust the PEM CA in tls_ca_cert, so a private CA no longer needs tls_skip_verify. Smallstep CA accounts with a Root CA Fingerprint and no trust bundle now trust step-ca's own root: MazeVault fetches /roots once and keeps only the root with that SHA-256 fingerprint, like step ca bootstrap, so insecure_skip_tls is no longer needed. ADCS servers and PostgreSQL rotation targets whose certificate is not trusted by the backend stop connecting with an x509: error: add the CA to the backend's trust store (see TLS Configuration, section 8) or switch verification off explicitly. Smallstep CA accounts with a Root CA Fingerprint and no CA Trust Bundle whose step-ca sits behind a TLS-terminating proxy with a public certificate also stop connecting (the proxy's certificate does not chain to the pinned root): pass TLS through to step-ca, or put the proxy's CA into the CA Trust Bundle.
  • Secret values no longer stored in rotation workflows, step history or notifications — The default body of the api_notification rotation step (also used as an Entra post-rotation action) no longer contains secret values: the step context is redacted like the webhook step's, so a new client secret or password is not posted to the configured URL. Generated database rotation steps no longer store the integration's admin username and password in the rotation configuration and the step history; the orchestrator reads them from the database integration when the step runs. Migration 000221 removes the stored copies from existing rotation configurations and from the execution history wherever the referenced database integration still holds them (a step whose integration was deleted keeps its values; the migration reports their number as a WARNING in the PostgreSQL server log, and SELECT 'rotation_configs' AS source, id::text FROM rotation_configs WHERE workflow_steps::text LIKE '%"admin_password"%' UNION ALL SELECT 'rotation_execution_steps', id::text FROM rotation_execution_steps WHERE step_config::text LIKE '%"admin_password"%'; lists the rows that still hold plaintext credentials). Previous Azure Key Vault values captured by post_rotation_sync are no longer stored in the step output, and gateway task checkpoints of delegated rotations are encrypted at rest like the final result (checkpoints written by the previous release are still read). A downgrade does not restore the removed credentials; a pending database step of a rotation interrupted across a downgrade must be re-run as a new rotation.
  • Logout and password change revoke refresh tokens — POST /api/v1/auth/logout now revokes the refresh token sent in the refresh_token cookie on the server (and records the LOGOUT audit event), so a copied refresh token no longer works after logout. Changing the password revokes every refresh token of the user; a browser session that changed it gets a new refresh_token cookie (API-token callers do not), other browsers and copied tokens must sign in again. A refresh token is no longer accepted as a Bearer access token (it was, for 7 days, even after logout); refresh tokens now carry typ: refresh and a unique jti, and every JWT without a role claim is rejected by the API. Access tokens already issued stay valid until they expire (15 minutes by default).
  • Organization integrations refuse internal targets (SSRF) — Creating, updating or testing a Jira, Teams, Slack or webhook integration now resolves the URL's host and is refused when any of its addresses is internal: loopback (incl. ::1 and ::ffff:127.0.0.1), 0.0.0.0/::, private networks (10/8, 172.16/12, 192.168/16, IPv6 fc00::/7), link-local (incl. the cloud metadata address 169.254.169.254), carrier-grade NAT 100.64.0.0/10 and multicast. Previously only some IPv4 prefixes and localhost were refused. On-premises installations whose Jira or webhook target resolves to a private address can no longer save or test that integration; integrations saved earlier keep sending until they are edited. Jira, Teams, Slack and webhook notifications no longer follow HTTP redirects: a target that answers 3xx is reported as a failed delivery, so configure the final URL.
  • ACME Server Binds Every Request to the Signing Account (RFC 8555) — The built-in ACME server (/api/acme/*) now serves an order, authorization, challenge, finalize or certificate download only to the account that created the order; another account gets 404 (previously any registered account could read another account's orders and finalize them with its own CSR). kid is the account URL from the new-account Location header, as RFC 8555 requires, so standard clients (cert-manager, certbot, acme.sh, lego) work after registration. The JWS url header is now required on every request, including revoke-cert and key-change, and must equal the URL listed in the directory, which is built from MAZEVAULT_BASE_URL (set it to the external URL ACME clients use, otherwise requests fail with 400 malformed). The EAB must be computed over the account key of the new-account request and carry the new-account URL; key rollover checks that the inner JWS has the outer url and names the signing account; revocation with the certificate's key (no account) requires the request to be signed by that certificate's key, and revocation in either mode requires the submitted certificate to be byte-identical to the issued one (a forged certificate with the same serial is 404). The header alg must match the account key (ES256 P-256, ES384 P-384, RS256 RSA), a JWS carrying both jwk and kid is 400 malformed, and an unsupported revocation reason code is 400 badRevocationReason instead of being revoked as unspecified. ES384 (P-384) account keys are accepted and verified with SHA-384, and a deactivated account gets 401 unauthorized instead of 404 accountDoesNotExist.
  • Entra ID SSO: Nonce Always Enforced, No More Linking by E-mail (action required) — The Entra ID sign-in now always rejects an ID token whose nonce does not match the one issued for that sign-in (OIDC replay protection); the MAZEVAULT_ENFORCE_OIDC_NONCE variable is removed and ignored, delete it from .env / Helm values (check-config.sh reports it). An Entra sign-in is also no longer attached to an existing MazeVault account just because the email or preferred_username claim matches: Microsoft documents both as mutable and unverified, so anyone able to set that address could take over the account, including the local administrator. An account is matched only by the immutable Entra object ID (oid) of the configured tenant: accounts that have signed in through Entra SSO before keep working unchanged. A user whose MazeVault account (same e-mail, in any letter case, including an archived account) has never signed in through Entra SSO now gets an error instead of a session; e-mail addresses of new Entra accounts are stored in lower case. One-time re-link: (1) an administrator deletes that MazeVault account (Access Control → Users), (2) permanently deletes it in the Archive (DELETE /api/v1/archived/users/{id}/permanent, permission archive:permanent_delete; this removes the user's API tokens and MFA records, anonymizes the account and writes an audit entry), (3) the user signs in through Entra ID, which creates a new account bound to their object ID, with roles from the Entra group mappings. A sign-in never removes an archived account itself. Roles granted locally to the old account and its personal data are not carried over, so re-grant them. Keep the local break-glass administrator on an e-mail address that is not used in Entra.
  • Database rotation steps: random Oracle passwords and no SQL injection (action required for Oracle) — An Oracle rotation step without new_password in its input (simple rotation or sqluser:<role>:<user>) used to set the same fixed, publicly known password on every account; it now generates a 30-character random password (crypto/rand, at least two upper-case and lower-case letters, digits and ^_~ specials each, so it also passes ora12c_strong_verify_function). Rotate such accounts again: affected are accounts rotated by an Oracle database step whose workflow did not pass new_password (for example a custom workflow without a password generator step); find them in the rotation history, or simply rotate every Oracle account managed by MazeVault. The MSSQL and Oracle ensure steps and the MSSQL, MySQL and Oracle reconcilers reject grant permissions that are not SQL Server permissions (whitelist) or Oracle/MySQL privilege keywords before any SQL is sent; Oracle steps reject invalid user, role and member names instead of creating an empty identifier. Session termination accepts only a session number (MSSQL, MySQL) or sid,serial# (Oracle), and the Oracle PDB name must be a plain identifier. The MySQL reconciler escapes passwords, user, role and grantee names and quotes schema/object names; the Oracle reconciler accepts passwords containing -- or /* and rejects ones with a double quote. The generic SQL step no longer doubles backslashes for PostgreSQL (only when the server runs with standard_conforming_strings=off), so passwords containing \ keep working after rotation (a PostgreSQL account rotated earlier by this step with a \ in its password, possible only with a custom password policy, has the doubled form stored and should be rotated again); the step stops with an error when the setting cannot be read, and it rejects user names with whitespace, quotes, brackets, backslashes or #. The MySQL reconciler builds its connection string with the driver, so a database name, user name or password containing ?, &, @ or / can no longer add driver parameters such as allowAllFiles.
  • Agent discovery requires the agent's client certificate — POST /agent/discover used to accept any signed-in user or token and handed out a fresh JWT of the agent registered for the given hostname (or created a new active agent). It now accepts only an enrolled, active agent identified by its mTLS client certificate (as POST /api/v1/agents/me/refresh-token does): the agent can update only its own name, OS and version, a different hostname is refused with 403, a call without a valid agent certificate gets 401, and the endpoint no longer creates agents (201 is gone) — new agents enroll with POST /api/v1/agents/register. MazeVault agents never called this endpoint, so deployed agents are not affected.
  • Gateway heartbeats change gateway identity only with the gateway's token — POST /api/v1/gateways/{id}/heartbeat used to apply the role, environment, software version, managed identity client ID and supported environments from any caller who knew the gateway's ID; these fields steer task routing and failover. They are now applied only when the heartbeat carries the gateway's credentials from registration (X-Gateway-ID + Authorization: Bearer <token> or X-Gateway-Token). Heartbeats without them are still accepted and keep the gateway alive (last_heartbeat), so gateways of the previous release keep working, but their identity fields are ignored, logged and counted in mazevault_gateway_heartbeat_identity_ignored_total. Gateways of this release send the token, and a heartbeat sent as an E2EE envelope is now decrypted with the gateway's session key (its identity fields were previously never applied). Upgrade your gateways: a gateway still on the previous release no longer changes its role or environments through its heartbeat.
  • Organization settings, bootstrap completion and system health require authorization — GET /api/v1/organization/summary needs org:read; incident contacts follow the System Outputs page: GET /api/v1/organization/incident-contacts needs system_output:read and PUT needs system_output:write (before, any signed-in user could rewrite where incident notifications go). The endpoints act on the caller's organization, changing the contacts is audited (UPDATE_INCIDENT_CONTACTS), and without system_output:write the page shows the contacts read-only. GET /api/v1/organization/settings stays readable by every member. Completing the initial setup is now POST /api/v1/bootstrap/complete and needs system:admin; it only takes effect once, later calls change nothing, and GET answers 405. GET /api/v1/health/system (component versions, connection pool, licence state) needs a signed-in user with system:read; liveness and readiness probes keep using the public /api/v1/health. The dashboard shows the Platform health panel only to users with system:read; for other users the sidebar shows the operational status as unavailable.
  • MFA codes and login challenges are single-use; disabling MFA re-authenticates — A TOTP code is accepted at most once (RFC 6238 §5.2; the last used time step is stored per user, migration 000224), and an MFA login challenge (session_token of POST /auth/mfa/verify) issues tokens only once. DELETE /api/v1/auth/mfa now requires a JSON body with the account password or a current TOTP / recovery code (400 without either, 401 when it does not match), so an access or API token alone can no longer turn MFA off. API clients that call this endpoint must send the body. POST /auth/mfa/enroll and POST /auth/mfa/verify-enrollment answer 409 while MFA is enabled, so the second factor can only be replaced after a re-authenticated disable.
  • Anonymous POST /api/v1/enroll removed — The backend answered an unauthenticated POST /api/v1/enroll for any customer ID by creating a POC licence, a server node and an OAuth client with agent:read, agent:write, certificate:read and certificate:request scopes, and returned the client secret to the caller. Nothing in MazeVault used this endpoint: the backend obtains its licence from the licence server, and agents enroll with a bootstrap token (mazevault-agent enroll). The endpoint no longer exists; a call is now rejected. OAuth clients already created through it are deactivated automatically on upgrade (migration 000225; nothing else ever created OAuth clients), and a token issued to a deactivated OAuth client is now rejected immediately instead of at its expiry.
  • Web Interface Security Headers (OWASP ZAP findings) — Every web interface response now carries Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy, and static assets now also get X-Content-Type-Options: nosniff (nginx dropped it there). Static assets no longer send Access-Control-Allow-Origin: *. A Content Security Policy and Cross-Origin-Embedder-Policy: require-corp ship in report-only mode; enforcement follows once no violations are reported. Missing files such as favicon.ico or sitemap.xml return 404 instead of the application page, and robots.txt disallows indexing. The deprecated X-XSS-Protection header was removed. The headers are defined once in the frontend image and used by both the Docker Compose kit and the Helm chart.
  • Web Interface Security Headers (OWASP ZAP findings) — Every web interface response now carries Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy and Cross-Origin-Resource-Policy, and static assets now also get X-Content-Type-Options: nosniff (nginx dropped it there). Static assets no longer send Access-Control-Allow-Origin: *. A same-origin Content Security Policy and Cross-Origin-Embedder-Policy: require-corp are enforced. API responses carry Cross-Origin-Resource-Policy: same-site, and nginx error pages no longer show the nginx version. Missing files such as favicon.ico or sitemap.xml return 404 instead of the application page, and robots.txt disallows indexing. The deprecated X-XSS-Protection header was removed. The headers are defined once in the frontend image and used by both the Docker Compose kit and the Helm chart.
  • Go Transitive CVEs Cleared (x/net, x/mod) — golang.org/x/net was upgraded 0.55.0 → 0.56.0 and golang.org/x/mod 0.37.0 → 0.40.0 across the agent, cli, k8s-operator, and terraform-provider-mazevault modules to resolve CVE-2026-46600, CVE-2026-56864, and CVE-2026-56865.
  • Cross-Module Dependency Refresh — Additional module dependencies were updated: golang.org/x/crypto → 0.55.0 and golang.org/x/sys → 0.47.0 in ocsp-responder, cloud.google.com/go and related packages in backend, and google.golang.org/grpc, google.golang.org/genproto, and OpenTelemetry packages in terraform-provider-mazevault.
  • gRPC DoS Cleared (CVE-2026-84445) — google.golang.org/grpc was upgraded 1.83.1 → 1.83.2 in backend and terraform-provider-mazevault to resolve CVE-2026-84445 (gRPC-Go denial of service via malformed RPC requests), which was failing the dev-pipeline Trivy filesystem scan. The fix is pinned to the 1.83.2 patch line (the 1.84.0 line is still affected). A transitive golang.org/x/mod → 0.40.0 bump (CVE-2026-56864 / CVE-2026-56865) that a subsequent tooling update pulled back to 0.39.0 was re-applied. Verified with go build, go vet, govulncheck (0 reachable), and a clean Trivy CRITICAL,HIGH --ignore-unfixed scan.
  • Jackson Core/Databind DoS Cleared (CVE-2026-68497, CVE-2026-89407, CVE-2026-89425, CVE-2026-91776, CVE-2026-91777) — com.fasterxml.jackson.core:jackson-databind was upgraded 2.18.8 → 2.18.11 in the Java SDK (sdks/java/pom.xml). The initial → 2.18.10 bump resolved CVE-2026-68497 (CPU DoS via unbounded numeric parsing); a subsequent Jackson advisory wave then flagged four more HIGH denial-of-service issues — two in transitive jackson-core (CVE-2026-89407 regex backtracking, CVE-2026-89425 unbounded StringBuilder growth) and two in jackson-databind (CVE-2026-91776 unbounded cache growth, CVE-2026-91777 quadratic forward-reference completion) — all fixed by 2.18.11, which also transitively aligns jackson-core → 2.18.11. Everything stays on the 2.18.x patch line (drop-in, no API changes) and the dev-pipeline Trivy filesystem scan is green. The Java SDK now imports the Jackson BOM (com.fasterxml.jackson:jackson-bom, pinned via a single jackson.version property) in dependencyManagement, so jackson-core, jackson-databind, and jackson-annotations stay version-aligned and future Jackson bumps are a one-line change.
  • x/net HTTP/2 DoS Cleared (CVE-2026-78669) — golang.org/x/net was upgraded → 0.60.0 across backend, agent, cli, k8s-operator, and terraform-provider-mazevault to resolve CVE-2026-78669 (net/http2 denial of service via excessive HTTP/2 SETTINGS frames), which was failing the dev-pipeline Trivy filesystem scan. All five modules build and test clean and the Trivy CRITICAL,HIGH --ignore-unfixed scan is green across every module.
  • Second Dependency Wave (verified) — A further batch of safe dependency bumps was applied and verified (backend go build/vet/full go test, agent linux+windows build/test, frontend tsc -b/lint/build/165-test on Node 24, Trivy, govulncheck): backend Go (Azure azcore 1.23.2 / azidentity 1.14.1, azblob 1.8.2, aws-sdk-go-v2/config 1.33.8, service/s3 1.114.2, gin-contrib/cors 1.7.9, go-ldap/ldap/v3 3.4.15, microsoft/go-mssqldb 1.11.2, golang.org/x/crypto 0.57.0, golang.org/x/oauth2 0.37.0, golang.org/x/time 0.16.0, google.golang.org/api 0.301.0 — the latter transitively advanced google.golang.org/grpc → 1.84.0, which the current Trivy database no longer flags for CVE-2026-84445, and a coordinated lockstep bump of k8s.io/api + k8s.io/apimachinery + k8s.io/client-go 0.28.0 → 0.37.1, which the Kubernetes rotation steps compile and test clean against); frontend npm (lucide-react 1.50.0, @types/node 26.6.4, globals 17.13.0, vitest 5.0.3); and GitHub Actions (actions/checkout, actions/setup-go, actions/setup-node v5 → v7, and actions/download-artifact v4 → v8, which stays compatible with the already-pinned actions/upload-artifact@v7 and the repository's name/pattern downloads). Deferred (blocked): the previously tracked typescript 7 and eslint 10 PRs (still blocked by their respective ESLint peer ranges).
  • Consolidated Dependency Bumps — A large batch of routine dependency updates was applied and verified end to end (backend/agent go test, frontend tsc/lint/build/165-test on Node 24, Trivy, govulncheck): backend Go modules (microsoftgraph/msgraph-sdk-go, swaggo/swag, swaggo/gin-swagger, Azure azcore/azblob, Venafi/vcert, aws-sdk-go-v2 core/config/credentials/s3/route53, gin-contrib/cors, jackc/pgx, miekg/pkcs11, lib/pq, mongo-driver, prometheus/client_golang, stretchr/testify), agent Go modules (go-resty, prometheus/client_golang, golang.org/x/crypto 0.57.0, golang.org/x/sys 0.48.0, go-pkcs12 0.7.3), frontend npm packages (react/react-dom 19.3.0, react-router 8.4.0, axios 1.20.0, date-fns, jspdf-autotable, @noble/hashes & @noble/ciphers 2.4.0, lucide-react 1.x, @types/node 26, @testing-library/react & user-event, @vitejs/plugin-react 6, globals 17, autoprefixer, @tailwindcss/postcss, eslint-plugin-react-refresh, vite-plugin-node-polyfills), container base images (alpine 3.21 → 3.24 for init-certs, python 3.12-alpine → 3.14-alpine for the docs build stage), and GitHub Actions (actions/upload-artifact 4 → 7, actions/github-script 7 → 9, azure/aks-set-context 4 → 5, azure/login 2 → 3, codecov/codecov-action 4 → 7, github/codeql-action 3 → 4, docker/build-push-action 6 → 7, docker/setup-buildx-action 3 → 4, docker/login-action 3 → 4, zaproxy/action-baseline 0.14 → 0.15). Automated dependency PRs are now opened against the dev integration branch (Dependabot target-branch: dev for every ecosystem) instead of the default branch. Deferred (blocked upstream, kept as open tracked PRs): typescript 7 (blocked by typescript-eslint peer typescript <6.1.0) and eslint 10 / @eslint/js 10 (blocked by eslint-plugin-react peer, which does not yet declare eslint ^10).
  • Material UI Upgraded to v9 (Major, Frontend) — The admin console was migrated from Material UI 7 to @mui/material / @mui/icons-material 9.4.0 and @mui/x-date-pickers 9.14.0. All removed/renamed v9 APIs were migrated with the official MUI codemods plus targeted fixes: deprecated system style props (fontWeight, display, fontFamily, alignItems, fontStyle, spacing, …) moved into sx; TextField/Dialog/Checkbox/Autocomplete legacy slot props (InputProps, inputProps, InputLabelProps, SelectProps, PaperProps, TransitionProps) moved to the unified slotProps API; the removed *Outline icons were repointed to their *OutlineOutlined equivalents; and the theme's MuiButton containedPrimary style override was rewritten with the v9 variants API. This is a frontend-internal component-library upgrade with no change to application routes, behavior, or backend/API contracts; the full TypeScript type-check, ESLint, production build, and 165-test unit suite pass.
  • Frontend Build/Test Toolchain Requires Node ≥ 22.22 — The test toolchain was raised to vitest 5 and jsdom 30, whose bundled undici requires a Node runtime API (webidl.util.markAsUncloneable) available only on Node ≥ 22.22. CI and the frontend Docker image already run Node 24; .nvmrc files were added at the repository root and in frontend/ (pinned to Node 24) so local development selects a compatible runtime automatically.
  • Auth Cookies Are SameSite=Lax (OWASP ZAP finding) — The refresh_token and XSRF-TOKEN cookies were sent with SameSite=None whenever COOKIE_SECURE=true, which let browsers attach them to cross-site requests. Both are now always SameSite=Lax; COOKIE_SECURE still controls only the Secure flag. The web interface always calls the API on its own origin, and SSO sign-in (Entra) is unaffected. Existing sessions continue: the refresh cookie gets the new attribute on the next token refresh.
  • API Cache and Cross-Site Request Protection (OWASP ZAP findings) — API responses now default to Cache-Control: no-store, no-cache, must-revalidate and X-Content-Type-Options: nosniff, so login, refresh, SSO and status responses are never stored by browsers or proxies; CRL and ACME responses keep their own cache policy. A Fetch Metadata resource isolation policy rejects cross-site browser requests with 403 unless they are top-level navigations or come from an origin listed in CORS_ALLOWED_ORIGINS. Non-browser clients (agents, gateways, SCEP/ACME/OCSP, CLI, Terraform) are unaffected. If a browser application on another domain calls the API, list its origin in CORS_ALLOWED_ORIGINS.
  • Organization-Scoped Roles Stay in Their Organization — A role assigned for one organization no longer grants access to another organization's projects, secrets, certificates or rotation runs: the permission check now judges every request in the resource's own organization and project instead of the caller's default organization, and only a role assigned without organization and project counts as global. Roles inside their own organization and global roles work as before. Cached permission decisions are now kept per organization, project, client IP and MFA state. Assigning a role (POST /api/v1/rbac/users/{id}/roles) with a project_id, organization_id or environment that is present but invalid (not a UUID, an empty string, a number) is rejected with 400 instead of being stored as a global assignment.
  • Secrets Are Created Only in Projects You May Write To — POST /api/v1/secrets, POST /api/v2/secrets, POST /api/v2/secrets/migrate and POST /api/v2/secrets/compare take the project or secret from the request body, and the permission check now judges it there: the caller needs secret:write (compare: secret:read) in that project, its organization or globally, for the environment in the body. Before, a role limited to one project could create a secret in any project, including another organization's, migrate another tenant's secret to zero-knowledge format and compare another project's environments. Such requests now get 403; a migrate batch that contains a secret the caller may not write is refused as a whole, without migrating any of it.
  • Rotation Configs, Executions and Backups Stay in Their Project — /api/v1/rotation/configs/{id} (read, update, delete, preflight, trigger), /api/v1/rotation/resources/{kind}/{id} (read, readiness, history, trigger: Entra credentials in their app registration's project, certificate deployment targets and renewals in the certificate's project; system keys, keytabs and service identity secrets for global roles only), /api/v1/rotation/executions/{id} (and /resume), /api/v1/rotation/preflights/{id} and /api/v1/rotation/backups/{id}/… are now judged in the project and organization of the rotation config, execution, preflight or backup they name; an id that does not exist answers 404. Before, a rotation role in one project could read and trigger another organization's secret rotation and reveal or restore its pre-rotation value. POST /api/v1/rotation/configs authorizes the secret_id in its body (403/404), and backup reveal/restore check secret:read in the secret's own project and environment. GET /api/v1/rotation/executions, GET /api/v1/rotation/backups and GET /api/v1/rotation/resources list only what lies in the projects and organizations where the caller holds rotation:read (all of it, including resources without a project such as system keys, for a global role). A secret_manager certificate target may write only into a project of the certificate's organization, and saving one needs secret:write in that project (404/403); preflight blocks an existing cross-organization target (target_project_other_organization).
  • Environment-Limited Roles Are Judged in the Secret's Environment — A role assigned for one environment is now checked against the environment of the secret a request names, not against ?environment= or the default: a dev-only role no longer reads, migrates or copies a prod secret (403). POST /api/v1/secrets/{id}/copy checks the target environment in the secret's project and answers 404 for an unknown secret.
  • Drift Detection Across All Organizations Needs a Global Role — POST /api/v1/secrets/drift/detect without project_id checks the secret links of every organization; it now answers 403 unless the caller holds secret:write globally. The web interface always sends the project.
  • Project Roles Count Only in Their Project Outside Route Checks — A project-scoped assignment no longer applies to decisions made without a project (an organization-level rotation backup, a list's scope, a body-scope check). The route check of endpoints without a project or a resolvable id still lets a project role through until those endpoints narrow to their own scope.
  • Integrations, Entra Apps, Sync Rules and Secret Links Stay in Their Project — The routes that carry only an integration, Entra app registration, Entra credential, managed subject, Entra sync conflict, sync rule or sync conflict id (/api/v1/projects/integrations/{id}… incl. database sessions, /api/v1/entra/{integrationId}/…, /api/v1/entra/apps/{id}…, /api/v1/entra/credentials/{id}…, /api/v1/entra/subjects/{id}, /api/v1/entra/sync-conflicts/{id}/resolve, /api/v1/projects/sync-rules/{id}…, /api/v1/projects/sync-conflicts/{id}/resolve) are now judged in the project that resource belongs to: a role of another project or organization gets 403, and an id that does not exist 404 (before, some answered 200 with an empty list, 409 or a "failed" dry-run). GET /api/v1/integrations, GET /api/v1/entra/expiry-report and POST /api/v1/admin/azure/mi-permissions-check show only the integrations and credentials of the projects in which the caller holds the permission. A sync rule, a secret link and an Entra rotation config's post-rotation action may only name an integration of their own project (400 otherwise), a sync rule update no longer changes project_id (the Terraform resource mazevault_sync_rule now replaces the rule when project_id changes), deleting a secret link needs the link's own secret (404 otherwise), and a database integration reads Key Vault references (admin_password_integration_id, wallet_password_integration_id, stored admin credentials) only from an integration of its own project.
  • Agent Bindings and Discovery Policy Stay in the Agent's Organization — POST /api/v1/projects/{id}/integrations/agents binds only an agent of the project's organization; another organization's agent or an unknown agent_id is answered 400 and nothing is stored (before, any agent could be bound, and an unknown one gave 500). PUT /api/v1/agent-integrations/{id} is now judged in the binding's project: a writer of that project (project:write) can edit it (before, only organization-wide roles could, and they could edit any organization's bindings), a role of another organization is refused and an unknown binding gets 404. GET /api/v1/agents/me/discovery/policy builds the bundle only from the templates of the calling agent's organization (before, every agent got the oldest organization's templates, including its keystore password candidates); an agent without an organization gets only the built-in defaults. Agents need no change: the request is the same and the organization is taken from the agent's identity. An agent registered without an organization (older registrations) cannot be bound to a project until it re-registers with a deployment token. Bindings created across organizations before this release are not removed automatically: review the agent list of each project (the project's Integrations tab) and unbind agents of other organizations.
  • Approval Policies and Requests Stay in Their Organization — PUT/DELETE /api/v1/projects/approval-policies/{policyId}, GET /api/v1/projects/approval-requests/{requestId} and its /approve and /reject actions are now judged in the project the policy or request belongs to. Before, a role in any organization could read another organization's approval request, approve or reject it, and weaken or delete its approval policies. A role of another project or organization now gets 403, and an id that does not exist 404 (before 500 for update, approve and reject, and 204 for delete). A policy update changes only the policy's own settings (name, trigger, conditions, approvers, expiry, notifications, enabled); project_id, id and timestamps in the body are ignored, so a policy can no longer be moved into another project. The Terraform resource mazevault_approval_policy treats a policy already deleted in MazeVault as deleted on terraform destroy.
  • Certificate Update and Import Stay in the Caller's Organization — PUT /api/v1/certificates/{id} with a project_id now also needs certificate:import in the target project (403 otherwise), and the service refuses to move a certificate (and its private key) into a project of another organization (400); a template of another organization is now answered 400 as well (before, 500). POST /api/v1/certificates/import, /import/bulk and /import/file judge every project_id of the request: without certificate:import in that project the whole request is refused with 403 and nothing is imported (before, a holder of one project's right could import a certificate and private key into any project, including another organization's). A template_id of another organization than the target project is answered 400 on every import route, and so is a template_id without a project_id; PUT /api/v1/certificates/{id} likewise refuses a template for a certificate without a project unless the same request assigns one. /import and /import/bulk ignore organization_ca_account_id and ca_id in the body (as the file import already did); the CA account follows the template. The "already exists" answer names the existing certificate's project only to an import into that same project. The Swagger path of the bulk import is corrected from /certificates/import/batch to /certificates/import/bulk (the route itself is unchanged).
  • Configuration Files and Discovered Certificates Stay in Their Organization — GET /api/v1/config-files, GET /api/v1/config-files/overview, GET /api/v1/discovery-results, GET /api/v1/discovered-certificates and GET /api/v1/discovered-certificates/stats now list and count only the records of the projects and organizations in which the caller holds the read permission (before, every organization's files, discovery results and discovered certificates). A record without a project belongs to the organization of the agent that reported it (a discovered certificate synchronised from a CA: the CA account's organization); an agent without an organization: global roles only. GET /api/v1/config-files/{id}, POST /api/v1/discovery-results/{id}/adopt and /ignore, and GET/POST /api/v1/discovered-certificates/{id} (detail, assign, adopt, ignore, archive, import-key) are judged in the record's project or organization: another organization's record answers 403 and nothing changes, an unknown id 404 (GET /config-files/{id} answered 500 before). The target project of assign, bulk/assign, bulk/adopt and of a discovery-result adopt must be one in which the caller holds discovered_cert:assign (for a discovery result config:write; 403 otherwise), and bulk/assign, bulk/adopt and bulk/ignore refuse the whole request (403) when one of the ids belongs to a scope the caller does not hold discovered_cert:bulk in. A project role sees and acts on only the discovered certificates already assigned to its project; unassigned ones need an organization role. The target project must belong to the record's organization, also for a caller with roles in several organizations (403; an unknown target project 404 instead of 500), and adoption uses the record's organization. A discovered certificate is linked automatically (agent report, CA synchronisation) only to a managed certificate of its own organization, so another organization's certificate can no longer be moved or revealed through it; assigning a record that is linked to a managed certificate moves that certificate only within the organization (409 otherwise) and now also needs certificate:write on it and in the target project (403). Links to another organization's managed certificate created before this release stay in place but can no longer move it. Importing or renewing a certificate links only discovered certificates of its own organization, the detail and import-key ignore an older link to another organization's managed certificate (import-key answers 400), and a CA account's synchronisation and adoption no longer give a discovered or managed certificate of another organization that CA account or its order.
  • Certificate Authorities, CA Accounts and Templates Stay in Their Organization — A CA belongs to the organization of its project or, for an organization-level CA, to the organization whose CA account links it. GET /api/v1/ca lists only the CAs of the caller's organizations and projects (all CAs for a global role); GET/PUT /api/v1/ca/{id}, POST /api/v1/ca/{id}/hsm and POST /api/v1/ca/{id}/crl/regenerate on another organization's CA, or on a CA linked to no organization without a global role, are refused with 403. A CA account's ca_authority_id and a certificate template's ca_account_id must belong to the same organization (otherwise 400), and every issuance path (including renewal and ACME) refuses a template that was linked to another organization's CA account before this release. PKI mode root creates the organization's own root CA instead of reusing another organization's CA of the same name. POST /api/v1/projects/{id}/ca/{caId}/hsm and POST /api/v1/projects/{id}/certificates/{certId}/revoke now act on {caId} / {certId} of project {id} (before, they read the project id as the CA / certificate id and never worked); a CA or certificate outside the project is 404. Gateway certificates (POST /api/v1/gateways/csr) are issued only from the instance's system gateway-mtls template and signed only by an instance CA (one created with POST /api/v1/ca/initialize, which now requires a global role: 403 for an organization role); a tenant template named gateway-mtls (a workaround some installations created) is now ignored, and without the system template or an instance CA the answer is 404 (was 500). Templates without a CA (such as agent-mtls) are signed by their organization's internal CA or else the instance CA, never by another organization's CA. System templates (agent-mtls, gateway-mtls) can no longer be edited or deleted (403). A CA's ocsp_responder_cert_id must be a certificate of the CA's organization (400). Issuance, renewal, ACME finalization and DCV all use a template's CA account only within the template's organization. A certificate request (POST /api/v1/certificates/csr, POST /api/v1/projects/{id}/csr) can only use a template of the project's (or the agent's) organization; another organization's template is answered 404, and POST /api/v1/certificates/csr without a project requires certificate:request in the template's organization. Upgrade: an organization without its own internal CA and an installation without an instance CA no longer get agent mTLS certificates signed by some other internal CA: agent enrollment fails until the organization initializes its internal CA (Organization Settings → Project Settings → Certificate Authorities → Initialize Internal CA) or a global administrator creates an instance CA (POST /api/v1/ca/initialize). A CA created with POST /api/v1/ca/initialize that an organization's CA account later linked is that organization's CA, not an instance CA; if it was the only one, gateways get 404 until a global administrator creates a new instance CA. A PKCS#11 HSM whose module cannot be loaded now fails with an error (the device test reports it unreachable) instead of panicking, which stopped the backend when a certificate of a CA configured for it was revoked.
  • Organization Pages Act on the Organization in the Path — /api/v1/organizations/{id}/… is now judged in the organization {id} names, not in the caller's default organization: a role scoped to one organization no longer renames another organization, adds its environments, reads its licence or sync settings, or manages its CA accounts, internal CA, HSM devices, renewal policies or ACME (EAB) credentials (403), also not by adding ?project_id= of a project of its own organization (403). Items under the path are looked up inside that organization: an environment, renewal policy, HSM device or EAB credential of another organization answers 404 and is left unchanged; deleting one that does not exist (or no longer exists) now answers 404 instead of 204 (environments, renewal policies, EAB credentials) or 400/409 (HSM devices). POST /api/v1/organizations/{id}/acme-credentials refuses a project_id, default_template_id or domain-rule template of another organization (404), and an ACME order no longer uses a default or domain-rule template of another organization stored earlier (without another template the order is refused). PUT/DELETE /api/v1/organizations/integrations/{id} and /api/v1/organizations/templates/{id}, the agent actions /api/v1/agents/{id}/… (update channel, lifecycle impact, unregister, re-register, uninstall) and the deprecated /api/v1/ca-accounts/{id}/products and /sync are judged in the organization of the integration, template, agent or CA account (an agent without an organization: global roles only); an unknown id answers 404. GET/POST /api/v1/ca-accounts/ with an organization_id other than the caller's needs the CA permission in that organization (403). The Terraform resources mazevault_environment, mazevault_renewal_policy and mazevault_hsm_device treat a 404 on destroy as already deleted.
  • Approvals Enforce Four Eyes, Expiry and Single Use — The requester of an approval request can no longer approve it (four-eyes principle), even with approval:approve and required_approvers: 1; a request past its expires_at can no longer be approved, and an approved rotation publish request is no longer honoured after expires_at. Both are refused with an error and the request stays pending. Concurrent votes are serialized so the quorum is counted correctly. Creating an approval request through the API no longer accepts server-owned fields (status, policy_id, expires_at, resolved_at, decisions), so a client cannot create an already approved request or votes in other users' names, and required_approvers is at least 1; the requester's own vote never counts toward the quorum. An approved rotation publish request is now used by only one new rotation intent: when the original rotation intent cannot be reused (environment-bound secrets) or the automatic continuation after approval re-authorizes, the new intent takes over the approval of the waiting intent (same resource, action and approval policy; a user only takes over approvals of their own requests), which is marked superseded. Previously such rotations ended on approval required again and never ran. Concurrent triggers of an intent that is reused (its preflight is still valid) are not limited this way yet and can still both use one approval (tracked as F-BEROT-044).
  • Role and Access Grants Cannot Exceed the Caller's Own Rights — Assigning a role (POST /api/v1/rbac/users/{id}/roles), removing an assignment, creating or deleting a group mapping (/api/v1/rbac/mappings) and creating a user with a role (POST /api/v1/rbac/users) now require the caller to hold every permission of that role, including the permissions its deny entries (!x) restrict (and the admin role itself to hand out admin) through its own role assignments that cover the target scope and do not expire earlier; otherwise the answer is 403. An organization admin manages roles inside its organization only; a user created with a role gets a global assignment, so only a caller holding that role globally may create it (create the user without a role and assign an organization or project role instead). Deleting a user (DELETE /api/v1/rbac/users/{id}) requires the rights to remove each of the user's role assignments. Organization admins and the Access Control page: the page creates users with the global role user and assigns roles without a scope (globally), so an organization admin now gets "Failed to create user" / "Failed to assign role" there. Create the user via the API without a role (POST /api/v1/rbac/users with email and password only) and assign the role with POST /api/v1/rbac/users/{id}/roles and {"role_id": "<role>", "scope": {"organization_id": "<organization>"}} (or project_id; project roles can also be assigned on the project's Access tab). A scope whose project_id belongs to another organization than organization_id, or an unknown project, is rejected with 400; granted_by is always the caller. Temporary access (POST /api/v1/access/temporary) now accepts the documented body (target_user_id, resource_type, resource_id, permissions, duration_minutes, reason, optional environment); the approver needs <resource_type>:grant_access in the resource's project or organization and may grant only permissions it holds itself or concrete actions of that resource type, and a grant for one resource no longer applies to requests without that resource. Break-glass (POST /api/v1/access/break-glass) requires the access:break_glass permission and * through a global assignment (in practice a global administrator); refused attempts are audited as GRANT_BREAK_GLASS_DENIED.
  • Role assignments for every environment (*) and break-glass access now take effect (review them before upgrading) — A role assignment with environment * now applies in every environment; before, it applied in none and granted nothing. It stays limited to its organization or project and, like any environment-bound assignment, does not apply to a request without an environment (the same holds for * in a temporary grant). A break-glass grant (POST /api/v1/access/break-glass) now gives its holder * on every resource type in every environment until it expires an hour later or is revoked (access decisions are cached for up to CACHE_PERMISSION_TTL_SECONDS, default 5 minutes, so an allow can outlive the expiry by that long); before, it never applied. The archive lifecycle actions still need the admin role, and a deny entry (!x) of one of the holder's roles still blocks x during break-glass. Temporary grants for environment * (POST /api/v1/access/temporary) take effect the same way. When several temporary grants match a request, their permissions now add up (before, one of them was picked). Before upgrading, review the assignments that will start to apply with SELECT user_id, role_id, organization_id, project_id FROM user_roles WHERE environment = '*'; and the active break-glass and * grants with SELECT identity_id, resource_type, environment, valid_until, reason FROM environment_permissions WHERE (resource_type = '*' OR environment = '*') AND is_active AND (valid_until IS NULL OR valid_until > now());.
  • Consistency Groups, Renewal Queue Items and Certificate Targets Stay in Their Project — /api/v1/consistency-groups/{id} (read, update, delete, /check), /api/v1/consistency-warnings/{id}/resolve and /api/v1/renewal-queue/{id}/… (approve, cancel, resume, DCV state, DCV retry and e-mail resend) are now judged in the project of the consistency group or of the queued certificate: a role of another project or organization gets 403, and an id that does not exist 404 (before, a role limited to one project could read, rewrite and delete another organization's consistency group, cancel its certificate renewals and read their DCV records). /api/v1/certificates/{id}/targets/{targetId}… and /api/v1/certificates/{id}/rotation/executions/{execId} answer 404 for a deployment target or rotation execution that does not belong to the certificate {id}; before, they returned another organization's target configuration and execution (a malformed {id} on the target routes now answers 400). Creating or updating a certificate deployment target (POST /api/v1/certificates/{id}/targets, PUT …/targets/{targetId}) accepts only an agent_id of the certificate's organization and an integration_id / database_integration_id of the certificate's project (400 otherwise); before, a target could name another organization's agent or Key Vault integration and /sync deployed the certificate with its key there. Targets saved before this release are not re-checked: review the agents and integrations of existing certificate targets. An agent registered without an organization (older registrations) can no longer be set on a target until it re-registers with a deployment token.
  • ?organization_id= and a Body organization_id No Longer Reach Another Organization — A request that names an organization in ?organization_id= (managed tokens, keytabs, certificate templates, deployments, the renewal queue, CA accounts) now needs the route's permission in that organization too, otherwise it is answered 403; before, the role was checked in the caller's organization while the handler worked in the named one. The same holds for organization_id in the body of POST /api/v1/projects/ (project:write) and POST /api/v1/deployments/ (deployment:write). GET /api/v1/projects/ lists only the projects in which the caller holds project:read. /api/v1/certificate-templates/{id}, /api/v1/naming-conventions/{id}/… and POST /api/v1/keytabs/discovered/{id}/import are judged in the organization of the template or discovered keytab (an unknown id is 404, before 500); POST /naming-conventions/{id}/provision creates projects only in the template's own organization (another organization_id is 403), and a discovered keytab is imported only into a project of its own organization (404). Creating a project needs project:write at organization level, in the body organization or, without one, in the caller's organization (a project role no longer suffices); without organization_id the project is still created only on an instance with a single organization. API and SDK callers that hold only a project role and send ?organization_id= of an organization now get 403 on those routes (for example the token and keytab lists); send project_id of your project instead where the route takes it, or hold a role in that organization. Without project_id a project role counts on these routes only for a project of the caller's organization (the instance's default organization, in which these routes are judged); before, a project role of any organization acted there with the rights of the whole default organization.
  • Dashboards, Reports and the Weekly Report Show Only the Caller's Organizations — Without project_id, /api/v1/dashboard/* (secrets, certificates, configuration and rotation overviews, /export of every type, /rotation/failures, /rotation/actionable, /sync/overview, /sync/conflicts, /sync/failures), GET /api/v1/certificates/, /api/v1/archived/summary, /projects, /secrets, /certificates and GET /api/v1/reports/preview now count and list only the projects and organizations in which the caller holds the permission; before, they covered every organization. Rows without a project (certificates, rotation executions) need a global role; the ca_accounts export lists only organizations in which the caller has an organization role. Acknowledging a rotation failure or a sync failure or blocked seed of another organization is refused (404 or 403); dismissing another organization's actionable rotation answers already_removed and changes nothing. GET /api/v1/sync-dashboard/events and /merkle (replication data of the whole instance) need a global sync:read role. Each report-enabled channel (Slack, Teams, webhook, Jira) now gets the weekly report of its own organization only, and so do the e-mail recipients, which are now set per organization (see Weekly-report recipients are set per organization). Compliance report password statistics count only the organization's secrets and templates.
  • Service Identities Belong to an Organization (check after upgrading) — A service identity now has an organization_id: it is created in the instance's default organization (the one routes without a project are judged in), or in the organization named by ?organization_id=, where the caller needs service_identity:write in the default organization and in that one, and a role scoped to an organization lists, reads, renames, deletes and manages Universal Auth and client secrets (/api/v1/auth/universal-auth/identities/{id}…, rotation resources of kind service_identity_secret) only for that organization's identities; global roles keep managing every identity. Before, a service_identity:* role of any organization managed every tenant's identities. An unknown identity id now answers 404 (before 500 on update). Migration 000235 assigns each existing identity to the single organization of its unexpired role assignments, else of the user who created it (a creator with a global role assignment does not count), else, on an instance with exactly one organization, to that organization; it removes or disables nothing and Universal Auth sign-in keeps working. On an instance with several organizations an identity it cannot attribute stays without an organization, so only global roles manage it; the migration lists each as a WARNING line (000235: service identity …) in the PostgreSQL log. After upgrading, list them with SELECT id, client_id, display_name FROM service_identities WHERE organization_id IS NULL; and assign each with UPDATE service_identities SET organization_id = '<organization id>' WHERE id = '<identity id>';. POST /api/v1/service-identities/?organization_id= answers 400 for an invalid id and 404 for an unknown organization. The Terraform resource mazevault_service_identity exports organization_id and treats an identity already deleted outside Terraform as gone.
  • Offloaded Keys Stay in the Caller's Organization and Vault (Orchestrator Mode) — Importing a certificate with key_storage_mode: external accepts a key_integration_id only from the organization of the import's project_id and refuses it without a project (400); before, a private key could be written through another organization's Key Vault integration. Every key offload (certificates, keytabs, managed tokens) now uses an explicitly named integration only inside the caller's organization, and an offload that names neither an organization nor a project is refused instead of using the oldest secret manager of any organization: in Orchestrator Mode, importing or discovering a private key of a certificate without a project now fails until the certificate is assigned to a project. Reading and deleting an offloaded key uses the Key Vault named in its reference (before, the first Azure integration of the organization, or of any organization, so with two vaults the key was looked up in the wrong one); a reference whose vault no enabled integration of the owning organization holds fails instead. Exporting, deleting or reusing at renewal the externally stored key of a certificate without a project is refused for the same reason. Key custody events of an offload carry the caller's organization also when it is known only from the request context.

Notes

  • Upgrade Impact (Effective Chain, Discovery Body, Agent Deployment Contract) — Version 1.0.61 adds one idempotent, additive migration, 000199_discovered_certificate_body (certificate_pem, chain_pem TEXT NULL on discovered_certificates; the database health check validates both). No column is added to certificates, no backfill runs, no existing chain is rewritten. Certificates that already carry a stored chain export, display, rotate and deploy byte-for-byte as before (covered by a regression test); only certificates with an empty chain change behavior, and only when a verified issuer exists in the same organization. Reading (export, detail, agent list) never persists anything; the resolved chain is written only when an import or adoption stores a certificate body that has no chain. API additions are additive: chain_source, chain_complete, chain_warnings on GET /certificates/{id} (and /projects/{id}/certificates/{certId}); certificate_pem, chain_pem, chain_certificates on GET /discovered-certificates/{id}; export of a record without a body now returns 422 CERTIFICATE_BODY_MISSING instead of a generic 500. Terraform: mazevault_certificate data source gains chain_pem, chain_source, chain_complete; its certificate_pem is now populated (it was always empty before because the SDK read a field the API does not emit) — plans that referenced it will show a value for the first time. Behavioral changes to review: (1) a private key import for a managed certificate without a body is rejected until the body exists (the next agent scan, the next CA synchronisation, or a manual import of the public certificate — CRT/PEM/DER or PFX — attaches it; agents still using the legacy /agents/me/discovery protocol do not report the body, so records discovered only by such agents are completed by one of the other two paths); (2) deployment targets are validated when saved and when the rotation workflow is built: WebLogic targets need keystore_path, a trust_store_password_ref when trust_store_path is set, and wlst_script_path for any restart mode; database wallet targets need an agent, wallet_path, an Oracle provider and update_tns_names=false (rac_nodes is refused as well); web_server and load_balancer targets are refused. Existing targets are not modified — an enabled target that violates these rules now fails its rotation with the target ID and reason instead of being skipped or reporting false success, and it can always be disabled or deleted; the UI editor turns a legacy update_tns_names=true off on save and shows the alternative for unsupported providers and target types. Review the deployment targets of production certificates for web_server, load_balancer, non-Oracle Database Wallet entries and wallet targets without an agent before enabling automatic rotation on them; (3) with the agent option install_chain_to_trust_store enabled, an intermediate CA that previously had no chain will now also install its root into the host trust store on deployment — the same behavior PFX imports with an embedded root always had. Agent rollout: the backend/gateway fix for JKS / PKCS#12 / Windows My keystore deployment works with already-deployed agents. The Agent File (PEM) target, agent_command steps (WebLogic restart, Oracle orapki), the public-only semantics of windows_root / windows_ca, and Oracle wallet deployment require the 1.0.61 agent; older agents answer those requests with an explicit unsupported proxy message type error and the deployment step fails fast. Oracle and WebLogic deployments are covered by contract tests against the agent protocol — validate them against a real orapki / WLST host on staging before relying on them in production. The unreachable duplicate discovery-adoption code path (CertificateDiscoveryService, handlers_discovery.go) was removed; its list filters (managed_certificate_id, discovered_by_agent_id, expiry) were merged into the live GET /discovered-certificates endpoint as the query parameters agent_id, managed_certificate_id and expiring_within_days, and POST /discovered-certificates/{id}/ignore now accepts an optional {"reason": "..."} body that is stored as state_reason and recorded in the audit log — the only two capabilities the removed code had that the live path lacked; adoption, bulk adoption and the detail enrichment were already a superset there. Both live endpoints gained Swagger documentation, and the five never-routed /discovery/certificates* paths disappeared from the Swagger output. There are no new environment variables, Helm values, or release-package format changes.
  • Upgrade Impact (Certificate Key Custody, CSR Validation, Serial Normalization) — No database migration, no new environment variable, no Helm value, no release-package format change, and no Terraform provider contract change (the mazevault_certificate resource is CSR-based and immutable; attaching a key is an operator action). API additions are optional and additive: expected_certificate_id on POST /certificates/import and POST /certificates/import/file, key_source_file in the preview-multi response, identity_normalized in the POST /admin/certificates/backfill response, and the failed value in the certificate-request status enum. Behavioral changes to review before upgrading a production inventory: (1) templates with a non-empty allowed_key_sizes now reject uploaded CSRs with other key sizes at submission — including for ADCS and external CAs, which previously did not check — so verify the list on templates used with customer-supplied CSRs (an empty list means any size); (2) templates with an explicit san_config now enforce it on uploaded CSRs — allow_additional_sans and max_san_count columns are deliberately not used for uploaded CSRs because the template editor never persisted them reliably; (3) on primary startup a background, idempotent pass rewrites serial_number / thumbprint_sha256 / thumbprint_sha1 only for rows that diverge from their stored DER (hex serials written by ACME/Vault/external-order finalization, uppercase thumbprints); internal-CA, ADCS, DigiCert, Venafi and imported certificates are already canonical and are not touched, CA/CA-account bindings and renewal settings are never modified, and no sync events are emitted; (4) importing a certificate that already exists in another project now returns an error instead of silently attaching material to the other project's record; (5) auto-approved CSR requests whose issuance fails are marked failed (externally offloaded pending keys are intentionally retained with their reference for operator cleanup — nothing is deleted from a secret manager on a CA failure), and manually approved failures return the request to pending. Deferred decisions (documented in the architecture reference): a unique index on certificates.thumbprint_sha256 (pending external orders and legacy duplicates would break upgrades), enforcing template-to-project assignment on CSR submission (requires a MAJOR bump for API/Terraform clients), custom EKU OIDs in the internal CA, and a retry cap / denied-state detection for ADCS orders that stay pending indefinitely.
  • Upgrade Impact (Rotation Templates) — Version 1.0.61 adds one idempotent migration, 000195_rotation_template_policy_fields, which adds three columns to rotation_config_templates: lead_time_days (INT, default 0), grace_period_days (INT, default 0), and secret_custody_mode (VARCHAR, default local). The database health check now validates the new template policy columns. The mazevault_rotation_template Terraform resource gains the optional lead_time_days, grace_period_days, and secret_custody_mode arguments, and the Go SDK is updated to match; all additions are additive and backward compatible, with no removed or renamed resource, data source, API endpoint, or SDK contract. There are no new environment variables, Helm values, or release-package format changes. The provider and SDK additionally expose the already-persisted project_id and retry_delay_seconds fields (no migration), and organization_id on the resource is Optional+Computed. Caveat: on template create, an explicit 0 for max_retry_attempts, retry_delay_seconds, or timeout_minutes is not honored — the server applies its defaults (3/5/10); omit those fields or set a positive value (changing them to 0 later via update is honored).
  • Upgrade Impact (Agent Lifecycle) — This feature adds one idempotent migration, 000196_agent_lifecycle_metadata, which adds nullable lifecycle bookkeeping columns to agents (unregistered_at, unregistered_by, lifecycle_reason, lifecycle_reason_category, identity_revoked_at, uninstall_status, uninstall_requested_at, uninstall_completed_at, replaced_by_agent_id); the database health check validates the new columns. A second idempotent migration, 000198_deployment_purpose, adds a purpose column (VARCHAR, default standard) to deployments so an authorized single-use replacement token can be distinguished from a standard enrollment token — only a replacement token may re-enroll a retired agent; the database health check validates this column too. All additions are additive and backward compatible. The RequireActiveAgent guard on JWT-authenticated agent routes means agents that have been unregistered, revoked, or deleted are now rejected even while holding an unexpired token; re-enrollment through a valid deployment (or single-use replacement) token remains the supported recovery path — older agents remain compatible because they simply re-register when rejected. Four additive agent:admin API endpoints are introduced (/agents/:id/lifecycle-impact, /unregister, /reregister, /uninstall) plus the additive DELETE /api/v1/deployments/:id (deployment:write). Deployment list responses now omit the bootstrap token (DeploymentResponse), while the create response continues to return it once (DeploymentCreatedResponse) — the Go SDK and the mazevault_deployment Terraform resource are unchanged in contract and still receive the token on create. The Terraform provider's mazevault_deployment delete now calls the new delete endpoint (previously a state-only no-op), so terraform destroy invalidates the enrollment token server-side; the resource Read also repopulates organization_id so an imported deployment can be destroyed. Registered agents are unaffected. Remote uninstall is opt-in and only attempted for connected Linux packaged installs. There are no new environment variables, Helm values, or release-package format changes for this feature.
  • Upgrade Impact (Security & Dependencies) — These are dependency and build-time changes only: no database migrations, environment variables, Helm values, or release-package format changes, and no API, Go SDK, or Terraform provider contract changes.
  • Upgrade Impact (Frontend MUI v9 & Node toolchain) — The Material UI v9 upgrade is a frontend-internal change with no change to application routes, behavior, backend/API/Go SDK/Terraform contracts, environment variables, Helm values, or release-package format. Operators building the frontend from source now need Node ≥ 22.22 (CI and the frontend container image already run Node 24; .nvmrc pins Node 24 for local development). Runtime deployment of the pre-built frontend image is unaffected.
  • Upgrade Impact (Rotation Backups) — This change adds one idempotent migration, 000197_rotation_backup_lifecycle, which adds status (VARCHAR, default pending), acknowledged_at, and acknowledged_by columns to rotation_backups, backfills status from restored_at, and replaces the old expiry index with a purge-oriented partial index plus a status index; the database health check validates the new columns. One new optional runtime environment variable is introduced — MAZEVAULT_ROTATION_BACKUP_RETENTION_DAYS (default 7) — propagated across .env.example, docker-compose, installers, and all Helm values. Four additive rotation API endpoints are added under /rotation/backups (list rotation:read; restore rotation:execute; reveal/acknowledge rotation:write, with reveal/restore additionally enforcing secret:read). All changes are additive and backward compatible — no removed or renamed resource, data source, API endpoint, or SDK contract — and there are no release-package format changes. Because the backup insert path now succeeds, the pre-rotation backup and automatic-rollback machinery become active for the first time; backups needing manual recovery are surfaced via the new endpoints and the mazevault_rotation_backups_recovery_required metric.
  • Upgrade Impact (Enterprise Functions / Deployment Gating) — This is a UI-reorganization and license-enforcement change only: no database migration, no environment variable, no Helm value, and no release-package format change. A new license feature key, deployment_management, is added to the PoC and Enterprise feature sets derived by getLicenseFeatures (state-aware: active / grace / expiring), and FeatureLicenseMiddleware is applied to the /api/v1/deployments CRUD group. No API route, request/response schema, Go SDK, or Terraform provider contract changes, and the Swagger surface is unchanged. Behavioral change: tenants on a Small Business or unlicensed plan that previously reached the Deployment CRUD API now receive HTTP 403 and an upgrade banner; PoC and Enterprise tenants are unaffected. The unauthenticated UUID-protected agent bootstrap endpoints remain ungated, so agent installation is not affected. The upgrade-banner copy for SSH, Configuration, and KeyTab was aligned to read "PoC or Enterprise".
  • Upgrade Impact (Orchestrator Mode Key Offload) — Backend-only change: no database migration, no new environment variable, no API contract change. Two behaviors are intentionally stricter and should be reviewed by operators running Orchestrator Mode: (1) Integration lookup no longer widens beyond the caller's scope. A token, keytab or private-key offload that carries a project/organization scope and finds no enabled secret_manager / key_storage / hsm integration in that scope now fails with no enabled key storage integration found for project/organization … instead of silently using the first integration anywhere in the system; an explicitly selected integration_id that does not exist or is disabled is rejected the same way. Make sure every organization that uses managed tokens or keytabs in Orchestrator Mode has an enabled secret-manager integration in one of its projects. (2) Azure Key Vault size limit is enforced before the write. Values whose base64 transport encoding exceeds 25 600 bytes (≈19 200 raw bytes) are refused with 400 and the exact limit; this is Azure's documented per-secret cap, not a MazeVault restriction — larger values require a HashiCorp Vault or AWS Secrets Manager integration (64 KB) as the project's key-storage target. Secrets created in Key Vault by this release use the new flat naming (mazevault-token-…, mazevault-keytab-…, mazevault-<cn>-<serial>); existing references stored on certificates are honored unchanged (update_version rolls a new version of the same secret). Nothing is renamed or migrated in Key Vault.

Version 1.0.60

Release Date: 2026-07-20

New Features

  • Token Management — External API Tokens with Mandatory Expiry Tracking and Agent Deployment — A new Enterprise function (PoC or Enterprise license, feature token_management) for managing externally-issued API tokens — for example Jira or Signi tokens — that can only be renewed manually in the source system's own UI. Each token is stored with a mandatory expiration date and an optional per-token alert lead time (default 30 days); MazeVault raises a token_expiry incident through the existing notification channels (email, webhook, Teams, Slack, Jira, SMSEagle) before the token lapses, and escalates it to critical once expired. Token values are encrypted at rest (AES-256-GCM) in standard mode, or offloaded to the configured external secrets manager (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault) in Orchestrator Mode. A token can optionally be bound to a project and deployed to the same target types as secrets, reusing MazeVault's shared value-delivery executors: an agent-managed server configuration file (JSON/YAML/ENV key→value, with an optional allowlisted post-install command), a Kubernetes secret, an Azure Key Vault secret, or an AWS Secrets Manager secret (Windows registry / IIS targets are intentionally deferred). A manual renew action records the newly obtained value and its new expiry, resolves the open expiry incident, and re-deploys any targets flagged for sync-on-renewal. Token Management is deliberately separate from Project Key management (project secrets) and Organization System Keys (MazeVault infrastructure keys), and appears under a new Enterprise Functions navigation group. Access is governed by new token:read / token:write / token:delete / token:admin permissions — deployment and renewal require token:write (admin implies it), and revealing a stored token value requires token:admin. The feature is fully manageable through the new mazevault_token Terraform resource and mazevault_tokens data source.
  • Upgrade Impact (Token Management) — Adds two idempotent migrations: 000193_token_management (creates managed_tokens and token_deployment_targets, seeds token:* RBAC permissions onto existing roles — org_admin full; operator read/write/delete; user and auditor read-only) and 000194_token_target_types (generalizes token_deployment_targets with target_type / integration_id / config so token delivery reuses the shared secret/certificate executors). There are no new environment variables, Helm values, or release-package format changes — the feature reuses the existing encryption, secret-offload, notification/incident, rotation-step delivery, and license infrastructure. The database health check now validates the two new tables. The Terraform provider gains the additive mazevault_token resource and mazevault_tokens data source; no existing resource, data source, API endpoint, or Go SDK contract is changed or removed. The endpoints are gated by the token_management license feature (PoC and Enterprise); without it the /api/v1/tokens API returns HTTP 403 and the UI shows an upgrade banner.

Improvements

  • Admin Batch Triage on Dedicated Rotation and Synchronization Problem Pages — System administrators can now select individual rows or all currently visible rows on Rotation Problems and in each section of Synchronization Problems. The frontend deliberately replays the existing audited single-item actions with at most three requests in flight, reports progress and partial failures, and refreshes the affected problem lists after completion; no unaudited bulk mutation path is introduced. Rotation failures are acknowledged and their linked open incident is resolved when available. Synchronization failures and blocked-seed incidents are acknowledged in their own sections, while pending synchronization conflicts can only be batch-ignored, which changes local conflict status without writing either value to an external provider. “All visible” is intentionally page-bounded and every confirmation states the exact affected count. Individual remediation remains available to non-admin operators with the existing write permissions; batch controls are shown only to the system administrator.
  • HashiCorp Vault Transit HSM/KMS Backend — mazevault_hsm_device now supports provider_type = hashicorp_vault, backed by the HashiCorp Vault Transit secrets engine. Previously the value was accepted by validation but instantiation failed with unsupported HSM provider: hashicorp_vault because no implementation existed. Vault Transit keys are generated and held by Vault (RSA-2048/3072/4096, ECDSA P-256/P-384/P-521); signing is performed remotely via the Transit sign endpoint so private key material never leaves Vault. The device connectivity test, key listing, public-key retrieval, remote signing (used by internal CA signing and other HSM-routed operations), and key deletion are all implemented. Configuration takes vault_url, token, and an optional mount_path (default transit). Key-encryption-key (KEK) wrapping remains served by the separate system HSM interface and is out of scope for this provider.
  • Provider-Aware Certificate Templates for External Commercial CAs — The certificate template form now adapts to the capabilities of the selected issuing CA account's provider instead of showing one fixed field set for every authority. Each external CA has a different API and issuance model, so the form is driven by provider-declared capabilities (exposed on GET /ca-providers/{type}/schema): providers with a product catalog (SSLMarket, DigiCert) present the certificate product as a dropdown from the CA's synced product list — the certificate type is derived from the selected product and the free-text external-template OID is hidden; providers that require domain control validation expose a per-template Domain Validation Method (email / dns / file), stored on the template as dcv_method (migration 000191); ACME issuers continue to show the ACME challenge policy; internal CAs show neither. The SSLMarket provider now resolves the product code from the template's linked CA product (falling back to a free-text code and then the account default) and honors the per-template domain validation method; DigiCert likewise prefers the per-template method. A new optional dcv_method argument is available on the mazevault_certificate_template Terraform resource. Existing templates are unaffected: the new column is nullable and empty means "use the provider/account default".

Bug Fixes

  • Terraform Managed Tokens Now Have a Valid, State-Safe Lifecycle — The mazevault_token resource used the reserved root attribute name provider, causing Terraform schema validation to reject the resource. The label is now exposed as token_provider, with a versioned state upgrader that preserves existing state values. Token reads and deletes now treat HTTP 404 as resource absence while preserving state on authorization, transport, and server failures. Empty description / project_id responses no longer leave stale non-empty state. New tokens can use Terraform 1.11+ value_wo with value_wo_version, so plaintext is absent from plan and state; legacy value remains supported but is explicitly documented as stateful. Import now supports metadata-only management. Migrating an existing resource from value to value_wo is intentionally not automatic because replacement changes the token ID and cascade-deletes its deployment targets.
  • Rotation Failure Triage Shows the True Open Total with a Rolling Ten-Item Queue — The Overview widget previously displayed the number of incidents in the API's default 20-row page as if it were the total, so tenants with more than 20 open rotation failures always saw an incorrect count. The widget now uses the existing paginated incident response's authoritative total, requests only the ten newest unresolved rotation-failure incidents for display, and reloads the first page after each individual close so the next unresolved incident immediately fills the queue. The misleading page-local High/Critical badge has been removed. Overview intentionally remains an individual-triage surface with no selection or batch-close action.
  • Secrets Dashboard "Missing in PROD" Metric Now Correctly Reflects Cross-Environment Parity — The Secrets Dashboard KPI previously labeled Missing in PROD never inspected production environments or the is_production organization setting. It counts secret keys that exist in at least one active (comparison-enabled) environment but are missing in another active environment — a cross-environment parity signal that requires at least two active environments. On instances with no production environment (for example a test tenant with only development, integration, migration, and testing), this produced a confusing non-zero "Missing in PROD" count. The card is now labeled Missing (Cross-Env) on both the Secrets Dashboard overview and the Reports view, matching the existing Missing Secrets (Cross-Environment) table below it. The underlying cross-environment computation is unchanged — only the misleading naming and label were corrected.
  • Orchestrator Mode Key Custody Events Persist Reliably — In Orchestrator Mode, offloading a private key (for example a certificate key sent to Azure Key Vault) recorded a custody audit event whose metadata value was an empty string. Because key_custody_events.metadata is a PostgreSQL JSONB column, the insert failed with invalid input syntax for type json (SQLSTATE 22P02) and the custody entry was lost — logged only as a warning — even though the key offload itself succeeded. Custody events now always persist syntactically valid JSON: an unset or blank metadata value is normalized to an empty JSON object ({}) at the write boundary, so every key offload, retrieval, and deletion produces a durable custody audit record. Callers that already supply structured metadata are unaffected. The database health check now also validates the key_custody_events columns.
  • SSLMarket Certificate Issuance Corrected to the Live SSLmarket API — Issuing a certificate through an SSLMarket CA account failed with API request failed with status 400: {"error":["No route found"]} on both standard and Orchestrator Mode instances. The SSLMarket provider had been implemented against a REST shape that does not match the published SSLmarket API. The provider now targets the real endpoints and payloads: orders are submitted to POST /order/certificate/{product_code} with the flat order body (domain, SANs, years, CSR, the mandatory dv_auth_method, and owner/authorized-contact/technical-contact/invoice fields that fall back to the SSLmarket account defaults when not configured); order detail, certificate download (by order id and format), domain-control-validation tokens, and revocation now use the correct routes; product resolution reads the real {"products":[…]} catalog; and responses are parsed with the API's actual JSON shapes (string identifiers, array dns_names, and the paid/requested order states). Authentication (X-Auth-Token) and the base URL were already correct and are unchanged. Certificate issuance and the asynchronous external-CA order poller now complete against SSLMarket.
  • Orchestrator & Sync Secret-Push Audit Events No Longer Fail with a Foreign-Key Violation — In Orchestrator Mode, pushing a secret to an external provider (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault) recorded a SECRET_PUSH_CREATE / SECRET_PUSH_UPDATE audit event whose user_id was mistakenly set to the project's ID instead of a user. Because audit_events.user_id has a foreign key to users(id), the insert failed with violates foreign key constraint "audit_events_user_id_fkey" (SQLSTATE 23503) and the audit record was lost even though the external push itself succeeded — so the external secret diverged from the audit trail. Secret-push and sync-seed audit events are now attributed to the initiating user (and, for system-initiated syncs, to the resolved system actor), so they persist correctly. As defense-in-depth, the audit writer now also guarantees durability: if any audit event ever fails to persist because of a bad actor reference, it is retried once with a null (unattributable) actor rather than dropped; only if the database is entirely unreachable is the full event emitted to the central structured log as a last resort. The previous local emergency-log file sink (/var/log/mazevault/emergency_audit.log), which was unusable on read-only container filesystems in Kubernetes, has been removed. A new mazevault_audit_persist_failures_total metric makes any last-resort emission alertable.
  • Merkle Integrity Cache Now Tracks Secret Changes — The incremental Merkle integrity cache recognized only the certificate/keytab event vocabulary (create / update / delete) and silently ignored the secret service's past-tense events (created / updated / deleted), logging merkle cache updater: unknown event type and leaving the secret integrity cache stale after every secret change. The cache updater now accepts both forms, so secret create/update/delete operations correctly update the Merkle integrity tree.
  • Rotation Backups Are Now Encrypted and Support Automatic Rollback — The rotation orchestrator was started without its encryptor wired in, so pre-rotation backups degraded to non-restorable version references and logged rotation backup requested but encryptor not configured — rollback will be limited. Automatic rollback of a failed rotation was therefore unavailable. The orchestrator now receives the system encryptor at startup, so the pre-rotation state is captured as an encrypted backup and automatic rollback is restored. Backups for external names_only secrets continue to rely on the external provider's own version history, as before.
  • Entra ID Integrations Accept AKS Workload-Identity Background Authentication — Configuring an Entra ID integration (used for app-registration secret rotation and Microsoft Graph sync) with background_auth_method=workload_identity was rejected with unsupported background_auth_method, even though the Microsoft Graph client and the shared Azure credential factory already support AKS federated workload identity. Three separate validation gates — integration create/update, test-connection, and the runtime consistency check used by rotation — defaulted to rejecting the value, so the integration could not be created or, if forced, failed later during rotation with configuration is inconsistent. All three gates now accept workload_identity, which — like managed_identity — requires no static credentials in the integration config: on a workload-identity cluster the runtime performs the federated token exchange for the configured (or webhook-injected) managed-identity client ID. This unblocks Entra credential rotation on AKS workload-identity clusters without a dedicated client-secret app registration.
  • Spring Actuator Refresh Rotation Step No Longer Crashes on Malformed Configuration — The spring_actuator post-rotation step used unchecked type assertions on actuator_url and on entries of the optional endpoints list, so a wrong config key (for example url instead of actuator_url) or a non-string endpoint caused a worker panic instead of a clear error. The step now validates its input defensively and returns a structured error, and it builds the refresh URL safely so a base URL, a trailing slash, or a URL that already ends in /actuator or /actuator/refresh never produces a doubled path.
  • Post-Rotation Secret Delivery to Key Vault and Other Targets Now Receives the Rotated Value — Post-rotation delivery actions (Azure Key Vault push, AWS/GCP/OCI secret managers, DevOps variables) require the freshly rotated secret value, but the post-rotation action phase rebuilt an execution context that omitted it, so a delivery action logged new_password not found in input context and silently skipped the write. The rotated value is deliberately never persisted, so it is now resolved at execution time from the just-updated MazeVault secret — identically whether the action runs on the primary or is delegated to a gateway (the value is fetched by the executing runtime and is never carried in the gateway task payload). Delivery for Entra credential rotations continues to flow through the dedicated seeded delivery workflow. As a hardening measure, the previous Key Vault value captured for rollback is now redacted from the persisted post-action record so no secret material is written to the rotation history.
  • Azure Managed HSM / Key Vault HSM Devices Can Now Be Tested and Used — Instantiating an azure_managed_hsm HSM/KMS device — for the device connectivity test as well as any CA signing, key offload, key rotation, or secret encryption routed through it — failed with CredentialFactory is required for AzureKVProvider because the HSM device service constructed the Azure provider without a credential factory. The Azure credential factory is now injected into the HSM device service and threaded through the provider factory, so Azure Managed HSM devices initialize with a real Azure credential.
  • Manual Secret-Manager Integration Sync No Longer Rejects Non-Canonical Type Values — Triggering a manual synchronization on a secret-manager integration could fail with sync not supported for integration type: secret-manager when the integration's stored type used a hyphen instead of the canonical underscore form. Integration types are now canonicalized (lowercased, trimmed, hyphens folded to underscores) when an integration is created, and the manual-sync guard compares against the canonical form, so secret-manager and secret_manager are treated identically. Migration 000192 normalizes any existing non-canonical project_integrations.type values so exact-match checks (manual sync, sync rules, key offload) stay consistent.
  • Pull/Bidirectional Sync Rules Require a Target Environment Up Front — Creating a pull or bidirectional sync rule without a target_environment was accepted but later failed at execution deep in the environment canonicalizer with environment slug is empty. Sync-rule creation now requires and canonicalizes target_environment for any rule that is not a pure push (push rules derive the environment from the source secret), returning a clear validation error instead of a confusing runtime failure.
  • ACME Certificate Issuance Now Completes Finalization (Let's Encrypt / ACME CAs) — A v1.0.59 report described ACME issuance passing dns-01 validation but failing at finalize with the error POST-as-GET requests must have an empty payload / must have a nil body, and this was reproduced end-to-end against a local Pebble server. Two defects were found and fixed. First, MazeVault submitted the CSR to the order URL (order.URI) instead of the order's dedicated finalize URL (order.FinalizeURL); because the order URL is a read-only POST-as-GET resource, posting the CSR body there was rejected by the CA. Second, once the CSR reached the correct finalize endpoint, some RFC 8555-compliant servers (including Pebble) do not return a Location header on the finalize response, so the underlying ACME client's internal post-finalize order poll targeted an empty URL and failed even though the CSR had been accepted; issuance now recovers by polling the known order URL and fetching the issued certificate. End-to-end dns-01 issuance through finalize is verified by a Pebble-backed reproduction test (build tag acme_pebble).
  • Upgrade Impact — Version 1.0.60 is a naming and label correction with no database migrations, environment variables, Helm values, or release package format changes. In the dashboard secrets overview endpoint (GET /api/v1/dashboard/secrets/overview) the response field missing_in_prod is renamed to missing_cross_env. This is an internal dashboard field consumed only by the MazeVault frontend; it is not part of the Go SDK or the Terraform provider, so no integration contract is affected. The cross-environment parity logic and the missing-secrets listing are functionally unchanged.
  • Upgrade Impact (Orchestrator custody & SSLMarket) — The Orchestrator Mode key custody persistence fix and the SSLMarket provider correction are backend-only changes with no database migrations, environment variables, Helm values, or release package format changes. To issue through SSLMarket, the CA account integration must set the correct product_code (an SSLmarket account may offer products — such as trial certificates — that are not returned by the API product catalog and therefore must be configured explicitly) and, when the account has no default business data, the owner/contact/invoice settings required by the chosen product. dv_auth_method defaults to Email when not configured.
  • Upgrade Impact (Provider-aware templates) — The provider-aware certificate template feature adds one nullable column via migration 000191 (certificate_templates.dcv_method) and one runtime-computed field (template_form) on the GET /ca-providers/{type}/schema response; both are additive and backward compatible. No environment variables, Helm values, or release package format changes. The mazevault_certificate_template Terraform resource gains an optional dcv_method argument; existing configurations continue to work unchanged.
  • Upgrade Impact (Audit, Merkle & Rotation) — The secret-push audit attribution fix, the audit durability hardening, the Merkle integrity cache fix, and the rotation backup encryption fix are backend-only reliability changes with no database migrations, environment variables, Helm values, or release package format changes. One additive Prometheus counter, mazevault_audit_persist_failures_total, is exposed; no existing metric, API endpoint, Go SDK, or Terraform provider contract changes. The local /var/log/mazevault/emergency_audit.log fallback is removed in favor of the central structured log, so the last-resort audit sink now works on read-only container filesystems.
  • Upgrade Impact (Entra WI, Rotation Delivery, HSM & Sync Validation) — These fixes add one idempotent data migration, 000192_canonicalize_integration_type, which normalizes existing project_integrations.type values to their canonical lowercase-underscore form; it is data-only and backward compatible. There are no new environment variables, Helm values, or release package format changes. background_auth_method=workload_identity is now an accepted value for Entra integrations (including via the mazevault_integration API surface), and pull/bidirectional sync rules now require target_environment at creation time. No existing API endpoint, Go SDK, or Terraform provider contract is removed or renamed.
  • Upgrade Impact (Vault HSM & ACME finalize) — The HashiCorp Vault Transit HSM provider and the ACME finalize fix are backend-only with no database migrations, environment variables, Helm values, or release package format changes. The hashicorp_vault HSM device provider is additive; no existing HSM device configuration changes. The ACME finalize fix corrects the certificate issuance flow only; no API endpoint, Go SDK, or Terraform provider schema is added, removed, or renamed. The Pebble reproduction test is gated behind the acme_pebble build tag and does not run in normal CI.
  • Upgrade Impact (Problem Triage UX) — The truthful Rotation Failure Triage total and the administrator batch workflows are frontend-only changes. They reuse the existing incident pagination metadata and existing single-item mutation endpoints, permissions, and audit events. There are no database migrations, API or Swagger contract changes, new RBAC permissions, environment variables, Helm values, release-package changes, Go SDK changes, or Terraform provider changes.

Security & Dependency Updates

  • Trivy CRITICAL/HIGH Dependency Vulnerabilities Cleared Across All Modules — The dev-pipeline Trivy filesystem scan (CRITICAL,HIGH, --ignore-unfixed) was failing on fixed-but-outdated dependencies. All affected packages were upgraded to their fixed versions and verified with go build, go vet, module unit tests, and govulncheck (0 reachable vulnerabilities), plus a frontend build and the full Vitest suite:
    • Go — golang.org/x/text 0.37.0 → 0.39.0 (CVE-2026-56852 / GO-2026-5970, norm.Iter infinite loop) in backend, cli, k8s-operator, and terraform-provider-mazevault.
    • Go — google.golang.org/grpc 1.79.3 / 1.80.0 → 1.82.1 (GHSA-hrxh-6v49-42gf / GO-2026-6061, xDS RBAC and HTTP/2 transport) in backend and terraform-provider-mazevault.
    • Go — software.sslmate.com/src/go-pkcs12 0.7.0 → 0.7.2 (GO-2026-5052) in backend and agent; this reachable vulnerability was surfaced by govulncheck even though Trivy's severity filter did not report it.
    • Frontend — axios 1.16.0 → 1.19.0 (GHSA-gcfj-64vw-6mp9 and related advisories).
  • React Router Upgraded to v8 (Major) — The frontend was migrated from react-router-dom 7.17.0 to react-router 8.3.0, which resolves CVE-2026-55685 and GHSA-qwww-vcr4-c8h2. React Router v8 removes the separate react-router-dom package; all imports were moved to react-router (the app is a declarative SPA using BrowserRouter, so no react-router/dom entry point is required). react and react-dom were raised to a minimum of 19.2.7.
  • Upgrade Impact (Security & Dependencies) — These are dependency and build-time changes only: no database migrations, environment variables, Helm values, or release-package format changes, and no API, Go SDK, or Terraform provider contract changes. The frontend build now requires Node ≥ 22.22 (declared via a new engines field in frontend/package.json); the CI frontend jobs and the frontend Docker image already run on Node 24. The React Router v8 upgrade is a frontend-internal migration with no change to application routes or behavior; the full frontend unit-test suite passes unchanged.

Version 1.0.59

Release Date: 2026-07-18

Improvements

  • Secret Rotation Views Refresh Silently After Background Completion — The Secrets tab now uses the existing per-secret rotation status poller as an event detector. When a background secret rotation reaches a terminal state, MazeVault refreshes only the affected secret through GET /api/v1/secrets/{id} and updates the visible version counter without reloading the whole secrets table. If the Version History or Rotation Configuration modal is already open for that secret, the modal reloads its version list or execution history in place. No extra global polling, toast, highlight, or full-page refresh is introduced.
  • Dedicated Access Control for Synchronization Problem Remediation — Synchronization problems now use a dedicated sync:read / sync:write permission boundary instead of implicitly tying action buttons to project:write. The project_admin, certificate_manager, and secret_manager roles can actively remediate synchronization problems, while auditor and regular users remain read-only. Navigation and the Synchronization Problems page now reflect the user's actual sync permissions.
  • Sync Failure and Blocked Seed Actions Are Available Directly from the Problem View — The global Synchronization Problems page and the project-level sync conflicts tab now provide confirmed actions for retry, conflict ignore, current sync failure fingerprint acknowledgement, and sync_seed_blocked incident acknowledgement. The left navigation badge now uses the aggregate sync_problem_count, covering conflicts, current sync failures, and blocked seed incidents.
  • Synchronization Problem Dismissals Scale with One Batched Lookup — The sync failure listing now evaluates acknowledged current-failure fingerprints with a single batched dismissal query instead of one lookup per failed sync rule. This preserves the existing rule/timestamp/error fingerprint semantics and soft-delete behavior while keeping the Synchronization Problems view responsive as the number of failed rules grows.
  • Certificate Exports Include the Issuer CA Chain More Reliably — Certificate export now normalizes stored certificate bundles and, when needed, resolves the issuing internal CA hierarchy through parent CA records. PEM/CRT and PKCS#7 exports include the leaf certificate followed by the issuer chain, while DER remains leaf-only as before.
  • Certificate Renewal Readiness Understands Internal CA Accounts — Renewal eligibility, rotation preflight, and rotation resource metadata now resolve internal organization CA accounts through their backing CertificateAuthority. Internal CA-backed renewals report both the CA account and the underlying signing CA, matching the runtime renewal path.
  • External CA Domain Validation Is Now Visible and Durable — External CA orders from DigiCert and SSLMarket now expose DCV instructions through registered renewal queue DCV endpoints and the project certificate table. Certificates with pending domain validation show a View Domain Validation action that opens the existing DCV status panel, where operators can inspect DNS/HTTP/email validation details and trigger provider re-checks without leaving the certificate workflow.
  • New External CA Certificate Requests Can Wait for Provider Validation — Certificate requests issued through an external CA no longer assume that SignCSR returns a certificate immediately. If the provider returns an order ID without PEM, MazeVault stores a pending certificate placeholder, links it to the request, records a durable submitted_to_ca queue item, and lets the external CA order poller complete the request when the provider later makes the certificate available.
  • CA Account Provider Settings Are Persisted and Terraform-Manageable — CA account create/update/sync flows now preserve non-secret provider settings such as DigiCert organization_id, server_platform_id, dcv_method, certificate_dcv_scope, custom base_url, and SSLMarket defaults in account metadata. The Terraform mazevault_ca_account resource adds a settings map for those non-secret values while keeping API keys and ACME EAB material in write-only credentials.
  • Terraform Secrets Support Write-Only Values for Ephemeral Passwords — The mazevault_secret resource now supports value_wo and value_wo_version for Terraform 1.11+ write-only inputs. Operators can generate a password with an ephemeral Terraform resource such as ephemeral "random_password" and pass it to MazeVault without storing the plaintext in Terraform plan or state. value_wo_version is the stored resend trigger: increment it when Terraform should send a new write-only value. The legacy value argument remains supported for existing configurations, and server-side generate remains available for MazeVault-generated secrets.
  • Secret Update API and Go SDK Support Metadata-Only Updates — PUT /api/v1/secrets/{id} now treats value as optional: omitting it updates metadata and TTL only, while supplying a non-empty value continues to create a new secret version. The Go SDK preserves the existing UpdateSecret(id, value, ...) method and adds a request-based update path that can intentionally omit value, which the Terraform provider uses for write-only secret lifecycle management.

Bug Fixes

  • Synchronization Problems No Longer Lack Action Icons for Authorized Roles — Users who could see problems through sync:read previously often had no way to resolve them because mutations were bound to project:write. The new sync failure and blocked seed acknowledgement endpoints are protected by sync:write, audited, and intentionally limited to the synchronization problem surface.
  • Ignoring a Sync Conflict No Longer Requires an External Provider Write — The ignore resolution now closes the local sync_conflicts row with status ignored and does not write to the target secret manager. This allows operators to permanently defer a known conflict without risking an unintended external value change.
  • Sync Failure Acknowledgement Applies Only to the Current Failure — Sync failures derived from sync_rules.last_sync_status are dismissed by a fingerprint composed from the rule, last sync timestamp, and error. If a later run fails differently, the problem appears again; acknowledgement therefore does not hide new or changed failures.
  • Rotation Configuration Edit No Longer Shows a Spurious Partial-Load Warning — The secret rotation configuration modal now addresses rotation history and readiness by the canonical rotation resource id (rotation_configs.id / resource.resource_id) instead of the secret id. This fixes the misleading "Some rotation sections could not be loaded. Available data is shown." warning when opening Secrets → Rotations → Edit for a configured secret.
  • Secret Version Rollback Works Reliably for Existing Version History — Secret rollback now stores archived version rows with a generated primary key, preserves the original version timestamp instead of stamping the archive time, and updates rollback metadata through the model serializer path. The frontend also surfaces backend rollback message / error details as an error notification instead of replacing them with a generic "Rollback Failed" response.
  • Legacy Secret Version Rows with Missing UUIDs Are Repaired — Migration 000189_fix_secret_version_null_uuid assigns valid UUIDs to legacy secret_versions rows that had missing or zero IDs, and the SecretVersion model now generates an ID before insert when one is absent. This prevents primary-key collisions and rollback failures on environments that already contain affected history rows.
  • Manual Secret Rotation No Longer Creates Duplicate Local Versions in Staging — The pre_rotation_sync step now compares the external value with the current local secret value before writing a local update. If the values are identical, the step records the pulled value for downstream rotation context without bumping the secret version. This eliminates the staging symptom where one manual rotation produced two new versions and a confusing intermediate system version.
  • Version History Now Shows the Correct Author for the Current Version — The current (active) version row in the Version History modal was displaying the original creator of the secret instead of the actor who last changed it. The service layer now derives the current-version author from the most recent archived history row, which carries the actor of the last update, exactly as all other history rows do. Automated rotations triggered by the scheduler continue to appear as System; manual triggers show the user who initiated the rotation.
  • Certificate Chain Fields No Longer Duplicate the Leaf Certificate — Issuance, import, renewal, ACME completion, and external CA order completion now normalize certificate PEM data at the write boundary. CertificatePEM remains the current certificate, ChainPEM stores issuer certificates only, and PublicChainPEM stores the full leaf-plus-chain bundle. Existing contaminated chain data is defensively normalized during export.
  • ACME Issuance Stores Leaf and Chain Separately — ACME issuance now splits the returned order certificates into leaf-only certificate data and a normalized issuer chain. This prevents full ACME bundles from being stored twice or treated as both the current certificate and the chain.
  • ACME Bridge Finalization Validates CSR Identifiers and Persists Async ADCS Certificates — MazeVault's ACME server now rejects finalize CSRs whose DNS/IP identifiers are outside the ACME order, uses the RFC 8555 certificate URL path consistently, and creates the tracked Certificate row when an ADCS-backed ACME order completes after CA manager approval.
  • External CA Polling Handles DCV, Pending Orders, and Certificate Downloads Correctly — The external CA order poller now counts only resolved orders as completed work, fetches/stores DCV tokens when a provider reports validation is required, downloads certificate bundles for DigiCert and SSLMarket before finalization, and completes request-originated orders atomically across the certificate, queue, request, and agent assignment records.
  • DCV Retry No Longer Resumes Expired Challenges Prematurely — Operator-triggered DCV retry resets expired or stale challenges before re-fetching provider instructions, handles a missing audit service safely in tests/background contexts, and keeps a queue item in pending_dcv when expired tokens remain instead of incorrectly resuming certificate download.
  • ADCS DCOM Responses Accept Numeric and String Statuses — The backend DCOM parser now accepts both legacy numeric ADCS disposition codes and agent responses such as pending, issued, or denied, preserving existing behavior while allowing the agent/backend contract to converge safely.
  • Renewed Certificates Preserve the Backing Internal Signing CA — Renewals that use an internal organization CA account now preserve the backing CertificateAuthority ID on the renewed certificate while continuing to sign through the configured CA account provider. External CA account renewals continue to leave the internal CA field empty.
  • Approval Continuation Audit Events Carry Rotation Context — Automatic rotation continuation after approval now writes audit events with the rotation intent's project and initiating actor when available, improving traceability without changing continuation idempotency or approval enforcement.
  • Internal CA HSM Algorithm Inference Is Covered by Regression Tests — The internal CA HSM signer now uses a cleaner type switch for RSA, RSA-PSS, and ECDSA algorithm inference, with tests covering the expected JWA algorithm mapping.
  • Terraform Write-Only Secret Updates No Longer Replay or Rotate Values During Metadata Changes — Metadata or TTL updates for a Terraform-managed mazevault_secret that uses value_wo now call the backend without a plaintext value unless value_wo_version changes. MazeVault therefore preserves the existing secret material, version history, offload state, external provider state, and propagation behavior during metadata-only applies. Explicit empty secret values are rejected instead of being confused with omitted values.
  • Terraform CA Account Provider Types Are Validated Consistently — The Terraform mazevault_ca_account resource now rejects empty, whitespace-only, mixed-case, and unsupported provider_type values before any backend API call. The accepted canonical values are aligned with backend CA account resolvers that can actually connect, including internal, the rapidssl SSLMarket alias, and venafi; placeholder I.CA/PostSignum and PSD2 integrations remain excluded until they report a connectable backend status. Venafi account-backed provider resolution is now wired through both CA account service paths, so documented Venafi CA accounts no longer fail in the organization CA-account resolver. Existing documented lowercase configurations continue to work unchanged.

Migration Notes

  • Migration 000189_fix_secret_version_null_uuid repairs invalid secret_versions.id values by replacing missing or zero UUIDs with generated UUIDs. The migration is data-repair only and does not change public API contracts.
  • Migration 000190_sync_problem_actions adds the sync:write permission to eligible system roles and creates the sync_problem_dismissals table for durable acknowledgement of current sync failure fingerprints. The migration is idempotent and does not change configuration schema or environment variables.

Notes

  • Upgrade Impact — Version 1.0.59 contains two database migrations: 000189_fix_secret_version_null_uuid for legacy secret version UUID repair and 000190_sync_problem_actions for sync remediation permissions and the per-fingerprint dismissal table. Sync-specific API endpoints are added for current sync failure acknowledgement and blocked seed incident acknowledgement; they require no new environment variables or release/package format changes. Terraform write-only secret values require Terraform 1.11+ when value_wo is used with ephemeral inputs; existing value and generate configurations continue to work. Secret update now accepts metadata/TTL-only requests without creating a new version, which is an API compatibility expansion. Secret rotation view refresh is frontend-only, and the Version History author fix is a backend service layer change with no configuration impact. The certificate chain, export, ACME, external CA DCV, new-request async issuance, CA account settings, renewal, approval-continuation audit, HSM, SDK, and Terraform provider changes add no additional migrations, environment variables, Helm values, or release package format changes. Terraform CA account provider type validation now rejects invalid values earlier; documented lowercase provider_type configurations continue to work. CertificateRequest.status can now expose submitted_to_ca while an external CA request is waiting for DCV/provider issuance, and the new renewal queue DCV endpoints reuse existing certificate RBAC permissions.

Version 1.0.58

Release Date: 2026-07-16

New Features

  • ACME Challenge Policy on Certificate Templates — Certificate templates now carry a challenge_type field (auto, dns-01, http-01, tls-alpn-01, default auto) that controls how ACME validation is performed when the issuing CA account is an ACME provider (Let's Encrypt, ZeroSSL, custom ACME). The policy is enforced at issuance time in the ACME provider: dns-01 requires a configured DNS solver and fails fast without one, http-01 rejects wildcard identifiers (which ACME cannot validate over HTTP), and tls-alpn-01 returns a clear "not implemented" error instead of silently falling back. auto preserves the previous behavior (wildcard → DNS-01 when a solver exists, otherwise HTTP-01). The field is exposed in the Project Templates certificate wizard, in the certificate template detail views, and via the Terraform provider (mazevault_certificate_template.challenge_type). It is ignored for non-ACME issuers (internal CA, DigiCert, ADCS, …).
  • ACME Account Attributes in the Terraform Provider — The mazevault_ca_account resource now supports the ACME account fields email, directory_url, eab_kid, and eab_hmac_key (the HMAC key is marked sensitive/write-only). These map into the backend's encrypted credential store, enabling Terraform-managed Let's Encrypt, ZeroSSL, and custom/EAB-gated ACME CA accounts. The existing api_key/base_url attributes remain for API-style providers. The provider now sends the backend-compatible credentials/display_name contract instead of the legacy generic config payload, and write-only credentials are preserved on read (no spurious drift).

Improvements

  • Single Internal CA — Coherent Lifecycle and Management — The organization's internal Certificate Authority is now managed as a single, first-class object under Organization Settings → Project Settings → Certificate Authorities → Internal Certificate Authority. New endpoints resolve, initialize, and repair the internal CA (GET/POST /organizations/{id}/internal-ca, POST /organizations/{id}/ca-accounts/{accountId}/initialize-ca), and creating a second internal root is rejected (single-CA guard). The UI provides an Initialize Internal CA action (common name, key size, validity) and an Initialize CA repair action for internal accounts that were previously created without CA key material. OCSP/CRL URLs are editable and CRL regeneration is available directly from the internal CA card. Agents continue to distribute the CA certificate to server trust stores unchanged.
  • "Add CA Account" Is Now External-Only — The Add CA Account flow no longer offers an Internal provider option (which previously created a non-functional "hollow" account with no CA key material). Internal CA management lives exclusively in the Internal Certificate Authority section. The internal CA is filtered out of the external CA-account cards to avoid duplicate representation, while remaining fully selectable everywhere else (project integrations, certificate template CA binding, ACME/EAB dialogs).
  • Strict Renewal Enforcement: Template + CA Required — The certificate renewal pipeline now enforces that every certificate must have a TemplateID and the assigned template must have a CAAccountID before renewal can proceed. Certificates without a template or with a template lacking a CA account are rejected with a clear error message at the pipeline entry point. This eliminates the previous multi-level CA fallback chain (cert-level CA, project overrides, internal CA default) and establishes template.CAAccountID as the single source of truth for CA resolution during renewal.

Bug Fixes

  • Internal CA No Longer Reported as "Not Found" When It Exists — The Certificate Authorities tab previously enumerated project-scoped CAs to display internal CAs, so the organization-level internal CA (which has no project binding) was invisible and the section always showed "No internal CAs found." The section now resolves the internal CA through the organization's internal CA account link and displays it correctly, including its OCSP/CRL configuration.
  • OCSP/CRL Configuration Persists for Org-Level Internal CA — Editing OCSP/CRL URLs for the internal CA now uses the CA-scoped endpoint (PUT /ca/{id}) instead of a project-scoped path that could not address an org-level CA (project_id = NULL). OCSP remains internal-CA-only by design — external CAs (DigiCert, Let's Encrypt, ZeroSSL) continue to advertise their own OCSP/CRL responders via the embedded AIA/CDP pointers.
  • Integration Deletion Impact Now Excludes Soft-Deleted Secrets — The raw SQL aggregates behind affectedSecretCount and collectOrphanedSecretIDs filtered only archived_at IS NULL, ignoring GORM soft-deletes (deleted_at). This could inflate the reported "total affected" / orphaned counts relative to every other code path that queries secrets via GORM (which applies deleted_at IS NULL automatically). Both raw queries now also filter deleted_at IS NULL, restoring a single, consistent secret population across the delete result, the deletion-impact endpoint, the audit log, and the per-environment breakdown.
  • Delayed Secret Rotation No Longer Records a Refresh That Did Not Happen — markDelayedSecretReauthorizing set refresh_performed=true in the execution metadata before the single re-authorization call actually ran, so a crash or failure before AuthorizeRotationConfigPublish persisted a misleading refresh_performed=true even though no refresh completed. The placeholder now records refresh_performed=false; the flag is set to true only on the successful re-authorization path. At-most-once semantics are unchanged — the loop guard relies on refresh_attempted + refresh_generation.

Removals

  • ProjectCAOverride Removed — The project_ca_overrides table and all associated code (model, handlers, service method findProjectCAOverride) have been removed. This feature was never exposed via the API router (routes were not registered) and the table was always empty in production. The renewal orchestrator now resolves CA exclusively from the template. Migration 000188 drops the table.
  • buildTemplateFromCert Removed — The synthetic template fallback for certificates without a template has been removed. Certificates must be assigned a proper template before they can be renewed.

Maintenance

  • Dead PKI UI Removed — Three orphaned, unreachable frontend components were deleted after confirming no capability loss: CAManagementTab (its Initialize CA / HSM / CRL capabilities are superseded by the Internal Certificate Authority section) and CertificateTemplatesTab + CertificateTemplateModal (superseded by the variable-driven Project Templates certificate flow, which covers subject DN and SANs via template variables). The live CertificateTemplateViewModal (used by the project Templates tab) is retained.
  • Migration 000188 Header Comment Corrected — The header comment in 000188_drop_project_ca_overrides.up.sql incorrectly referenced "Migration 000187"; it now correctly reads "000188". No functional change.

Migration Notes

  • Migration 000188_drop_project_ca_overrides drops the project_ca_overrides table. This is a non-destructive change as the table was never populated through the application (API routes were never registered).
  • Certificates without a TemplateID that previously relied on the internal CA fallback will now report "no template assigned" in the rotation dashboard and cannot auto-renew until a template is assigned.

Notes

  • Upgrade Impact — Version 1.0.58 adds two database migrations: 000182_add_challenge_type_to_certificate_templates (adds a challenge_type VARCHAR(20) DEFAULT 'auto' column with a CHECK constraint to certificate_templates) and 000188_drop_project_ca_overrides (drops the always-empty project_ca_overrides table). Both migrations are idempotent and backward-compatible; existing templates default to auto and existing ACME issuance behavior is unchanged. No configuration schema changes.
  • Terraform Provider — Existing mazevault_ca_account configurations continue to work; the new ACME attributes are optional. mazevault_certificate_template gains an optional, defaulted challenge_type attribute. The internal CA is intentionally not managed via Terraform — it is managed in the MazeVault UI.

Version 1.0.57

Release Date: 2026-07-07

Bug Fixes

  • Rotation Sync Steps (pre/post_rotation_sync) Now Use Managed Identity Instead of Stale Config Credentials — When the rotation orchestrator's pre_rotation_sync or post_rotation_sync steps resolved Azure credentials via getAzureCredentialFromConfig, the function auto-detected client_secret fields from the encrypted integration config blob even when the integration was configured with background_auth_method=managed_identity. This caused AADSTS7000215 (invalid client secret) errors during rotation sync steps while the identical Key Vault connection succeeded during non-rotation sync paths. The credential helper now prefers system managed identity when auth_method is empty and the system has a configured MI, preventing stale SSO app credentials from being picked up. Additionally, the gateway delegation layer no longer blocks delegation solely because managed identity is present, and delegated sync steps now carry a secret_metadata snapshot so gateway-executed sync steps can resolve the target secret key without requiring a direct database lookup.
  • Gateway Delegation No Longer Blocked by Managed Identity Presence — canDelegateEnvironment() previously returned false unconditionally when the credential factory reported managed identity availability, preventing any Azure KV sync delegation to gateways in environments where MI was configured. This gate has been removed; gateway delegation now relies solely on environment matching and gateway availability, allowing MI-authenticated sync operations to be properly delegated.
  • Orchestrator Mode Rotation No Longer Performs Duplicate Key Vault Push — In orchestrator mode (storage_mode=names_only), the mazevault_secret rotation step previously called SecretService.Update which internally pushed the new value to the external vault via PushSecretToEnvironment. Since post_rotation_sync also pushes the same value to the vault, every rotation produced a redundant duplicate write. The step now calls BumpVersion() (local version counter increment only) in orchestrator mode and defers the sole external write to post_rotation_sync.
  • Orchestrator Mode Rotation Compensate No Longer Fails on Empty EncryptedValue — When a rotation saga rolled back the mazevault_secret step in orchestrator mode, Compensate() attempted to decrypt the previous version's EncryptedValue which is always NULL/empty for names_only secrets. This caused spurious "partially_rolled_back" statuses. The compensate path now returns nil immediately for orchestrator-mode secrets — rollback of the external vault value is handled by PostRotationSyncStep.Compensate which has the per-integration pre-overwrite snapshot.
  • Pre-Rotation Sync No Longer Round-Trips Value Back to Key Vault in Orchestrator Mode — When pre_rotation_sync detected a newer value in Azure Key Vault, it called UpdateSecret which in orchestrator mode pushed the same value right back to the vault (redundant network call). In orchestrator mode the step now captures the KV value as previous_value context for downstream steps without triggering a local DB write or external push.
  • Agent Discovery Report No Longer Silently Drops Metadata Update — ProcessDiscoveryReport() persisted discovered certificates into the database but never updated the agent's own metadata (last_discovery_at, discovered_certificates_count). After a successful discovery run the agent row remained stale, making it impossible for operators to tell from the dashboard whether an agent had ever performed discovery or how many certificates it found. The transaction now concludes with an atomic count query and agent row update so dashboard stats reflect reality immediately after each report.
  • Agent Version Not Persisted from Health Heartbeat — UpdateHealth() stored the full health payload into the health_status JSONB column and refreshed last_seen, but never extracted the agent_version string into its dedicated column. Agents reporting their version via the heartbeat appeared as version-unknown in the UI. The handler now copies agent_version from the health map into the agent model before saving.
  • Empty Environment Dropdown in Bind Agent Dialog — The "Bind Agent" dialog on the Project Integrations tab fetched environment data from GET /projects/:id/environment-settings (which returns ProjectEnvironmentSetting[] with an environment field) but rendered the list expecting Environment[] objects (with slug and name fields). Because the field names didn't match, every dropdown item appeared blank. The dialog now fetches from GET /organizations/:orgId/environments which returns the correct Environment[] shape, populating the dropdown correctly.

Version 1.0.56

Release Date: 2026-07-01

Improvements

  • Release Pipeline Security Gating Expanded — The development CI pipeline now scans all shipped images, including docs and init-certs, and the release pipeline now scans docs as well and fails the release on CRITICAL/HIGH Trivy findings instead of reporting them passively. This closes a blind spot where auxiliary images could ship with unresolved vulnerabilities even when backend, frontend, and ocsp were clean.
  • Base Image and Local Runtime Alignment — init-certs now builds from the pinned Dockerfile.init-certs path in local docker-compose instead of running an ad-hoc alpine:latest bootstrap command. The init-certs image moved to Alpine 3.21 with a package upgrade during build, and the docs runtime stage now refreshes Alpine packages before packaging nginx. This reduces drift between CI artifacts and local runtime behavior and lowers exposure to known OS-package CVEs.
  • Backend Dependency Refresh for Reachable Integrations — Patched Go versions are now pinned for github.com/Azure/go-ntlmssp, github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream, github.com/aws/aws-sdk-go-v2/service/s3, github.com/quic-go/quic-go, and go.mongodb.org/mongo-driver, covering reachable optional paths such as ADCS NTLM authentication, S3 audit archive transport, QUIC stack usage, and MongoDB reconciliation and rotation code.

Bug Fixes

  • Request ID Tracing Normalized Across Middleware and Logs — RequestIDMiddleware now populates both requestID and the legacy request_id Gin context key while continuing to emit the X-Request-ID response header, and environment-resolution warnings now read the canonical requestID key. This removes a traceability gap where some logs emitted an empty request ID even though a request ID had already been assigned.
  • Orchestrator Mode Guard Uses Exact Route Patterns — The orchestrator ingress guard no longer relies on raw URL suffix and substring heuristics. It now matches registered Gin route patterns via c.FullPath() for secret write routes in both /api/v1/secrets and /api/v2/secrets, while avoiding false positives on nested certificate import flows. Direct secret payloads carrying value remain blocked in orchestrator mode, but legitimate certificate-import endpoints are no longer at risk of accidental interception.
  • Entra Selected-App Sync Now Stays Pending Until Scoped Bindings Exist — When an Entra integration is configured with selected_apps scope but no selected application bindings have been saved yet, a manual sync now returns HTTP 409 Conflict instead of being reported as a generic failure. The integration is recorded with last_sync_status = pending, last_sync_error stays empty, and the hourly scheduler logs a skip until at least one selected application is configured. This makes the API and operational status truthful during staged onboarding.
  • Sync Rule Status and Audit Surfaces Now Preserve Execution Provenance — Sync executions now persist a canonical execution record and propagate the resulting execution_id, trigger_source, trigger secret/environment, and aggregate outcome counts into rule status and sync-log metadata. Bidirectional sync also suppresses the immediate echo-push of secrets that were just pulled or left unchanged by the pull stage, reducing pointless external churn while keeping sync_rules.last_sync_status and project_integrations.last_sync_* aligned with the final outcome.
  • Rotation Step 0 (connection_validation) Now Respects Integration background_auth_method — When the rotation orchestrator executes locally (non-delegated path), the TestConnection() method for Azure Key Vault integrations now calls prepareAzureIntegrationRuntimeConfig() before attempting the connection. Previously, stale client_secret credentials in the encrypted integration config would override the configured background_auth_method=managed_identity, causing ClientSecretCredential authentication failed errors during rotation while sync (which already used the correct preparation path) continued to succeed. The fix ensures auth method resolution is consistent across sync and rotation execution paths.

Notes

  • Upgrade Impact — Version 1.0.56 contains no database migrations and no configuration schema changes. Existing API contracts remain stable; the main operational change is stricter release gating, so CI and release jobs can now fail if docs or init-certs images contain unresolved CRITICAL/HIGH findings.
  • Local Development Impact — The first docker compose up --build after upgrading rebuilds the dedicated client-init-certs image instead of downloading a generic Alpine base and running inline package installation. Build time may increase slightly, but local behavior now matches the shipped image path more closely.

Version 1.0.55

Release Date: 2026-06-29

New Features

  • Secret Rotation Targets — Full CRUD management for secret rotation targets is now available via API, frontend, Go SDK, and Terraform. Targets define the deployment destinations (Kubernetes secrets, database password rotation, agent sync, DevOps pipeline variables, cloud vaults) that MazeVault pushes updated credentials to immediately after a rotation completes. The new SecretRotationTargetList component in the Secrets tab lets operators add, edit, and remove targets without leaving the project view. The Terraform resource mazevault_secret_rotation_target enables fully declarative management; the config_json argument accepts a jsonencode()-encoded JSON object describing the target-specific configuration.
  • Internal CA Configuration Update + AIA Backfill — Two new API endpoints allow updating mutable fields on an internal CA account without recreating it: PUT /api/v1/ca/:id and PUT /api/v1/projects/:id/ca/:caId accept ocsp_url, crl_url, and ocsp_responder_cert_id with patch semantics (omitted fields are left unchanged). A companion POST /api/v1/admin/certificates/backfill-aia endpoint retroactively copies the current OCSP and CRL URLs from a CA account into all existing certificates it issued. OCSP URL, CRL Distribution Points, and Issuer Certificate URL extensions are now properly embedded in the x509 template inside InternalCAProvider.SignCSR() for all newly issued certificates.
  • Agent Selection in Rotation Config UI — The SecretPostRotationActionsPanel and SecretRotationTargetEditor now expose a dropdown that allows an operator to bind a specific registered agent to a rotation target. This gives precise control over which on-premise agent executes a deployment step without relying solely on environment-based routing.
  • Certificate Template View Modal — A new CertificateTemplateViewModal component provides a full-detail read-only view of any certificate template (SAN patterns, key usage, extended key usage, validity period, CA account binding) without navigating away from the current page. The modal is accessible from both the organisation-level CertificateTemplatesTab and the project-level TemplatesTab. The view now also surfaces the signature algorithm (falling back to the CA default when unset) and the ACME profile name, and annotates validation-only constraints and external-CA-governed fields so operators understand which settings are enforced by MazeVault versus the issuing CA.
  • Certificate Templates Now Govern Issuance End-to-End — Certificate templates have been promoted from a partially-decorative metadata record into the authoritative governance "cookbook" for certificate issuance across the full lifecycle. Template fields that were previously stored but never applied are now honoured by the signing engine and the CSR generator: max_path_length (with explicit path-length-zero handling for CA certificates), certificate policy OIDs (policy_identifiers / certificate_policies), signature_algorithm, the default common-name template (cn_template), and the subject email. A new Governance tab in CertificateTemplateModal exposes approval requirements, allowed environments, and allowed key sizes, and a dedicated Renewal & Rotation section makes the key-reuse policy independently settable. Template defaults are now normalised on create/update (validity_days → 365, key_size → 256/2048 by algorithm, min/max validity windows) so a template can never persist inert zero values.
  • Project & Config Template View Modals — Two new read-only detail modals, ProjectTemplateViewModal and ConfigTemplateViewModal, bring the same at-a-glance inspection experience already available for certificate templates to project templates (general settings, environment rules, variable schema, protocol configuration) and configuration templates (metadata, definition summary, raw JSON). Both are reachable via a view (eye) action in their respective management tabs.
  • Terraform: Certificate Rotation Config, Entra Rotation Config, and Rotation Resources Data Source — Three new Terraform surfaces complete IaC coverage of the rotation platform: mazevault_certificate_rotation_config manages automatic renewal scheduling and post-rotation action lists for a specific certificate; mazevault_entra_rotation_config manages Entra ID credential rotation settings including days-before-expiry threshold and staged-rotation behaviour; mazevault_rotation_resources is a data source that lists all rotation-managed resources with their current status, filterable by kind and environment_scope.

Environment Identity Reform

  • Environment Identifiers Are Now Stored Verbatim and Matched Exactly — Environment identifiers entered in Organization Settings are the single source of truth. The system now stores them verbatim (case-preserving) and matches them exactly (case-sensitive) everywhere — RBAC, secrets, sync rules, gateway routing, gateway tasks, environment locks, and the frontend. Identifiers that differ only in case (for example developmentA, DeveLopmentB, DeveLopmentC) are treated as distinct environments. The system never silently rewrites, lowercases, trims, or alias-maps what an operator entered.
  • CanonicalizeEnvironment is now validate-only: it rejects empty/whitespace-only, reserved (*, default, all, system, none), too-short/long, and malformed identifiers (allowed shape ^[A-Za-z][A-Za-z0-9_-]{1,31}$), and returns the input unchanged on success.
  • The gateway environment alias map (pro→production, npr→nonproduction, etc.) has been removed from both backend and frontend. SupportsEnvironment(), GetEnvironmentMode(), gateway routing, health-monitor failover, and the frontend environments helper now compare identifiers exactly. The frontend environment helper no longer applies toLowerCase()/token normalization, and all environment selectors source their options exclusively from the organization environment list.
  • Impact: existing customer environment data is uniformly lowercase, so exact case-matching is a no-op for current records — zero RBAC/secret/sync regression. Mixed-case identifiers are supported going forward only.

Improvements

  • Rotation Pre-Execution Probe Completeness (Gateway, Agent, and Entra Runtime) — The rotation orchestrator now verifies every required execution target is reachable before the first mutating step, and returns a structured HTTP 412 error when a rotation cannot safely start. Three new error codes — rotation_blocked_no_gateway, rotation_blocked_agent_offline, and rotation_blocked_runtime_unavailable — are surfaced instead of an opaque 500/409. The probe is now certificate-aware (it resolves CertificateRotationConfig targets correctly), collects all required agent IDs across every agent-bearing workflow step, and is also enforced inside executeCertificateWorkflow. Entra credential rotation runs the same runtime probe in RotateCredentialWithRequest before the lifecycle transitions to rotating, so a blocked runtime never leaves a credential half-rotated. A credential or certificate change that cannot be deployed now fails fast and visibly rather than starting and stalling.
  • Gateway Delegation for Azure Key Vault Sync — Azure Key Vault push and pull sync operations are now delegated to the appropriate gateway when the backend detects that the direct network path to the Key Vault endpoint is not available. Gateway tasks carry the integration configuration and perform the actual Azure SDK calls; the primary backend records only the resulting sync state. The sync rule scheduler start has been deferred to after gateway delegation is fully initialized, preventing transient DNS resolution errors during backend startup.
  • Rotation Config — Target Environment Auto-Fill — When GetConfigBySecretID creates a new rotation configuration for a secret that does not yet have one, target_environment is now automatically populated from the secret's own environment. This eliminates a manual follow-up update step that was previously required before the first rotation could be triggered.

Bug Fixes

  • Gateway Environment Matching Reworked to Verbatim/Exact Identity — Earlier in this release cycle, gateway environment matching relied on a case-insensitive alias map (pro→production, npr→nonproduction, etc.) that could silently map unrelated identifiers together and mutate operator-entered values. This approach has been replaced by the verbatim/exact environment-identity model (see Environment Identity Reform above): SupportsEnvironment() and GetEnvironmentMode() now compare exactly, the alias map is removed, and the previously planned alias-normalization migration 000176 has been neutralized to a no-op (it was never shipped to customers). Forward migrations 000178/000179 remove the lowercase-enforcing CHECK constraints and case-insensitive unique indexes so case-distinct identifiers are accepted.
  • AIA Extensions Not Embedded in Issued Certificates — InternalCAProvider.SignCSR() read ocsp_url and crl_url from the CA record but never wrote them into the x509 certificate template, so every certificate issued by an internal CA had empty Authority Information Access and CRL Distribution Points extensions. Both extensions are now correctly embedded for all new certificates. Existing certificates can be retroactively updated via the new backfill endpoint.
  • Device Fingerprint — Concurrent Insert Race Condition — The anomaly detection service could produce duplicate-key constraint violations when two concurrent authentication requests from the same previously unseen device tried to register its fingerprint simultaneously. The insert path now uses an upsert pattern with an ON CONFLICT DO NOTHING guard to handle concurrent registrations gracefully.
  • Sync Rule Scheduler — DNS Errors at Backend Startup — On backends configured with gateway delegation, the sync rule scheduler's first scheduled execution could fail with DNS resolution errors because it started before the gateway delegation subsystem had completed initialisation. The scheduler start is now deferred until the delegation layer is ready.
  • Terraform Organization Resource — Delete Silently Swallowed Errors — The Delete method in the Terraform mazevault_organization resource returned success even when the backend responded with a non-2xx status code. Backend errors are now surfaced as Terraform diagnostics so terraform destroy fails visibly instead of leaving the resource in an inconsistent state.
  • Certificate Policy OIDs Silently Dropped from Issued Certificates (Go 1.24+) — InternalCAProvider.SignCSR() populated only the deprecated x509.Certificate.PolicyIdentifiers field. Go 1.24+ x509.CreateCertificate emits the Certificate Policies extension from the new Policies ([]x509.OID) field instead, so template-declared policy OIDs were silently omitted from every issued certificate. SignCSR now populates both fields — the modern Policies slice (for current encoders) and the legacy PolicyIdentifiers slice (for older parsers) — so policy OIDs are reliably embedded.
  • Certificate Template Signing & Subject Fields Were Inert — max_path_length, signature_algorithm, certificate policy OIDs, the default common-name template, and the subject email were persisted on certificate templates but never reached the issued certificate or generated CSR. All of these template fields are now applied during signing and CSR generation, closing the gap between what a template declares and what a certificate actually contains.
  • External CA Template Import Persisted Zero validity_days / key_size — Templates imported from external CA providers (ADCS, SmallStep) were stored with validity_days = 0 and key_size = 0, which then surfaced as invalid defaults in the template editor. Imported templates now carry sane defaults (key_size = 2048, valid_days = 365), and the service layer normalises any remaining zero values on create/update as a defence-in-depth backstop.

Database Migrations

  • 000176_normalize_gateway_environments — Neutralized to a no-op (SELECT 1). This migration originally normalized legacy environment aliases to canonical slugs, but the environment identity reform reversed that direction (identifiers are now stored verbatim). Because the migration was never released to any customer, both its .up.sql and .down.sql are now documented no-ops, leaving customer gateway data untouched.
  • 000177_certificate_templates_signature_algorithm — Adds the signature_algorithm VARCHAR(50) column to certificate_templates (idempotent ADD COLUMN IF NOT EXISTS) so templates can pin the issuance signature algorithm. The matching .down.sql drops the column. ExpectedColumns["certificate_templates"] in database_health.go was updated accordingly.
  • 000178_relax_canonical_environment_check — Drops the 11 chk_*_canonical CHECK constraints (added by migration 000125) that required column = LOWER(column) on every environment-bearing column. These constraints would reject case-distinct identifiers, contradicting the verbatim contract; application-side validation (CanonicalizeEnvironment) continues to enforce shape, length, and reserved-word rules. The down migration re-adds the constraints. Idempotent (DROP CONSTRAINT IF EXISTS).
  • 000179_case_sensitive_environment_indexes — Replaces the case-insensitive LOWER() unique guardrails from migration 000122 with an exact, case-sensitive unique index uniq_environments_org_slug (organization_id, slug) and restores plain raw-column lookup indexes for every environment-bearing column. Existing per-project uniqueness (idx_project_env) is already exact. The down migration restores the LOWER() indexes. Idempotent.

Version 1.0.54

Release Date: 2026-06-27

Bug Fixes

  • Entra Credential Watchdog — Stuck Detection Permanently Bypassed by Hourly Sync — The PostgreSQL trigger trg_entra_creds_updated refreshes updated_at = NOW() on every UPDATE to entra_credentials, including the non-state field writes performed by the hourly reconciliation sync (syncedEntraCredentialUpdates). This meant updated_at < now() - 30min was never true for any credential that went through a sync cycle, preventing the watchdog from ever firing. A new rotation_started_at TIMESTAMPTZ column (migration 000175) is set when lifecycle_state transitions to rotating and cleared on rollback or watchdog recovery. The watchdog query now uses rotation_started_at IS NULL OR rotation_started_at < cutoff; IS NULL covers credentials already stuck before the migration was applied.
  • Entra Expiry Monitor — Preload("") GORM Error on Every Hourly Check — checkExpiringCredentials called .Preload("") (empty string) on the GORM query. GORM v2 rejects this with unsupported relations for schema EntraCredential, logging an error every hour and preventing the expiring_soon lifecycle state from being set on credentials that genuinely are expiring. The invalid .Preload("") call has been removed; behavior is otherwise unchanged.
  • Sync Rule Pull — secret_versions_created_by_fkey FK Violation for System Actors — SyncRuleService.executePull calls SecretService.Update with req.UserID = uuid.Nil (no authenticated user). Update unconditionally stored &req.UserID as created_by in the new SecretVersion row; GORM serializes *uuid.Nil as the string '00000000-...' rather than SQL NULL, which violates the non-null FK referencing the users table. The fix adds a nil-guard: created_by is set to NULL when the caller supplies a zero-value UUID, matching the nullable column contract.
  • Azure KV Integration — Synced Secrets Displayed as "Stored Locally" — syncAzureKeyVaultLocal created and updated secrets in the secrets table but never inserted a SecretIntegrationLink row with link_purpose = 'sync_source'. GetSecretSyncStatus therefore returned local_only for every KV-synced secret and the UI showed "Stored Locally" instead of "Synced from KV". The fix inlines the same upsert logic used by SyncRuleService.upsertPullLink directly in IntegrationService using s.db and clause.OnConflict, avoiding a cross-service dependency.

Improvements

  • Role Permissions — agent:admin Added to secret_manager and certificate_manager Roles — Agent management operations (agent:admin) have been added to the secret_manager and certificate_manager system roles (migration 000174). Operators holding only one of these roles can now register and configure agents without requiring a separate admin role assignment.

Database Migrations

  • 000174_agent_admin_for_managers — Grants agent:admin permission to the secret_manager and certificate_manager built-in roles.
  • 000175_entra_credentials_rotation_started_at — Adds rotation_started_at TIMESTAMPTZ to entra_credentials. Includes partial index idx_entra_creds_rotation_started over (rotation_started_at) for the rotating and pending_verification lifecycle states.

Version 1.0.53

Release Date: 2026-06-26

Bug Fixes

  • OCSP — Certificate Existence Not Verified Before Stapling — The OCSP service responded to stapling requests without first confirming that the referenced serial number exists in the local certificate store. An attacker with a forged or recycled serial could have elicited a valid OCSP response for a non-existent certificate. The service now verifies certificate existence before constructing a response, returning Unknown status for unrecognised serials.
  • Rotation Step — PostgreSQL Grants Vulnerable to SQL Injection — rotation_steps/postgres_db.go constructed GRANT/REVOKE statements by string-concatenating the caller-supplied role name and privilege list without quoting. Database role names are now double-quoted using pgx-style identifier escaping, closing the injection path.
  • SSH Keys — ExportPrivateKey Missing Authorization Check — ExportPrivateKey did not verify that the requesting user had ownership or admin rights on the SSH key before returning the encrypted private material. The handler now enforces the same project-membership and ownership check applied to all other SSH key mutation endpoints.
  • SSH Rotation — Incorrect API Response Field Mapping for Key Pairs — The SSH rotation response handler mis-mapped key-pair fields, causing the returned public_key and key_id to be swapped or empty after a successful rotation. The field mapping has been corrected.
  • Keytab Rotation — Unstructured Logging Bypassing Structured Pipeline — Keytab rotation errors were emitted via the legacy log package, bypassing the structured logging pipeline and losing request-scoped fields. All log calls in keytab_rotation_executor.go are now routed through slog.
  • CA Accounts — ca_authority_id Not Persisted on Update — UpdateCAAccount silently discarded ca_authority_id when updating an existing CA account because the field was absent from the GORM Updates map. The field is now included and covered by a new test.
  • CRL / Revocation Schema Drift — OCSP Returns 500 — The revocation_entries and certificate_revocation_lists tables were created by migration 000005 with legacy column names (reason_code, crl_pem) that diverged from the current GORM model fields (revocation_reason, raw_content). GORM appends deleted_at IS NULL to every query; because deleted_at was absent from the production schema the entire CRL/OCSP subsystem returned SQLSTATE 42703 errors. Migration 000173 adds all missing columns with idempotent ADD COLUMN IF NOT EXISTS guards and backfills values from the legacy columns.

Improvements

  • Rotation Trigger — Request Body Optional — The rotation trigger endpoints now accept requests with an empty or absent body; reason defaults to "manual" when not supplied.
  • CA Internal — HSM OCSP Signing Deferred with Explicit Documentation — OCSP response signing via HSM is explicitly deferred; the code path now carries a clearly marked comment and returns a software-signed response in the meantime, preventing silent fallbacks.
  • Feature Flags — Gateway Mode and License Validation Hardened — features.go tightens the evaluation logic for gateway_mode and license-gated features, eliminating a class of false-positive enabled results when the license payload is partially parsed.
  • SRP — MFA Step Sequencing Documented — The SRP session-verification flow now carries an explicit comment documenting MFA step ordering and the rationale for the current TOTP/backup-code sequence.
  • Secret Naming Policy Wizard — Auth Context Threaded to Token Management — The SecretNamingPolicyWizard now passes the authenticated user context through to token-management calls, closing a potential unauthenticated call path in the wizard's save flow.

Database Migrations

  • 000173_fix_revocation_crl_schema_drift — Adds missing columns to revocation_entries (crl_id, revocation_reason, updated_at, deleted_at) and certificate_revocation_lists (raw_content, updated_at, deleted_at). Backfills from legacy columns reason_code and crl_pem. Fully idempotent.

Version 1.0.52

Release Date: 2026-06-23

Bug Fixes

  • Renewal Policy Schema — key_reuse_enabled and validity_duration Missing from Database — The certificate_renewal_policies table was created by migration 000010 using the column name reuse_private_key; the Go model was later renamed to key_reuse_enabled without a companion migration, causing every write that touched the field to fail at the database layer. Migration 000172 adds key_reuse_enabled BOOLEAN NOT NULL DEFAULT FALSE (with a backfill from the legacy reuse_private_key column) and validity_duration INT NOT NULL DEFAULT 0. The same migration converts notify_emails from JSONB to TEXT using a three-step staging approach (add TEXT column → UPDATE via set-returning function → drop JSONB column → rename), working around the PostgreSQL restriction that forbids subqueries inside ALTER COLUMN TYPE … USING.
  • Renewal Queue — certificates.organization_id Column Does Not Exist — GetRenewalQueue filtered WHERE certificates.organization_id = ? but the certificates table only carries project_id; organization is reachable via certificates.project_id → projects.organization_id. The service now performs a second JOIN (JOIN projects ON projects.id = certificates.project_id) and filters on projects.organization_id.
  • SDK and Terraform Provider — notify_days_before []int Semantic Mismatch — The Go SDK (sdks/go/models.go) and Terraform provider (internal/resources/renewal_policy.go) declared a notify_days_before []int field that has never existed in the backend. The backend stores a plain comma-separated string under notify_emails. Both were rewritten to use notify_emails string, and the two new fields key_reuse_enabled and validity_duration were added to align with the live API contract. Acceptance tests that were blocked with t.Skip due to these gaps are now live.

Database Migrations

  • 000172_fix_certificate_renewal_policy_schema — Adds key_reuse_enabled BOOLEAN NOT NULL DEFAULT FALSE (backfilled from reuse_private_key), validity_duration INT NOT NULL DEFAULT 0, and converts notify_emails JSONB → TEXT to certificate_renewal_policies.

Version 1.0.51

Release Date: 2026-06-22

New Features

  • Acknowledge Rotation Failure — Operators can now permanently dismiss a reviewed rotation failure from the Rotation Problems dashboard without deleting the underlying execution record. A new POST /api/v1/dashboard/rotation/failures/{id}/acknowledge endpoint (RBAC: dashboard:write) writes acknowledged_at and acknowledged_by to the execution row. The dashboard query filters acknowledged rows, keeping the view focused on items that still require attention. The UI exposes a "Mark as reviewed" action from the failure detail panel.
  • Entra Credential Stuck Watchdog — A new background watchdog (RecoverStuckEntraCredentials) runs every 5 minutes and resets Entra credentials stuck in rotating or pending_verification lifecycle state for more than 30 minutes. This prevents indefinite stalls caused by backend restarts or goroutine cancellations mid-rotation. Each recovery writes stuck_watchdog_recovered_at to the credential row for auditability.
  • Entra Credential Status — pending_verification — The entra_credentials_status_check constraint was extended to allow the pending_verification value written by RotateCredentialWithRequest after every rotation when downstream propagation targets are configured. Without this, any rotation with propagation would produce a constraint violation and leave the credential in an inconsistent state.
  • CA Account — Assigned Certificates Count — The CA account list and detail responses now include assigned_certificates_count, the number of active certificates issued via certificate templates bound to that CA account. This gives operators an immediate signal of a CA account's active footprint without an additional query.
  • Terraform Provider — User, User Role, Keytab, Project Settings, Shared Secret — Five new Terraform resources complete the provider's coverage of the MazeVault user-management and project-configuration surfaces: mazevault_user (create/read/update/delete of platform users), mazevault_user_role (assign roles to users within an organization), mazevault_keytab (manage Kerberos keytab files attached to a project), mazevault_project_settings (project-scoped configuration knobs), and mazevault_shared_secret (secrets shared across multiple projects).

Improvements

  • Rotation Problems — Acknowledged Filter — The Rotation Problems page and the platform summary service exclude acknowledged executions from failure counts and lists by default, using the partial index idx_rotation_executions_unacknowledged introduced in migration 000170.

Database Migrations

  • 000169_entra_credentials_status_pending_verification — Extends the entra_credentials_status_check constraint to include pending_verification.
  • 000170_rotation_execution_acknowledgement — Adds acknowledged_at TIMESTAMPTZ and acknowledged_by UUID REFERENCES users to rotation_executions. Includes partial index idx_rotation_executions_unacknowledged covering only un-acknowledged rows.
  • 000171_entra_credential_stuck_watchdog — Adds stuck_watchdog_recovered_at TIMESTAMPTZ to entra_credentials. Adds partial index idx_entra_creds_stuck_watchdog over (lifecycle_state, updated_at) for the two in-flight states.

Version 1.0.50

Release Date: 2026-06-21

New Features

  • Rotation Config Templates — A new organization-scoped catalog allows operators to record reusable rotation settings (interval, lead days, retention window, max retries, timeout). Managed via five API endpoints under GET|POST /api/v1/rotation/templates and GET|PUT|DELETE /api/v1/rotation/templates/{id} with RBAC (rotation:read / rotation:write). A dedicated Terraform resource mazevault_rotation_template enables IaC-driven catalog management. Templates are not automatically applied to rotation configurations. The frontend exposes a full CRUD management page at /rotation/templates. Migration 000168 adds the rotation_config_templates table and a template_id foreign key on rotation_configs.
  • Sync Rule — Bidirectional Mode and Conflict Strategy — The SyncRuleModal now exposes configurable sync direction (pull / push / bidirectional) and sync mode (incremental / full_sync) fields. Both attributes are persisted server-side and documented in the new Terraform resource mazevault_sync_rule. Pull-sync operations now write last_pulled_at, link_purpose, and external_secret_path into secret_integration_links (migration 000167), enabling the synced_from_external status badge on secrets that were pulled from an external provider.
  • Dry-Run for Certificate Rotation — The Certificate Rotation tab now includes a "Dry Run" button alongside the existing "Trigger Rotation Now" action. Dry-run executes the full rotation workflow validation — including post-rotation action preflight and gateway probing — without committing any changes. The dry_run flag is propagated end-to-end through the API and all rotation steps.
  • Post-Rotation Action — Gateway Pinning and Environment Override — PostRotationAction now accepts an optional gateway_id field that pins the action to a specific gateway, bypassing environment-based routing. A companion target_environment field overrides the environment context used when resolving the target. Both fields are available in the Go SDK (PostRotationActionWF) and in the Terraform mazevault_rotation_workflow resource.
  • Terraform Provider — Sync Rules and Rotation Templates — Two new Terraform resources cover the full create/read/update/delete lifecycle: mazevault_sync_rule (sync direction, conflict strategy, key transform, path prefix) and mazevault_rotation_template (interval, lead days, retention, retry policy). Both are registered in the provider and documented in docs/resources/.
  • Rotation Problems — Failed Executions Detail View — The Rotation Problems page now renders a detailed failed-execution table with per-resource drill-down, a pie-chart distribution by resource kind, and a missing-secrets breakdown. Navigation from the sidebar highlights the section with a warning icon.

Improvements

  • Distributed Sync Semaphore via Redis — SyncRuleService now uses a Redis-backed distributed lock (SET NX PX) through a new TryAcquireLock / ReleaseLock pair on CacheService. When Redis is unavailable the service falls back to an in-process sync.Map lock. This prevents duplicate concurrent syncs for the same rule in multi-replica deployments.
  • Dry-Run Guard in All Rotation Steps — All seven built-in rotation steps (password generator, Azure Key Vault, Spring Actuator, agent command, shell script, IIS recycle, Kubernetes secret) now check the dry_run flag before making any mutating external call and return a preview result instead.
  • Connection Validation Scope Extended — The connection_validation rotation step was extended to cover non-database provider types (Azure Key Vault, Kubernetes, LDAP, GitHub, GitLab, generic webhook), ensuring pre-execution connectivity checks are not silently skipped for secrets backed by those integrations.
  • names_only Secret Rotation Guard — The rotation orchestrator now rejects rotation requests for secrets with storage_mode = names_only with a clear error message, preventing misleading execution records for secrets whose value lifecycle is managed entirely by the external provider.
  • Secret Sync Status — synced_from_external Indicator — GetSecretSyncStatus now returns synced_from_external when a secret has been successfully pulled from an external provider. The Secrets tab surfaces this as an informational "Synced from KV" badge.
  • Compliance Report — Deployment and Sync Coverage Stats — PasswordPolicyStats is extended with secrets_with_deployment_target, secrets_with_external_sync, and secrets_with_template counters. The compliance report service gathers these in a single additional DB pass.
  • Gateway Task Service — Direct Gateway ID Pinning — GatewayTaskService.CreateTask now accepts an optional GatewayID that, when set, skips the routing-service lookup entirely and dispatches the task directly to the named gateway. This supports pinned post-rotation actions without routing ambiguity.
  • Code Readability — Internal service and model struct fields were reformatted for consistency. resolveRotationResourceIncidentTarget parameters were simplified, removing redundant intermediate variables.

Bug Fixes

  • Dashboard Dismiss — TypeScript Type Narrowing — The dismiss handler for actionable rotation items now correctly narrows item.status from 'overdue' | 'failed' | 'running' to the server-accepted 'overdue' | 'failed' union before sending the dismiss request, fixing a TypeScript compile error introduced when 'running' was added to the item status type.
  • Rotation Template Routes Missing After Refactor — The five /api/v1/rotation/templates route registrations were inadvertently removed during a code-formatting pass. All five routes have been restored with RBAC middleware (rotation:read / rotation:write).
  • Swagger Path Drift for Rotation Templates — Swagger @Router annotations in handlers_rotation_templates.go incorrectly used /rotation-templates instead of the actual gin route path /rotation/templates. All five annotations were corrected and the OpenAPI spec was regenerated.
  • Audit Logging Missing on Secret Link Operations — CreateSecretLink and DeleteSecretLink handlers now emit SECRET_LINK_CREATED and SECRET_LINK_DELETED audit log entries with the correct resource type, ID, and caller IP.

Database Migrations

  • 000167_secret_integration_links_pull_tracking — Adds last_pulled_at TIMESTAMPTZ, link_purpose VARCHAR(64), and external_secret_path VARCHAR(512) columns to secret_integration_links. Includes a unique constraint uq_sil_secret_integration and a partial index for active links.
  • 000168_rotation_config_templates — Adds the rotation_config_templates table with full CRUD columns (name, description, org scope, default flag, rotation interval, lead days, retention days, retry policy, timeout). Adds template_id UUID REFERENCES rotation_config_templates to rotation_configs.

Version 1.0.49

Release Date: 2026-06-20

New Features

  • Domain Control Validation (DCV) for External CA Orders — External CA providers (DigiCert CertCentral v2, SSLMarket) that require domain ownership proof before issuing a certificate now have full DCV lifecycle support. A new certificate_dcv_tokens table (migration 000166) stores per-domain challenge tokens. The backend automatically fetches tokens after order submission, transitions the renewal queue item to pending_dcv, and polls every 5 minutes. The frontend DCVStatusPanel component shows per-domain challenge details (DNS TXT record name/value, HTTP file path/content, or approver email) with copy-to-clipboard buttons, a progress bar, and a manual Retry Verification action. New API endpoints GET /api/v1/renewal-queue/{id}/dcv and POST /api/v1/renewal-queue/{id}/dcv/retry expose the DCV state with RBAC (certificate:read / certificate:write).
  • ADCS Pending-Approval — Non-Blocking DCOM Submission — The ADCS DCOM method no longer blocks the HTTP handler goroutine with a 5 × 5 s retry loop when the CA manager has not yet approved a request (ADCS status 5). It now returns ErrADCSPendingApproval immediately; the existing ADCSApprovalPoller background job handles asynchronous retrieval once the request is approved.
  • Certificate Request — Advanced Subject DN Fields — The Request Certificate modal now exposes optional Subject DN fields: Organisation (O), Organisational Unit (OU), Locality (L), State/Province (ST), and Country (C). Populated values are sent as a structured requested_subject object in the CSR request payload.
  • Certificate Request — Key Algorithm Override — For MazeVault-generated key pairs, operators can now override the key algorithm (RSA, ECDSA, Ed25519) and key size directly in the Request Certificate modal. The selection is sent as requested_key_algorithm and requested_key_size in the CSR payload.
  • Actionable Rotations Dashboard Panel — The Overview dashboard now includes a dedicated "Rotation operations" panel that surfaces overdue and failed rotation resources requiring immediate attention. Each item shows kind, project, and status badge, and supports a one-click Dismiss flow with audit note. Up to five items are shown, prioritized by age (overdue) and recency (failed).
  • Dashboard Expiring Certificates — 30-Day Filter — The "Direct actions" section of the Overview dashboard now filters expiring certificates to the next 30 days, making the list immediately actionable instead of showing all future expiries. The section is titled "Expiring certificates (next 30 days)". The GET /api/v1/dashboard/certificates/expiring endpoint accepts a new optional days query parameter (1–3650) to scope results; omitting it returns all future-expiring certificates (backward-compatible).
  • Entra Credential Staged Rotation and Dry-Run — Entra credential rotation now supports staged rotation (staged_rotation_enabled) with a configurable soak window (soak_window_hours). Both fields are persisted in the rotation resource metadata and round-trip correctly through the rotation config GET/PUT cycle. A dry-run path validates the rotation workflow against the live Entra tenant without applying any changes.
  • Entra Integration — Instance ID and Protocol Versioning — Entra integrations now carry an instance identifier and a protocol version field, enabling more precise targeting and future compatibility in multi-tenant and delegated execution scenarios.
  • Rotation Scheduler — Stuck-Item Recovery — The rotation scheduler now detects resources stuck in a running or approval-pending state beyond a configurable deadline and resets them to a retriable state, preventing silent indefinite stalls. Renewal orchestration also gained explicit coordination logic for resources transitioning through the renewal window.
  • Certificate Rotation — Next Due Date Calculation — Certificate rotation due-date logic is now covered by dedicated unit tests. The next due date is computed from effective lead-day policy across certificate, template, and CA-account layers, ensuring consistent renewal window behavior.

Improvements

  • Dashboard Data Fetching Resilience — Dashboard data fetching now uses Promise.allSettled instead of Promise.all. Individual widget failures (network error, 401, 404) no longer discard data from all other widgets. Each failure is logged via the structured logger for observability.
  • RBAC Permissions for Rotation Endpoints — Rotation resource read and write permissions are now consistently applied across all new dashboard and rotation endpoints, including archived-resource filtering in service-layer queries.
  • Dashboard Service Archived Resource Exclusion — Platform rotation summary and actionable rotation queries now explicitly exclude archived resources, preventing historical data from inflating overdue or failed counts.

Bug Fixes

  • Entra Rotation Config Edit — Disabled State Regression — Editing an existing Entra credential rotation configuration no longer shows all fields as disabled for non-primary credentials. The GET response now returns rotation_enabled from the stored user preference (metadata.rotation_enabled) instead of from the scheduler gate (resource.Enabled = RotationEnabled && IsPrimary), which was always false for non-primary credentials.
  • Entra Rotation Config — Staged Fields Not Restored on Edit — staged_rotation_enabled and soak_window_hours are now included in the rotation resource projector metadata and are therefore correctly restored when opening an existing rotation configuration for editing.
  • Entra Scope Binding — Tenant ID Not Required — Creating an Entra scope binding no longer requires tenant_id when the integration uses managed identity authentication. The backend validation and the frontend wizard were updated to treat tenant_id as optional for managed identity flows.

Infrastructure

  • Step-CA Network Policy — Added network policy rule to allow inbound traffic to Step-CA on port 9000 from within the cluster, unblocking staging ACME issuance flows that route through the internal Step-CA provisioner.

Version 1.0.48

Release Date: 2026-06-16

New Features

  • PFX Compatibility Profile Options — Certificate PFX export now supports selecting a compatibility profile (legacy / modern) that controls the encryption and MAC algorithms used in the PKCS#12 container. legacy targets OpenSSL 1.x, Java KeyStore tooling, and older Windows endpoints; modern targets OpenSSL 3.x, .NET 6+, and current Go runtimes.

Improvements

  • Log Redaction — Original Request URL Preserved — The log redaction middleware now preserves the original RequestURI on the Gin context before any path rewriting or normalization occurs. This ensures audit and structured log entries reflect the URL the client actually sent, not a post-processing artifact. A regression test covers the URL-preservation contract.
  • Binary Download — ArrayBuffer and Delayed URL Revocation — Certificate and agent binary downloads now use arraybuffer response type and delay object URL revocation until the download has been reliably delivered to the browser, eliminating a race condition on slow connections where the revocation fired before the download completed.

Dependency Updates

  • form-data and hasown — Updated form-data and hasown to their latest patch releases, resolving minor compatibility warnings with current Node.js runtimes.

Version 1.0.47

Release Date: 2026-06-14

New Features

  • Shared Entra Rollout Actions and Readiness Checks — Entra credential rotation now supports ordered rollout actions through the shared rotation platform, including Azure Key Vault, Kubernetes Secrets, agent-managed runtime files, Spring refresh/webhook delivery, and IIS app pool recycle on Windows targets. Dry-run and preflight checks validate the same runtime paths before production execution.
  • Per-Certificate Renewal Key Policy — Certificate operators can now choose per certificate whether renewal regenerates a new private key or reuses the existing key material where the provider and custody path allow it. Target processing order is now preserved explicitly in the renewal workflow.
  • Stronger Entra Integration Authentication Validation — The integration wizard and backend now validate interactive and background Entra authentication settings together, preventing save-time acceptance of configurations that cannot support background sync, dry-run, or post-rotation execution.

Improvements

  • Unified Rotation Readiness in the Project Hub — The Project Rotations view now shows inline readiness and preflight state for Entra credentials, certificate renewal resources, and certificate deployment resources, including clearer manual review required surfacing for rollout targets that cannot be probed automatically.
  • Role and Contract Alignment for Rotation Operations — RBAC migrations and project-surface permission checks were aligned with the expanded rotation and configuration surfaces, reducing cases where operators could open a screen without the permissions needed to complete the workflow.
  • Swagger and Operational Signal Refresh — Generated Swagger output now reflects the updated severity vocabulary and improved host handling, while certificate and database health checks expose stronger diagnostics for rotation readiness and export paths.
  • Secret Rotation API Contract Refresh — The secret rotation API and generated Swagger now document the explicit create/edit/delete lifecycle more accurately, including persisted post-rotation actions and the clean reset path after a secret rotation is removed.

Bug Fixes

  • Entra Lifecycle Cleanup Consistency — Deleting an Entra integration or a locally managed app registration now removes associated Entra credential rotation resources in the same lifecycle operation. Upgrade migration 000155 also removes historical orphan rows, and repository reads defensively hide stale entra_credential entries that predate the cleanup.
  • Certificate Rotation Defaults on Import — When an imported certificate is eligible for managed renewal, MazeVault now prepares the rotation configuration without silently enabling it. Initial lead days are resolved from the effective certificate, template, and CA-account policy instead of being masked by the model default.
  • Rollout Validation and Export Robustness — Validation for agent_id versus direct agent_url rollout steps is stricter, and certificate export flows now return clearer PFX and private-key failures instead of partially masked errors.
  • Secret Rotation Delete and Recreate Semantics — Deleting a secret rotation now removes the rotation config, canonical secret rotation resource, and linked rotation integrations without deleting the secret itself. After deletion, secret status surfaces show Rotation not configured, and reopening the rotation modal stays in create mode until the operator explicitly saves a new configuration.

Version 1.0.46

Release Date: 2026-06-09

New Features

  • External CA Order Poller Baseline — MazeVault added background polling for externally issued certificate orders, allowing asynchronous CA workflows to complete and update the certificate lifecycle without manual tracking.

Notes

  • Release Note Clarification — The Entra rotation rollout, certificate rotation UI unification, and related readiness and preflight work that had previously been drafted for v1.0.46 landed after the tag and are therefore documented under v1.0.47.

Version 1.0.45

Release Date: 2026-06-09

New Features

  • Rotation Resource Registry and Platform Ownership Expansion — The rotation platform is expanding from certificate-only orchestration into a registry-backed resource model. New control-plane services introduce resource kinds, target registries, and shared ownership for rotation scheduling, making future secret, certificate, and Entra rotation flows converge on the same platform primitives.
  • Agent Discovery Policy Bundles and Persistent Config Indexing — Agents now receive backend-driven discovery policy bundles built from active configuration templates. Filesystem discovery persists a local metadata index and reuses cached config and certificate findings across runs, reducing repeated scans while keeping policy-version changes authoritative.
  • Configuration Discovery Submission — Agents can now submit configuration discovery findings back to the backend, including classification and rewrite-plan metadata, using the existing discovery result model.
  • Certificate Template Code Deduplication and Conflict Resolution — Organization certificate template management now deduplicates template codes and blocks ambiguous conflicts. Upgrade migrations normalize existing duplicates so template routing and issuance remain deterministic.
  • System Certificate Classification — Certificates now carry an is_system flag for internal mTLS and identity-management material. MazeVault can retain these certificates for platform use while keeping them separate from normal operator-facing inventory.
  • Integration Groups — Projects now support named integration groups that map logical groupings to integration targets with structured JSONB configuration. Groups can be created, listed, updated, and deleted via the new /api/v1/projects/{id}/integration-groups and /api/v1/integration-groups/{id} API endpoints (migration 000152).
  • Real Slack Incident Notifications — Incident management now dispatches real-time Slack notifications via a configured Incoming Webhook. When an incident is raised on a project with a Slack integration, the platform posts the incident type and resource name to the configured channel.
  • GCP Secret Manager and Kubernetes Connectivity Tests — Integration health checks now validate live GCP Secret Manager API access (listing secrets in a given project) and Kubernetes API server reachability using in-cluster or kubeconfig credentials, in addition to the existing provider tests.
  • HSM Key Operations — HSM providers (PKCS#11, AWS CloudHSM, GCP Cloud HSM, Azure Managed HSM) now expose full key lifecycle operations: GetPublicKey (reconstructs RSA/EC PKIX DER from hardware), DeleteKey (destroys all key objects on the device), ListKeys (enumerates all managed key handles), and GetKeyInfo (returns key type, size, and extractable flag).
  • Vault PKI Full Lifecycle — HashiCorp Vault CA provider now implements the complete CA interface: RenewCertificate, GetCertificateStatus (checks revocation timestamp), ListIssuedCertificates (enumerates via PKI list endpoint), GetOrderStatus, and CancelOrder (revokes the underlying certificate).
  • ADCS Deferred Certificate Retrieval — The ADCS (WCCE) provider now fetches certificates that were issued asynchronously by the CA via a SOAP Renew request. Previously pending certificates are retrieved and returned in PEM format once the CA completes issuance.
  • Venafi VaaS Full Lifecycle — Venafi CA provider now implements RenewCertificate, GetOrderStatus (retrieves pick-up status from VaaS), and CancelOrder.
  • DigiCert Rate Limit Reporting — The DigiCert provider now queries live rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset) and surfaces them through the GetRateLimits interface.
  • Config Injection with Live Secret Values — Configuration versioning now resolves ${VAULT:<placeholder>} tokens against live MazeVault secret values at snapshot time, so injected config files carry real credentials without storing them in the config version history.

Improvements

  • Project-Aware Resource Visibility Policies — Azure discovery endpoints now apply resource_visibility_policy filtering in the context of the selected project. This keeps Integration Wizard choices aligned with project scope for non-admin operators instead of exposing subscription-wide results.
  • Configuration Management Auditability — Configuration management flows now record audit-aware rewrite-plan activity so operational changes are traceable alongside the rest of the platform's lifecycle events.
  • Generated CSR and Key Custody Metadata — Certificate request flows now carry generated-CSR and private-key custody metadata forward, improving visibility of whether MazeVault owns, reuses, or must preserve external key material in subsequent lifecycle actions.
  • Delegated Agent Secret Sync Step — Rotation and execution pipelines now have an explicit agent-side secret synchronization step, improving parity between platform orchestration and what managed agents actually apply downstream.
  • Certificate Owner-Surface Navigation — Certificate links from the main dashboard, project rotations, and the certificate inventory now resolve directly to the owning surface. Project-owned certificates open their project detail view, while unassigned inventory opens the Discovered Certificates owner surface via managed_certificate_id, replacing the legacy duplicate /certificates flow.
  • Secret Dashboard Data Consistency — The Secrets Overview tab now uses the shared dashboard API, restoring missing-secret drill-ins and keeping overview counts and decrypted names aligned with the main dashboard data layer.
  • Discovered Certificate Lookup Filter — Discovery APIs and generated Swagger documentation now support managed_certificate_id filtering, enabling targeted deep links from certificate overview tables to the matching discovered certificate record.

Bug Fixes

  • Rotation Scheduling Ownership Cutover — Renewal scheduling is moving to the shared rotation platform owner model so follow-up workflows operate on the current certificate resource instead of stale pre-renewal anchors.
  • Discovery Project Context Consistency — Integration discovery helpers now consistently pass project context, avoiding empty or over-broad Azure discovery responses when resource visibility filtering is enabled.
  • Detect Drift Authentication — POST /api/v1/secrets/drift/detect now executes inside the authenticated secrets route group, fixing 401 failures caused by missing auth context on drift-detection requests.
  • Dashboard Drill-In Regressions — Fixed stale certificate links that still pointed to the removed global certificates page and restored missing-secret links in the Secrets Dashboard overview.

Version 1.0.44

Release Date: 2026-05-25

New Features

  • Certificate Rotation Post-Actions — Certificate rotation now supports explicit post-rotation action chains, including webhooks and other registered post-processing steps, so operators can trigger downstream refresh and rollout workflows after a successful renewal or deployment.
  • Delegated Renewal and Rotation Publish Controls — Renewal orchestration gained delegated execution paths, runtime probes, and publish-gate safeguards that better separate readiness validation from side-effecting deployment work.
  • Archive Center Lifecycle Enforcement — Archive restore and permanent delete flows are now treated as centralized archive-lifecycle operations with admin-only enforcement across runtime RBAC and role editing.
  • Expanded Agent Binary Coverage — MazeVault agent distribution now includes additional ARM64 coverage and stronger build validation for heterogeneous Windows and Linux estates.

Improvements

  • Discovered Certificate Key Custody Tracking — Adopted discovered certificates now preserve explicit key-custody state, preventing MazeVault from silently taking ownership of private keys during renewal or deployment flows.
  • Certificate Issuance and Renewal Robustness — Certificate issuance, CSR handling, and renewal flows now use stronger locking, normalized post-action payloads, and clearer actor attribution across cross-project secret operations.
  • CA Account Provider Coverage — CA account and EAB credential handling now covers more provider initialization paths, including Smallstep-specific fallback fields used during account creation and validation.

Bug Fixes

  • Smallstep CA Account Initialization — Smallstep CA account creation now correctly falls back to credential fields such as server_url and provisioner metadata instead of failing before contacting the CA.
  • Rotation Action Contract Alignment — Certificate post-action handling now exposes only action types supported by the certificate rotation executor, avoiding invalid UI selections and mismatched step names.

Version 1.0.43

Release Date: 2026-05-11

New Features

  • Azure Key Vault Managed Identity Support — Azure Key Vault integrations now support Managed Identity and Workload Identity as authentication methods in addition to service principals. When Azure Key Vault is selected in the Integration Wizard, the background sync auth method defaults to managed_identity. Configure a specific managed identity client ID via AZURE_MANAGED_IDENTITY_CLIENT_ID, or leave it unset to let the platform resolve the bound identity from the environment (AZURE_FEDERATED_TOKEN_FILE + AZURE_CLIENT_ID + AZURE_TENANT_ID for workload identity).
  • Agent Systemd Service Installation — The agent installer script now deploys the MazeVault agent as a proper systemd service. A dedicated mazevault-agent system user is created automatically, and locked-down directories are provisioned (/etc/mazevault, /var/lib/mazevault, /var/log/mazevault) with ownership and permissions (0750). The service is configured with automatic restart on failure. Existing deployments should re-run the deployment script to receive the updated service unit.
  • Agent Binary Download Endpoint — A new deployment-scoped endpoint GET /deployments/:id/agent-binary serves the agent binary for a given deployment. The deployment UUID acts as the authentication token (unguessable 128-bit identifier), consistent with the /script and /config endpoints. Legacy agent binary download rewrite rules in the reverse proxy have been removed.
  • Redis Multi-Endpoint Support — Redis connection handling now supports multiple endpoint addresses for high-availability and fallback setups. Configure the primary endpoint via REDIS_URL and one or more fallback addresses via REDIS_FALLBACK_URLS (comma, semicolon, or newline-separated). The client automatically falls back to the next available endpoint on connection failure, and switches to an in-memory fallback if all Redis endpoints are unavailable.
  • Password Complexity Policy in Organization Settings — Organization Settings now exposes a dedicated Password Complexity Policy editor. Rules are enforced at password change time (minimum length, character class requirements, history depth). On upgrade, organizations are automatically migrated from the legacy secret_complexity_policy field.

Improvements

  • Entra Credential Rotation History — Entra ID credential rotation operations now produce a persistent rotation history record for each event. The history captures the rotation type, old and new key identifiers, workflow and execution IDs, step-by-step progress, final status, error details if applicable, and elapsed duration. This enables full lifecycle traceability for Entra credential rotations.
  • RBAC Permission Alignment — Role permissions updated to match the agreed product policy:
    • secret_manager — gains keytab:read and config:read (KeyTab Management and Configuration Management pages are now accessible)
    • certificate_manager — gains config:read (Configuration Management page is now accessible)
    • auditor — keytab:read removed (KeyTab Management is no longer visible to auditors; assign explicit keytab:read where audit access to keytabs is required)
  • Admin Credential Reference Types Expanded — Database admin credentials now support two additional reference types: mazevault (credentials stored as MazeVault-managed secrets) and external (generic external references). Full set of supported types: internal, mazevault, external, keyvault, aws_sm.
  • Secret Sync Async Seeding with Status Summary — Sync seed operations now run asynchronously and return immediately with a progress reference and detailed status summary (secrets created, updated, skipped, failed). Blocked seed incidents are now surfaced on the Conflict Resolution page.
  • Certificate Issuance Linked to Agent — Certificates issued to a registered agent are now linked to the issuing agent record at issuance time, connecting the certificate lifecycle to the agent in the dashboard and rotation orchestration.
  • Security Enhancements — Bootstrap and KeyTab Handlers — KeyTab update and delete handlers now enforce organization ownership checks (IDOR prevention): access to a keytab belonging to a different organization returns 404. The bootstrap password change endpoint is locked after initial bootstrap completes and rejects further calls with 403 Forbidden. A DAST scan workflow has been added to the CI pipeline.
  • License Renewal Contact Updated — The license renewal contact email is now info@mazevault.com across all notification banners and expiry modals.

Bug Fixes

  • SSO Provider Modal: Provider ID Preserved on Save — Fixed a bug where saving an existing SSO provider configuration dropped the provider ID, causing duplicate provider creation on subsequent saves.
  • Password Complexity Policy Backfill — Organizations that had configured a secret complexity policy via the legacy secret_complexity_policy field were incorrectly evaluated as having no password policy, causing false-positive PCI-DSS 8.3 compliance failures. The policy is now automatically propagated to the dedicated password complexity field on upgrade. Additionally, organizations without any complexity policy now receive a secure default policy on upgrade (16-character minimum, all character classes required, 10-entry history, 30-day rotation interval).

Version 1.0.42

Release Date: 2026-05-11

New Features

  • Certificate Metadata Fields — Certificates now support three user-editable metadata fields accessible from the certificate detail view and via PUT /api/v1/certificates/:id:
    • Tags — Free-form string labels for grouping and filtering certificates. Tags set at import time are now persisted and remain editable after import. Send an empty array to clear all tags.
    • Documentation URL — Optional link (http/https) to external documentation, runbooks, or CMDB entries for the certificate. Maximum 500 characters.
    • Notes — Short free-text annotation (maximum 256 characters). Send an empty string to delete the note.
    • All metadata field changes are recorded in the audit log.

Improvements

  • Environment Canonicalization Enforced at Task Creation — Gateway task creation now enforces environment slug canonicalization. All gateway tasks are stored with lowercase environment slugs regardless of caller casing, building on the canonicalization infrastructure introduced in v1.0.41. The MAZEVAULT_ENV_CANONICAL_ENFORCE variable (set to true to fail-close on non-canonical inputs) applies to task creation as of this release.

Version 1.0.41

Release Date: 2026-04-26

Improvements

  • Documentation Sync — Comprehensive documentation update aligned with v1.0.41 codebase. Environment variable reference expanded with five new sections: License/Organization Registration, Orchestrator Mode, ACME DNS-01, KeyTab Management, and Agent Binary Proxy. All existing sections updated with previously missing variables.
  • Office 365 Email Variable Corrected — Fixed incorrect variable name O365_ENABLED → O365_EMAIL_ENABLED throughout documentation. Full Office 365 authentication configuration documented (client secret, certificate, and managed identity methods).
  • KeyTab API Reference — Full API documentation published for all 14 KeyTab management endpoints. See KeyTab API.
  • Reports API Reference — Full API documentation published for the Weekly Expiry Reports endpoints. See Reports API.
  • Platform Version Sync — All documentation pages updated to reflect current platform version.

Version 1.0.40

Release Date: 2026-04-25

New Features

  • OIDC Nonce Enforcement — New MAZEVAULT_ENFORCE_OIDC_NONCE flag enables strict nonce validation on OIDC tokens. When set to true, tokens without a valid nonce claim are rejected, providing protection against token replay attacks. Recommended for all production deployments.
  • Agent Trust Store Controls — New environment variables MAZEVAULT_AGENT_INSTALL_CHAIN_TO_TRUSTSTORE and MAZEVAULT_AGENT_TRUST_STORE_PATH control whether the MazeVault agent installs the internal CA certificate chain into the operating system trust store, and allow overriding the default trust store path on Linux.

Improvements

  • Certificate Rotation Target Sync Status — GET /api/v1/certificates/:id/targets/:targetId/status now returns full per-step result details, making failed target synchronizations easier to diagnose.
  • Gateway Registration Stability — Improved retry logic for bootstrap token exchange reduces failed registrations caused by transient network issues during first-time gateway setup.
  • OCSP URL Validation — OCSP_URL backend variable now validates URL format at startup to prevent misconfiguration from silently causing OCSP failures.

Bug Fixes

  • Corrected AGENT_VERSION=latest resolution to always fetch the highest tagged release version rather than the most recent commit.
  • Fixed display overlap in the certificate import modal under Orchestrator Mode when both keytab and private key sections were visible simultaneously.

Version 1.0.39

Release Date: 2026-04-22

New Features

  • Agent Binary Distribution Control — New configuration variables provide granular control over how agent updates are distributed across the fleet. AGENT_ROLLOUT_PERCENTAGE limits what percentage of agents receive update notifications (0–100), enabling staged rollouts. AGENT_MAX_CONCURRENT_DOWNLOADS caps parallel binary download streams to prevent network saturation.
  • Agent Binary Proxy — MazeVault can now proxy agent binary downloads from a private GitHub release repository, removing the requirement for agent hosts to reach the public GitHub Releases endpoint directly. Configure via AGENT_BINARY_GITHUB_TOKEN, AGENT_BINARY_CACHE_DIR, AGENT_DOWNLOAD_BASE_URL, and AGENT_VERSION.
  • Primary Backend Environment Seeding — New MAZEVAULT_PRIMARY_ENVIRONMENTS variable pre-seeds the list of environments served directly by the primary backend on first startup, simplifying initial deployment configuration of multi-environment setups.

Improvements

  • KeyTab Dashboard Refresh — Fixed cipher compliance breakdown chart not updating after importing a keytab with deprecated ciphers.
  • Weekly Report Multi-Channel Reliability — Resolved a scheduling race condition that could silently drop one delivery channel when multiple channels were all enabled simultaneously.

Bug Fixes

  • Fixed gateway heartbeat timestamp not updating correctly following a network partition recovery.

Version 1.0.38

Release Date: 2026-04-19

New Features

  • KeyTab Management — Full Kerberos Lifecycle — Enterprise-grade Kerberos KeyTab management with complete lifecycle support. Import, discover, and manage keytab files across your infrastructure. Key capabilities include:
    • Import & Parse — Import MIT Kerberos v2 keytab binary files with automatic extraction of principals, realms, key version numbers (KVNO), and encryption types. Supports base64-encoded upload.
    • Agent Discovery — Agents automatically discover .keytab files on managed hosts, reporting file path, permissions, owner, and encryption type fingerprint. Discovered keytabs can be imported into managed inventory with a single action.
    • Cipher Policy Enforcement — Define organization-level cipher policies specifying allowed and deprecated Kerberos encryption types. Three enforcement modes: audit (report only), warn (allow with warning), block (prevent non-compliant keytabs). Default policy blocks legacy ciphers (DES, RC4-HMAC) while allowing modern AES and Camellia ciphers.
    • Version History — All keytab updates create immutable version records with change reason tracking for full audit compliance.
    • Dashboard & Analytics — Dedicated KeyTab dashboard showing total/active/expired counts, cipher compliance breakdown (compliant/warning/critical), expiry forecasts, and cipher type distribution.
    • Orchestrator Mode Support — In Orchestrator Mode, keytab binary data is offloaded to an external provider; only metadata is stored locally.
  • Weekly Expiry Report — Automated weekly reports showing certificates and secrets expiring within 60 days, delivered to multiple channels simultaneously:
    • Email — HTML-formatted report to configured recipient list
    • Slack / Microsoft Teams — Webhook-based notifications with expiry summaries
    • JIRA — Automatic issue creation with expiry details for tracking
    • Generic Webhook — HTTP POST with full report payload for custom integrations
    • Reports can be previewed before sending and triggered manually on demand.
  • Local Gateway Registration — The primary backend can now register itself as a local gateway, enabling unified gateway management UI for both local and remote gateways. A unique constraint ensures only one local gateway per deployment.

Improvements

  • Gateway Multi-Environment Support — Gateways can now serve multiple environments simultaneously, removing the previous one-gateway-per-environment restriction.
  • Report RBAC Permissions — New report:read and report:write permissions provide fine-grained access control for the reporting system. All standard roles (User, Certificate Manager, Secret Manager, Auditor) receive report:read; Admin and Project Admin additionally receive report:write.
  • KeyTab RBAC Permissions — New keytab:read, keytab:write, keytab:delete, and keytab:admin permissions control access to keytab management. Standard users and auditors receive read access; operators and organization admins receive write and delete; organization admins additionally receive admin access for policy management.

Bug Fixes

  • Entra Sync Rule Cleanup — Removed orphaned sync rules left behind by deleted integrations, and cleaned up duplicate sync rules for entra_id provider type (now handled by the dedicated Entra Sync Scheduler). Fixes recurring "sync failed for rule" errors in production environments.

Version 1.0.37

Release Date: 2026-04-16

Improvements

  • Gateway Task Payload Encryption — Sensitive task payloads exchanged between the primary backend and gateways are now encrypted at rest in the database. The payload_encrypted flag on gateway tasks ensures that JSONB payloads containing credentials and private keys are protected even if database access is compromised.
  • Write Queue Exponential Backoff — Multi-datacenter write queue now tracks the timestamp of each retry attempt, enabling proper exponential backoff calculation for failed synchronization operations. This improves reliability and reduces unnecessary load on remote gateways during connectivity disruptions.

Security Updates

  • Gateway Payload At-Rest Encryption — Task results and payloads in the gateway task queue are now encrypted before database storage, closing a potential data exposure vector in multi-datacenter deployments.

Version 1.0.36

Release Date: 2026-04-15

New Features

  • KeyTab Database Schema — New database tables for keytab management: keytabs (encrypted keytab storage with cipher compliance tracking), keytab_versions (immutable version history), keytab_cipher_policies (organization-level cipher enforcement), and discovered_keytabs (agent discovery results with stale detection).
  • KeyTab RBAC Permissions — New permission set (keytab:read, keytab:write, keytab:delete, keytab:admin) assigned to appropriate system roles for keytab lifecycle management.

Improvements

  • Gateway API Token Enhancement — Improved gateway authentication with dedicated API tokens and bootstrap provisioning support.
  • Agent KeyTab Discovery — Agents can now discover Kerberos keytab files on managed hosts and report findings including file path, permissions, owner, encryption types, and SHA-256 fingerprint.

Version 1.0.35

Release Date: 2026-04-14

Bug Fixes

  • PEM Import — Private Key Preservation — Fixed a critical issue where importing a PEM file containing a certificate chain and a private key would silently discard the private key. The system correctly detected the key during file preview but lost it during the actual import, causing subsequent PFX/JKS exports to fail. The PEM bundle parser now correctly extracts PKCS#8, RSA, and EC private key blocks.
  • Certificate Import — Project Name in Error Messages — When importing a certificate that already exists, the error message now includes the project name where the duplicate resides (e.g., "certificate already exists … project=MyProject"), making it easier to identify conflicts.

Improvements

  • Private Key Visibility in UI — Certificates now display their private key status across all views:
    • Certificate lists show a green shield icon when a private key is stored.
    • The certificate dashboard shows a green key icon next to certificates with private keys.
    • The certificate detail modal displays a chip indicating whether the key is stored locally, externally, or not available.

Version 1.0.34

Release Date: 2026-04-10

New Features

  • Identity Provider Group Discovery — New API endpoint GET /identity-providers/{id}/groups fetches groups directly from the configured identity provider (Entra ID via Microsoft Graph, LDAP via directory search). Supports search filtering by group display name for easy role mapping.
  • Identity Provider Test Coverage — Added comprehensive unit test coverage for identity provider CRUD operations, test-connection flow, and group discovery endpoints.

Improvements

  • JKS Export — Pure Go Implementation — Replaced the external keytool (JDK) dependency with a native Go implementation using keystore-go/v4. JKS export now works in any environment without requiring a Java runtime, includes the full certificate chain, and has comprehensive test coverage.
  • Gateway API Token Authentication — New gateway_api_tokens table and middleware for gateway-to-backend API authentication. Gateways can now authenticate using dedicated API tokens with automatic bootstrap provisioning.
  • Gateway Write Queue — Added gateway_write_queue table for buffering write operations from gateways, enabling reliable data synchronization in multi-datacenter deployments.
  • Gateway Bootstrap Hardening — Improved gateway bootstrap flow with enhanced validation, Azure SQL connectivity checks, and more reliable initial registration.
  • Azure Test Environment Terraform — New Terraform configuration for automated Azure test environment provisioning, including Entra ID enterprise apps, Key Vaults, and Azure SQL.

Security Updates

  • Gateway Middleware Authentication — New dedicated middleware validates gateway API tokens with proper scope checks and request context propagation.

Version 1.0.33

Release Date: 2026-04-09

New Features

  • Azure Permissions Check Endpoints — Added new Azure access validation APIs for permissions and resource visibility:
  • POST /api/v1/admin/azure/mi-permissions-check
  • GET /api/v1/azure/user-permissions-summary
  • GET /api/v1/azure/subscriptions/{subscriptionId}/sql-servers
  • Managed Identity Permissions Validation — New managed identity check flow validates access across configured Azure integrations and returns per-integration status results.
  • User Permissions Summary — Added a consolidated subscription-level overview of Azure resources visible to the authenticated user, including Key Vault and SQL server discovery.

Improvements

  • RBAC Integration for Azure Permission Checks — Azure permissions endpoints are now protected by MazeVault RBAC with integration:read and integration:write guards.
  • Swagger Schema Coverage — OpenAPI definitions now include the Azure permissions check response models (MIPermissionsCheckResponse, MICheckResult) for accurate API client generation.

Security Updates

  • Role Permission Alignment — Migration 000109_add_audit_settings_permissions adds audit:read and project:write permissions to certificate_manager and secret_manager roles for consistent access control behavior.

Version 1.0.32

Release Date: 2026-04-08

New Features

  • Azure Managed HSM Integration — Full support for key storage and manipulation in Azure Managed HSM with automatic certificate updates and key rotation. All private key operations occur in the HSM with metadata returned to the database.
  • Organization-Level Password Policy — Define password enforcement rules (minimum length, complexity, expiry) for all organization users. Rules are enforced at password creation and change with legacy integration compatibility.

Improvements

  • CRDT Sync Performance — Optimized conflict resolution for datasets >100k records, 40% speed improvement on multi-DC setups.
  • Extended Audit Logs — Comprehensive recording of all password, certificate, and administrative operations.

Bug Fixes

  • Fixed cache invalidation timing in cluster deployments.
  • Resolved sync failures with large CSR transactions.

Version 1.0.31

Release Date: 2026-04-07

New Features

  • Orchestrator Mode — External Key Storage — MazeVault can now run in a mode where all private keys and secrets are stored exclusively in external key vaults (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault). Local database contains only metadata.
  • License Compliance Tracking — Dashboard displays license status in real-time, including used licenses, upcoming expirations, and warning states.

Improvements

  • Standard → Orchestrator Mode Migration — New CLI commands for safely migrating existing Standard mode systems to Orchestrator mode.
  • Orchestrator Mode UI — Toggle in Onboarding Wizard and organization settings.

Bug Fixes

  • Fixed selective certificate copying in Orchestrator mode.

Version 1.0.30

Release Date: 2026-04-07

New Features

  • ACME Server — Full RFC 8555 Implementation — MazeVault now acts as a fully compliant ACME certificate authority. Any standard ACME client (cert-manager, Certbot, acme.sh, Kubernetes) can obtain and renew certificates directly from MazeVault without manual intervention. Supported challenge types: HTTP-01, DNS-01, TLS-ALPN-01.
  • External Account Binding (EAB) — Restrict ACME account registration to authorized clients using pre-shared EAB credentials (HMAC-based). Prevents unauthorized certificate issuance from unknown ACME clients.
  • Sync Dashboard — New dashboard section showing real-time synchronization status: configuration overview, active conflicts, and sync failures with per-item resolution guidance.
  • Sync Read Permission — New sync:read permission grants project members, auditors, and role holders access to sync status endpoints without requiring elevated privileges.

Improvements

  • ACME Authorization Nonce Hardening — Dedicated nonce table (acme_server_nonces) with TTL-based expiry ensures strict replay-nonce protection per RFC 8555 §6.5.
  • ACME Challenge Token Indexing — Challenge tokens now use a dedicated indexed column for O(1) validation lookups instead of JSON scanning, improving throughput under high ACME request volume.
  • RBAC — Sync Permissions Aligned — project_admin, certificate_manager, secret_manager, user, and auditor roles now include sync:read for consistent access to sync dashboard endpoints.

Bug Fixes

  • Fixed race condition in ACME order state transitions during concurrent finalize requests.
  • Resolved ACME authz table creation ordering issue (migration idempotency).

Version 1.0.29

Release Date: 2026-04-06

New Features

  • Configuration Management Interface — New multi-tab UI for lifecycle management of configuration files: discover YAML/JSON/INI files across environments, stage drafts, and promote configurations to production. Provides visibility into discovered vs. managed configuration counts.
  • Certificate Rotation Polymorphic Config — Rotation executions now support both secret and certificate rotation configs in a unified model. The config_type field distinguishes between the two, eliminating separate workflow tables.
  • Rotation Settings Source Tracking — Each certificate now records whether its renewal settings originate from a project template (template) or were manually configured (manual), providing clear audit attribution for rotation behavior.

Improvements

  • Organization Scoping for Certificate Requests — Certificate signing requests (CSRs) are now scoped to the originating organization. The organization_id field is backfilled from project associations, preventing cross-organization CSR data leakage.
  • ACME Server Authorization Tables — Database schema for the ACME server protocol (authorization objects and nonce tables) was provisioned in preparation for the v1.0.30 ACME server release.
  • Rotation Workflow Cleanup — Removed orphaned rotation_workflows and rotation_step_executions tables that were superseded by the unified rotation execution model.

Bug Fixes

  • Fixed rotation_configs.next_rotation column rename to next_rotation_at — scheduler queries now use the correct column name.
  • Resolved foreign key constraint on rotation_executions.config_id that prevented polymorphic rotation config references.

Version 1.0.28

Release Date: 2026-04-05

New Features

  • Secret Naming Policies — Define organization-wide naming conventions for secrets using regex-based rules with three enforcement levels: block (prevent creation), warn (allow with warning), and disabled (informational). Policies are managed as configuration templates and validated in real-time on secret creation.
  • Naming Compliance Dashboard — New tab in the Secrets Dashboard shows a policy violation heatmap, per-rule compliance rates, and auto-generated rule suggestions based on existing secret naming patterns.
  • Consistency Framework — Create consistency groups to verify that specified secrets exist across all required environments. The POST /projects/{id}/consistency/groups endpoint and dashboard tab surface missing values and environment gaps, with resolve-warning support for documented exceptions.
  • Database Security Defaults — Organizations can now configure per-environment TLS/encryption baselines for database integrations. Production environments default to strict TLS (verify-full, encrypt, TCPS); non-production environments use permissive defaults. Supported providers: Oracle, MSSQL, PostgreSQL, MongoDB, MySQL.
  • Enhanced Shared Secrets — Shared secrets now support optional passphrase protection (bcrypt), recipient_email for intent tracking, automatic content-type tagging (secret or certificate), and rotation source attribution (source_type, source_id) for automated post-rotation distribution.

Security Updates

  • Consistency RBAC Permissions — New consistency:read and consistency:write permissions control access to consistency groups and warning resolution. Assigned to project_admin, certificate_manager, secret_manager, user, and auditor roles.
  • Auditor Role Refinement — The auditor role no longer has access to the deployment dashboard or general dashboard views, focusing the role strictly on audit logs, user management, gateway status, and agent status — reducing the attack surface of read-only accounts.

Bug Fixes

  • Fixed secret naming policy storage: policies are now persisted in config_management_templates instead of the deprecated organizations.secret_complexity_policy JSONB column.

Version 1.0.27

Release Date: 2026-04-04

New Features

  • New Roles: Certificate Manager & Secret Manager — Two new purpose-built roles provide fine-grained access control without granting cross-domain visibility:
  • certificate_manager — Full lifecycle management for certificates, CA accounts, templates, discovery, and deployment. No access to secrets.
  • secret_manager — Full lifecycle management for secrets, rotation, deployment, and integrations. No access to certificates.
  • SSO Authorization Code Pattern — Access tokens are no longer passed via URL query parameters on SSO callback. The callback now delivers a short-lived opaque code (sso_code, 60-second TTL) that the frontend exchanges for tokens via POST /auth/sso/exchange. This eliminates token exposure in browser history, server access logs, and Referer headers. Applies to Entra ID, GitHub, and GitLab SSO providers.
  • Entra Group Mapping Consolidation — Entra ID group-to-role mappings are now stored in the unified group_role_mappings table with a source column (local or entra). This enables consistent group management across local LDAP groups and Entra ID cloud groups from a single interface.

Security Updates

  • OIDC Nonce Validation — OAuth2 state now carries a server-generated nonce validated at token exchange. Prevents CSRF/token-hijacking via cross-site OAuth state substitution.
  • Deprecated Roles Removed — Legacy roles (operator, developer, org_admin, secret_editor, secret_viewer, certificate_admin, system_admin) have been removed. Existing users with these roles were automatically migrated: operator → project_admin, developer → user. Integrations using deprecated role names in API calls must be updated.
  • User Role Data Integrity — Fixed zero-UUID primary key corruption in the user_roles table caused by a missing BeforeCreate hook. A composite unique index was added to prevent duplicate role assignments. Affected rows were deduplicated during migration.
  • Soft-Deleted User Email Index — The email uniqueness constraint is now a partial index (WHERE deleted_at IS NULL). Soft-deleted users no longer block new account creation or SSO registration with the same email address.

Bug Fixes

  • Fixed Entra group mapping duplication after SSO re-authentication.
  • Resolved role assignment failures for users created via LDAP group sync.

Version 1.0.26

Release Date: 2026-04-03

New Features

  • PFX/PKCS#12 Import Improvements — Enhanced PFX certificate import with improved parsing and validation
  • Certificate Template Fixes — Resolved template configuration issues affecting certificate enrollment workflows
  • Role Mapping Modifications — Updated group-to-role mapping logic for improved SSO integration
  • Audit Log Enhancements — Extended audit logging with additional event types and improved traceability

Security Updates

  • Comprehensive Vulnerability Remediation — Resolved 18 out of 24 identified vulnerabilities across all platform components through systematic dependency scanning and updates
  • SAML Signature Bypass Fix (CRITICAL) — Fixed critical SAML XML signature bypass vulnerability in SSO authentication flow (goxmldsig v1.3.0 → v1.6.0)
  • HTTP/2 CONTINUATION Flood Fix (CRITICAL) — Resolved actively exploitable HTTP/2 denial-of-service vulnerability in Kubernetes Operator (golang.org/x/net v0.19.0 → v0.52.0)
  • gRPC Authorization Bypass Fix — Fixed gRPC authorization bypass via missing leading slash in path (google.golang.org/grpc → v1.80.0)
  • JOSE/JWE Denial-of-Service Fixes — Resolved 3 separate DoS vulnerabilities in JSON Web Encryption handling (go-jose/v3 v3.0.0 → v3.0.4)
  • Node.js SDK Critical Fix — Eliminated 8 critical Handlebars.js advisories including JavaScript injection and prototype pollution
  • CI/CD Security Hardening — Pinned Trivy security scanner to specific version (supply chain protection), enabled security scanning on all CI events

Improvements

  • Kubernetes Operator Overhaul — Major dependency update to controller-runtime v0.22.5 and k8s.io/* v0.34.3 with code quality improvements including extracted reconciliation methods, configurable refresh intervals, proper watch propagation, and structured logging
  • Terraform Provider Fix — Fixed compilation error and updated all dependencies to latest stable versions (terraform-plugin-framework v1.19.0, grpc v1.80.0)
  • Go SDK Enhancement — Added Environment field to Project model for improved project management
  • Docker Image Hardening — Pinned OCSP Responder base image to alpine:3.21 (reproducible builds), switched Frontend to npm ci for deterministic dependency installation
  • Dependency Alignment — Aligned golang.org/x/crypto, golang.org/x/net, and other standard library packages across all 7 Go modules to latest stable versions

Version 1.0.25

Release Date: 2026-04-01

New Features

  • Swagger API Documentation Overhaul — Comprehensive regeneration of Swagger/OpenAPI documentation with complete endpoint coverage, improved schema definitions, and accurate request/response examples
  • Entra ID SSO Environment Configuration — New environment variables for Entra ID SSO and Azure Managed Identity configuration in .env.example for streamlined deployment setup

Improvements

  • LDAP & OAuth Provider Configuration — Enhanced SSO provider setup with improved LDAP bind DN handling and OAuth2 flow configuration
  • Email Status Endpoint — New GET /api/v1/system/email-status endpoint for monitoring email notification delivery status
  • Certificate Rotation Handlers — New API handlers for certificate rotation execution and status tracking
  • Agent Integration Enhancements — Improved agent discovery and integration handlers with better error reporting
  • SSH Key Management — Extended SSH key service with improved import and rotation capabilities

Security Updates

  • Updated authentication service with enhanced token validation and session management
  • Improved Entra ID Graph client with additional security headers

Version 1.0.24

Release Date: 2026-03-30

Bug Fixes

  • Entra ID SSO Fix — Resolved critical Entra ID SSO authentication issue affecting login flow and token refresh
  • Code Cleanup — Removed deprecated handler code and unused Entra mapping endpoints for cleaner codebase

Improvements

  • Updated schema models with additional field definitions for improved data integrity

Version 1.0.23

Release Date: 2026-03-30

Improvements

  • Certificate Templates and Expiry Management — Enhanced certificate template configuration with improved expiry tracking, scheduler optimizations, and better CA integration status indicators
  • Certificate Import Validation — Improved certificate import service with stricter chain validation and better error messages
  • SSLmarket CA Sync — Extended SSLmarket CA provider with improved product synchronization and certificate status tracking
  • Extended Logging — Enhanced logging across certificate services for better troubleshooting and audit trail

Bug Fixes

  • Fixed certificate status calculation in X.509 utility functions
  • Resolved certificate dashboard display issues for expiring certificates
  • Fixed certificate edit modal preserving incorrect values on save

Version 1.0.22

Release Date: 2026-03-30

New Features

  • Multi-Gateway Environment Support — New database migration and service layer for multi-gateway deployments with environment-specific gateway configuration, health monitoring, and task execution
  • Gateway Health Monitor — Real-time gateway health monitoring service with heartbeat tracking, automatic failover detection, and Prometheus metrics
  • Gateway Routing Service — Intelligent request routing across multiple gateway instances with load balancing and environment awareness
  • Gateway Task Executor — Distributed task execution framework for gateway operations with retry logic and status tracking
  • Identity Provider Management — New API handlers for identity provider configuration and management

Improvements

  • Cipher Key Resolver Hardening — Comprehensive test coverage for cipher key resolution with 715+ lines of new tests eliminating dual storage inconsistencies
  • Database Health Checks — Updated expected tables and columns for new gateway-related database schema
  • Integration Wizard — New multi-step integration wizard UI for configuring CA providers, secret managers, and external integrations
  • Key Derivation Fix — Resolved key derivation issue affecting encryption operations

Security Updates

  • Eliminated cipher key dual storage vulnerability via migration 000089
  • Enhanced authentication service with improved session handling

Bug Fixes

  • Fixed test failures in certificate orchestrator and configuration management services
  • Resolved EntraID SSO redirect issue on certain browser configurations

Version 1.0.21

Release Date: 2026-03-26

New Features

  • Azure Resource Discovery — New API handlers for Azure cloud resource discovery with Key Vault, certificate, and secret enumeration
  • Integration Wizard UI — Multi-step wizard for configuring integrations with CA providers and secret managers, including type selection, provider configuration, and review steps

Improvements

  • CA Account Service — Enhanced CA account management with improved error handling and status tracking
  • Model Schema Updates — Updated data models across SSH keys, MFA, OAuth, CRL, and zero-trust modules for improved consistency and validation
  • Test Coverage — Expanded test coverage for integration service providers, key offload service, and secret service offload operations

Bug Fixes

  • Fixed certificate audit event model inconsistency
  • Resolved discovered certificate model field alignment issues

Version 1.0.20

Release Date: 2026-03-23

New Features

  • Office365 OAuth2 Email Notifications — Send email notifications via Microsoft Graph API using OAuth2 client credentials flow instead of legacy SMTP; supports 3 authentication methods (client secret, certificate, managed identity); reuses existing Entra ID infrastructure with connection caching and retry logic; transparent replacement — all 6 email trigger points (expiry alerts, incidents, weekly reports, rotation failures, discovery summaries, test notifications) work automatically; new GET /api/v1/system/email-status endpoint and frontend status indicator in System Outputs → Notifications tab
  • SmallStep CA Provider — Integration with open-source step-ca as a Certificate Authority backend supporting JWK, X5C, and OIDC provisioners; mTLS authentication, certificate signing, renewal, revocation, and CRL signing with root fingerprint verification; ideal for zero-trust short-lived certificate architectures
  • Multi-Target Certificate Rotation — Deploy certificates to 5 destination types: Secret Managers (Azure Key Vault, AWS Secrets Manager, HashiCorp Vault), Kubernetes Secrets (TLS/Opaque), Agent Keystores (JKS, PKCS12, Windows), Agent Files (PEM), and Database Wallets (Oracle OCI); content mode selection (public only, public+chain, full chain with key, key only), format auto-detection, retry policies with configurable attempts and delays, and post-install command execution
  • Enterprise Entra ID Credential Lifecycle — Complete lifecycle management with states (created, active, expiring, expired, revoked, grace period), configurable grace periods (default 30 days), rotation history tracking with old/new key IDs and workflow tracing, expiry monitoring dashboard, idempotency protection against duplicate rotations, and sync conflict resolution for local vs. remote state
  • Compliance Report Viewer — Generate and view compliance reports with template-based formatting, organization-wide certificate compliance analysis, and exportable report output
  • Rotation Execution History — Detailed per-certificate rotation history with execution timestamps, status tracking (success/failed/pending), and target-level sync status visualization
  • Certificate Lifecycle Phase Tracking — New lifecycle phases (stable, renewing, rotating, revoking) prevent duplicate CA requests during in-flight operations; orthogonal to certificate status, ensuring certificates remain valid during phase transitions

Improvements

  • Audit Stream Destinations — New Elasticsearch destination with index template management, cluster mode and CosmosDB support; new Syslog destination with TCP/UDP transport, CEF and Syslog format support; enhanced log stream service with destination-specific configuration validation
  • LDAP Authentication Improvements — Enhanced LDAP service with improved bind DN handling, group membership resolution, schema configuration flexibility, and better error diagnostics for connection failures
  • Notification Scheduler Redesign — Improved scheduling logic for certificate expiry notifications with batching support to reduce alert fatigue, JIRA integration for incident ticket creation, and email notification enhancements
  • Weekly Expiry Report Service — Redesigned report generation with recipient management, customizable report content, and improved delivery reliability
  • Project Template Enhancements — Extended template configuration with naming convention integration, advanced default settings, and improved template-to-project application workflow
  • Naming Convention Service — Extended naming convention engine with additional pattern support, validation rules, and convention-to-template linking for automated enforcement
  • Prometheus Metrics — New histogram and counter metrics for certificate rotation, compliance reporting, and audit stream performance monitoring
  • CI/CD Pipeline — New GitHub Actions workflow for automated build, test, and deployment
  • SSO Provider Modal — Added LDAP provider configuration with server, bind DN, and schema settings directly from the SSO configuration interface
  • WebLogic Deploy Rotation Step — New rotation step type for Oracle WebLogic Server keystore deployment with automated domain configuration updates

Security Updates

  • Entra ID credential rotation with full audit trail — every rotation recorded with actor, timestamp, old/new key IDs, and execution status
  • SmallStep CA operations logged to audit stream with complete request/response metadata
  • Certificate lifecycle phase prevents concurrent CA operations, eliminating race conditions in renewal and rotation workflows
  • Entra sync conflict detection with automatic tracking of local vs. remote state discrepancies

Version 1.0.19

Release Date: 2026-03-20

New Features

  • Azure Gateway Deployment — Multi-region Azure Gateway infrastructure with Terraform modules for AKS, Key Vault, PostgreSQL, Redis, networking, monitoring, and identity management; Helm chart values for gateway configuration; Azure DevOps CI/CD pipeline for automated deployment
  • Key Vault RBAC Service — Granular role-based access control for Azure Key Vault operations with per-secret and per-certificate permission management
  • Key Vault Watch Connector — Real-time synchronization between MazeVault and Azure Key Vault with change detection and automatic secret updates
  • Naming Convention Engine — Domain-specific naming rules with wildcard pattern matching, priority ordering, and template-level enforcement for consistent resource naming across projects
  • Dashboard Reports Tab — Redesigned reporting dashboard with certificate overview statistics, interactive charts, and exportable report data
  • Node.js SDK — Official MazeVault SDK for Node.js with TypeScript definitions, supporting authentication (SRP), organizations, projects, and secrets management

Improvements

  • Audit Log Project Scoping — Audit events now include project association for efficient per-project filtering and compliance reporting; existing events backfilled from entity relationships
  • Orchestrator Storage Mode Fix — Normalized inconsistent storage mode values for Orchestrator Mode deployments, resolving constraint violations
  • Entra Group Mappings — Improved group-to-role mapping reliability with better error handling and UI feedback
  • Config Encryption Hardening — Enhanced configuration encryption service with additional test coverage and improved error handling for edge cases
  • Certificate Import Validation — Improved certificate import service with stricter chain validation
  • Go SDK Updates — Updated project and model definitions for consistency with latest API
  • Python SDK Updates — Model alignment with latest API schema

Bug Fixes

  • Fixed environment creation for unused projects being triggered unnecessarily
  • Resolved Entra ID SSO login redirect issue on certain browser configurations
  • Fixed project template modal not preserving environment associations on save

Version 1.0.18

Release Date: 2026-03-14

Improvements

  • Customer Documentation Updated — Comprehensive ACME certificate automation guide with step-by-step Kubernetes setup, cert-manager ClusterIssuer examples, ACME profile routing, troubleshooting, and complete end-to-end YAML examples; updated Certificates API reference with ACME endpoints and EAB management; release notes reformatted to correct 1.0.x versioning scheme
  • Azure Entra ID Integration Improved — Enhanced token refresh handling, improved group-to-role mapping reliability, faster SSO login flow with reduced redirect latency, and better error messages for misconfigured tenant settings

Version 1.0.17

Release Date: 2026-03-14

New Features

  • ACME Server (RFC 8555) — MazeVault now acts as a full ACME Certificate Authority, enabling automated certificate issuance via cert-manager and other ACME clients
  • External Account Binding (EAB) — Secure cluster registration with one-time-use credentials linking ACME clients to organizations and projects
  • ACME Profile Routing — Map cert-manager profile names to MazeVault Certificate Templates for automatic CA backend selection (cert-manager v1.18+)
  • Domain Rule Engine — Configure domain-to-template routing rules with wildcard pattern matching and priority ordering
  • Auto-Approve for Internal Domains — Certificates for .local, .internal, .lan, and .corp domains are issued instantly without HTTP-01 challenge
  • ADCS Bridge via ACME — Issue certificates from Microsoft Active Directory Certificate Services through standard ACME protocol
  • ADCS Agent Improvements — DCOM retry logic for pending certificate requests with configurable intervals
  • EAB Credential Management UI — Generate, list, and revoke EAB credentials from the web interface with cert-manager YAML examples

Improvements

  • ACME directory endpoint with meta profiles for automated client discovery
  • JWS middleware with ES256 and RS256 signature verification
  • Nonce-based replay protection per RFC 8555 §6.5
  • Full PEM certificate chain delivery for ACME clients
  • EAB credentials table with status tracking (Available / Used / Revoked)
  • One-click copy for ACME directory URL and generated credentials

Security Updates

  • EAB HMAC keys encrypted at rest with AES-256-GCM
  • EAB credentials are single-use and support expiration and revocation
  • JWK Thumbprint verification (RFC 7638) for account binding
  • All ACME operations recorded in audit log

Version 1.0.16

Release Date: 2026-02-28

Improvements

  • Organization settings redesign with tabbed navigation
  • Certificate Authority account cards with sync status indicators
  • Improved CA product discovery and sync trigger via UI
  • Agent heartbeat interval optimization for large fleets
  • Database connection pool tuning for high-concurrency deployments

Bug Fixes

  • Fixed certificate chain validation for intermediate CA certificates
  • Resolved race condition in concurrent secret rotation scheduling
  • Fixed OCSP responder cache invalidation on certificate revocation

Version 1.0.15

Release Date: 2026-02-14

Improvements

  • PFX/PKCS#12 import with configurable key storage (software / HSM)
  • Certificate template override support for CA account-level defaults
  • Improved agent reconnection logic after network interruptions
  • Enhanced audit log filtering by event type and date range

Bug Fixes

  • Fixed certificate export with chain for cross-signed intermediates
  • Resolved project template settings not persisting after save
  • Fixed SSH key discovery deduplication for rotated keys

Version 1.0.14

Release Date: 2026-01-31

Improvements

  • SSH key management enhancements — authorized key tracking and discovery
  • Rotation trigger improvements with foreign key constraint handling
  • Secret sharing post-rotation with automatic re-encryption
  • Improved error messages for agent proxy authentication failures

Bug Fixes

  • Fixed rotation scheduler timezone handling for non-UTC installations
  • Resolved dashboard certificate count discrepancy after bulk import
  • Fixed API token expiration check for service identities

Version 1.0.13

Release Date: 2026-01-24

Improvements

  • React import modernization — tree-shaking optimized MUI imports
  • Frontend build size reduction (~15% smaller bundle)
  • Improved certificate search with wildcard SAN matching
  • Enhanced monitoring Prometheus metrics with histogram buckets

Bug Fixes

  • Fixed RBAC permission check for nested project environments
  • Resolved Terraform export formatting for complex secret values
  • Fixed health check endpoint returning stale Redis status

Version 1.0.12

Release Date: 2026-01-17

Improvements

  • Trivy container image scanning integration in CI/CD pipeline
  • Security vulnerability remediation for dependency chain
  • Improved TLS cipher suite configuration with Mozilla Intermediate profile
  • Enhanced rate limiting with sliding window algorithm

Bug Fixes

  • Fixed certificate renewal scheduling for certificates with custom validity
  • Resolved sync conflict for simultaneously edited secrets across datacenters
  • Fixed agent registration token validation for re-registered agents

Version 1.0.11

Release Date: 2026-01-10

Improvements

  • PostgreSQL connection health monitoring with automatic reconnection
  • Bidirectional sync architecture improvements for multi-region deployments
  • Agent installation script fixes for air-gapped environments
  • Onboarding flow improvements for remote deployment scenarios

Bug Fixes

  • Fixed database migration rollback for failed upgrades
  • Resolved LDAP group sync not reflecting membership changes
  • Fixed certificate list pagination for projects with > 1000 certificates

Version 1.0.10

Release Date: 2026-01-03

Improvements

  • Enhanced external change detection for CA-managed certificates
  • Improved Helm chart values documentation with inline comments
  • Certificate expiry notification batching to reduce alert fatigue
  • Updated Go dependencies with security patches

Bug Fixes

  • Fixed CRDT merge for concurrent secret version creation
  • Resolved Azure Key Vault sync retry logic for transient failures
  • Fixed OCSP responder returning incorrect status for renewed certificates

Version 1.0.9

Release Date: 2025-12-15

New Features

  • Certificate Template System — Pre-configured certificate profiles for common use cases (Web Server, Client Auth, Code Signing, Email/S-MIME)
  • Bulk Certificate Operations — Import and manage certificates in bulk via PEM bundles
  • Enhanced Agent Discovery — Automatic discovery of certificates across agent-managed infrastructure
  • Sync Improvements — CRDT-based multi-datacenter synchronization with improved conflict resolution
  • Terraform Export — Export project configurations as Terraform HCL for infrastructure-as-code workflows

Improvements

  • Improved certificate import validation and error reporting
  • Enhanced OCSP responder performance with response caching
  • Updated RBAC with granular certificate management permissions
  • Improved audit logging with structured JSON output
  • Enhanced health check endpoints with component-level status

Security Updates

  • TLS 1.3 as default protocol
  • Improved CSRF protection with double-submit cookie pattern
  • Enhanced rate limiting with per-endpoint configuration
  • Updated cryptographic dependencies

Version 1.0.8

Release Date: 2025-11-20

New Features

  • External CA Integration — Connect to DigiCert, Venafi, Microsoft ADCS, and other external Certificate Authorities
  • HSM Support — Hardware Security Module integration for key protection (PKCS#11, Azure Managed HSM)
  • ACME Protocol — Automated Certificate Management Environment for automated certificate issuance
  • Multi-Factor Authentication — TOTP-based MFA for enhanced account security

Improvements

  • Redesigned certificate management interface
  • Improved secret rotation scheduling
  • Enhanced API rate limiting
  • Expanded Azure Key Vault integration

Version 1.0.7

Release Date: 2025-09-10

New Features

  • Zero-Knowledge Encryption — Client-side encryption for personal vault secrets
  • SCEP Protocol Support — Simple Certificate Enrollment Protocol for device certificate management
  • EST Protocol Support — Enrollment over Secure Transport for modern certificate enrollment
  • Agent Proxy — Agents can proxy secret access for local applications

Improvements

  • Improved database migration system
  • Enhanced logging and monitoring
  • Updated Kubernetes deployment manifests
  • Performance improvements for large certificate stores

Version 1.0.6

Release Date: 2025-06-15

New Features

  • Multi-Datacenter Sync — Bidirectional synchronization between MazeVault installations
  • Azure Entra ID SSO — Single Sign-On with Azure Active Directory
  • Project-Level RBAC — Granular role-based access control per project
  • CRL Distribution — Automated Certificate Revocation List generation and distribution

Improvements

  • Improved dashboard with real-time status updates
  • Enhanced certificate search and filtering
  • Updated API documentation with OpenAPI 3.0 specifications
  • Improved container image security

Version 1.0.5

Release Date: 2025-03-20

New Features

  • OCSP Responder — Real-time Online Certificate Status Protocol responder
  • Secret Versioning — Full version history with rollback capabilities
  • Agent Management — Centralized agent registration and monitoring
  • LDAP Integration — Directory service authentication support

Version 1.0.4

Release Date: 2024-12-10

New Features

  • Internal Certificate Authority — Full PKI with root and intermediate CA support
  • Certificate Lifecycle Management — Request, approve, issue, renew, and revoke certificates
  • Helm Charts — Standardized Kubernetes deployment via Helm

Version 1.0.3

Release Date: 2024-09-15

New Features

  • Azure Key Vault Integration — Sync secrets with Azure Key Vault
  • Secret Rotation — Automated and manual secret rotation
  • Kubernetes Deployment — AKS deployment with Terraform

Version 1.0.2

Release Date: 2024-06-20

New Features

  • Role-Based Access Control — User roles and permissions
  • Project Management — Organize secrets into projects
  • API v1 — Full REST API for secrets management

Version 1.0.1

Release Date: 2024-03-01

Initial Release

  • Encrypted secrets storage with AES-256-GCM
  • Web-based management interface
  • PostgreSQL backend with Redis caching
  • Docker Compose deployment
  • Local authentication with SRP protocol

Support Policy

MazeVault supports the current version and one previous minor version. Customers on older versions are encouraged to upgrade to receive security updates and new features.