Skip to content

Releases: dasomel/ldapium

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 09:40
0bfe765

Fixes a data-loss race in multi-provider replication and adds an opt-in detector for it.
No configuration change is required; upgrading from 0.2.1 is an image/chart version bump.
The server image's OpenLDAP is now upstream 2.6.15 plus one carried patch, so the
binaries are modified (NOTICE, docs/legal.md); appVersion stays 2.6.15.

Fixed

  • Two providers writing the same entry within milliseconds could leave the earlier-CSN
    node with a deleted attribute or missing entryCSN/modifyTimestamp, and it did not
    heal (#432, #434). The image now carries an OpenLDAP 2.6.15 syncrepl retry_diff patch
    (image/patches/, also in the image at /usr/share/doc/openldap/patches/, label
    io.ldapium.openldap.patches; #440). Measured: Password Modify race 46/3000 diverged
    unpatched vs 0/3000 patched, plain replace 9/8400 vs 0/12600; full CI including
    replication-chaos and the upgrade E2E passed on the patched image.

Added

  • Cross-provider divergence detector (#437). replication.check.divergence.enabled
    (default false) makes the replication check CronJob compare recently changed entries
    across providers (window windowMinutes, default 45; re-read after graceSeconds,
    default 5; at most maxCandidates, default 500, paged). A real divergence fails the job,
    which fires LDAPiumReplicationCheckFailed. It stays useful as a safety net for images
    built without the patch. Runbook: docs/ha-profile.md.
  • scripts/test/test-replication-write-race-live.py: an opt-in two-provider race harness
    (plain replace and Password Modify), not run in CI.

Tests

  • The two-provider unpartitioned M5 race in machine write measurements was recorded as
    a measurement while #432 was open (#435) and is a check again (diverged == 0) now that
    the image carries the fix (#440).

Documentation

  • Markdown docs brought up to date with 0.2.1, README feature and CI sections (#433);
    the replication write-race change package with the D434 decisions and the patch proof
    (docs/changes/replication-write-race/, #436, #439); AGENTS.md and docs/ha-profile.md
    describe the race and the fix.

What's Changed

  • docs: bring the markdown docs up to date with 0.2.1 by @dasomel in #433
  • test(machine-write): record the two-provider M5 race divergence as a measurement (#432) by @dasomel in #435
  • docs(changes): propose the replication write-race mitigation package (#434) by @dasomel in #436
  • feat(replication): add two-stage cross-provider divergence detector (#434) by @dasomel in #437
  • docs(changes): record the D434-3 retry_diff patch proof (#434) by @dasomel in #439
  • fix(image): carry the OpenLDAP syncrepl retry_diff patch (#432, #434) by @dasomel in #440
  • chore(release): 0.3.0 by @dasomel in #441

Full Changelog: v0.2.1...v0.3.0

v0.2.1

Choose a tag to compare

@github-actions github-actions released this 16 Oct 07:44
7342f96

Maintenance release: dependency and base-image refreshes, one documentation
correction, and more live proof for the machine password operation. No behaviour or
configuration changes; upgrading from 0.2.0 is an image/chart version bump.

Images

  • Go toolchain in the image builds: golang:1.27.2 (was 1.27.1) for the exporter
    builder in the server image (#424), and the UI backend builder re-pinned to a new
    golang:1.27.2-alpine digest (f92b6ef, #420).
  • UI backend Go modules: github.com/go-ldap/ldap/v3 3.4.15,
    github.com/prometheus/client_golang 1.25.0 (#421).
  • UI frontend dependencies (#422): the Radix UI primitives (react-dialog 1.2.0,
    react-slot 1.4.0, react-tooltip 1.3.0 and patch releases of the rest),
    lucide-react 1.55.0, vite 8.3.4, @vitejs/plugin-react 6.1.2, oxlint 1.87.0,
    @playwright/test 1.64.0, @types/node 26.6.5.

Documentation

  • Machine setPassword second-set response corrected (#417). The 0.2.0 notes and
    docs/api.md said an account whose password was already set answers 409. A completed
    set clears the pending marker, so a second request is refused 403 by the marker check;
    409 conflict only appears for a stuck entry (password present, marker left by a
    failed cleanup). The behaviour is unchanged and was measured through a real Keycloak
    token and the UI image.
  • Machine write v2 follow-ups closed (#417). Acceptance wording AC-005/AC-010 now
    matches the measured behaviour; maintainer decisions D417-1 (no privileged-group
    filter= backstop in write-lock.ldif) and D417-2 (nested privileged-group lock
    risk stays accepted; list subgroups in MACHINE_WRITE_PRIVILEGED_GROUPS,
    docs/machine-write-account.md 4.3) are recorded in the change package.

Release pipeline and CI

  • The machine write (real Keycloak) release gate also runs setPassword end to end
    through a real Keycloak token, the production UI image configured by environment
    only, and a real slapd, with the plaintext/hash secret scans (#427).
  • New non-release-critical job machine write measurements (E-22, two-provider R-3)
    repeats the check-to-write window and two-provider replication measurements on
    every change (#428, #430).
  • GitHub Actions bumped (#423): actions/upload-artifact 7.0.2,
    actions/download-artifact 8.0.2, actions/setup-node 7.1.0,
    github/codeql-action 4.38.3, step-security/harden-runner 2.22.1,
    ossf/scorecard-action 2.5.0, helm/kind-action 1.15.1.

Note on the dependency policy

The dependency updates (#420–#424) were merged before the seven-day cooling window in
docs/dependency-policy.md had elapsed, alongside the 0.2.0 release work.

What's Changed

  • docs(machine-write): confirm T-027 on the v0.2.0 tag SHA (#379) by @dasomel in #426
  • chore(deps): bump golang from 85dc106 to f92b6ef in /ui by @dependabot[bot] in #420
  • chore(deps): bump the actions group with 9 updates by @dependabot[bot] in #423
  • chore(deps): bump golang from 1.27.1-bookworm to 1.27.2-bookworm in /image by @dependabot[bot] in #424
  • chore(deps): bump the go-dependencies group in /ui/backend with 2 updates by @dependabot[bot] in #421
  • chore(deps): bump the frontend-dependencies group in /ui/frontend with 16 updates by @dependabot[bot] in #422
  • test(machine-write): live setPassword proof via real Keycloak, UI image and config.Load (#417 b) by @dasomel in #427
  • docs+test(machine-write): close the T-060 follow-ups (#417) by @dasomel in #428
  • ci(machine-write): run the E-22 and two-provider R-3 measurements (#429) by @dasomel in #430
  • chore(release): 0.2.1 by @dasomel in #431

Full Changelog: v0.2.0...v0.2.1

v0.2.0

Choose a tag to compare

@github-actions github-actions released this 15 Oct 00:04
aa0b8e2

Breaking / behaviour changes

  • Machine deleteUser / modrdn narrowed (Q16). With the v2c LDAP rules installed,
    machine deleteUser and modrdn only reach entries created through W_data; other
    entries are refused.
  • ppolicy state attributes are read-only for self (#400). pwdReset,
    pwdStartTime, pwdEndTime and pwdAccountLockedTime can no longer be written by
    the user on its own entry. The ACL is bootstrap-only: existing volumes keep their
    stored olcAccess until upgraded in place (see image/README.md, in-place upgrade).
  • The image refuses to start with extended or all in LDAP_ACCESSLOG_OPS (#398).
    Use writes reads bind.

Web UI

  • Users and Groups lists page through the cursor API (limit/cursor/q, #253).
    Search is now server-side and matches only uid/cn/mail/displayName (users)
    and cn/description (groups); the old client-side search over department,
    organization and organizationalUnit is gone (accepted in D215-7). The list no
    longer shows a total count.

Image

  • The entrypoint now refuses to start when the accesslog overlay is enabled and
    LDAP_ACCESSLOG_OPS contains extended or all (#398, F-7, D398-1). OpenLDAP
    2.6.15's accesslog overlay aborts slapd (accesslog.c:1623, exit 133) on the first
    Password Modify, including the human setPassword path. Use writes reads bind;
    there is no override until upstream fixes it.

API

  • Machine write v2c opened (#379, T-026): machine setPassword. With
    MACHINE_WRITE_ENABLED and MACHINE_WRITE_CRED_ENABLED on and
    directory.users.password.write in a client's ceiling, a machine client sets the
    initial password of an account it created itself with createUser
    credentialPending: true (body exactly {dn, password}, Idempotency-Key
    required, no If-Match, response {}; any other target 403, an account whose
    password was already set 409). It runs as the separate W_cred identity behind
    the T-028 premise checks. OpenAPI marks it machineBearer /
    x-machine-scope: directory.users.password.write. A ceiling may now name the
    password scope; with MACHINE_WRITE_CRED_ENABLED=false it still starts and the
    request is 403 scope_denied without any W_cred bind (the emergency block;
    AC-011 (a) is revised to 403, Q19).
    Unlock stays human-only and is the only write scope still refused in a ceiling.
  • Contract narrowing with v2c on (Q16, accepted 2026-10-14): a deployment that
    installs the v2c LDAP rules narrows machine deleteUser (and W_data's direct
    LDAP modrdn; the machine API has no rename operation) to entries W_data created; deleting an administrator-made user (any class) or a
    container is refused by the directory (LDAP 50) and answered 403 forbidden.
    Groups (v2b deleteGroup) and deployments without v2c are unchanged.
  • W_cred gets a no-lockout password policy (CHANGE.md Q17, decided 2026-10-14):
    guide docs/machine-write-account.md 8.1 attaches wcred-policy.ldif
    (pwdLockout FALSE, no pwdMaxFailure), so wrong binds can no longer lock
    W_cred and stop setPassword; the operator owns brute-force mitigation (long
    random password, network restriction, failed-bind alert). The premise checks
    enforce it (Q18 (b)): at start-up and every 60 s W_cred's policy must exist,
    differ from the default and pending policies and state pwdLockout: FALSE with
    no pwdMaxFailure/pwdMaxAge/pwdMustChange TRUE, else start-up is refused /
    setPassword answers 503.
  • Accepted risk (2026-10-14): with v2c on, a leaked write credential can rename an
    administrator-made user's mail/uid and give the old value to a new pending
    account; applications that resolve the login name from mail/uid are at risk
    (bind by entryUUID or alert on changes, or keep v2c off).
  • Chart: ui.machineAuth.write.cred.* renders the W_cred identity, the
    configuration reader and the canaries (MACHINE_WRITE_CRED_*; passwords are
    Secret references only, every identity and Secret key distinct). It still
    refuses next to audit.enabled=true or an accesslog ops list holding
    extended/all. The chart still grants no write scope in allowedClients.
  • Machine write v2c (#379, T-020) configuration and start-up validation only; no
    operation is opened
    (directory.users.password.write is still refused in a
    ceiling and the password routes stay 403 scope_denied with no bind). New
    MACHINE_WRITE_CRED_ENABLED (default false; when off no other
    MACHINE_WRITE_CRED_* is read). When on, start-up requires
    MACHINE_WRITE_CRED_BIND_DN/_PASSWORD (distinct from every other identity, in
    the protected set), a non-empty MACHINE_WRITE_PRIVILEGED_GROUPS,
    MACHINE_WRITE_CRED_SUBTREES inside the same client's MACHINE_WRITE_SUBTREES,
    and MACHINE_WRITE_CRED_PENDING_POLICY_DN (not the default policy); optional
    MACHINE_WRITE_CRED_MIN_LENGTH (12–1024, default 12) and
    MACHINE_WRITE_CRED_POLICY_DNS. The policy entries join the protected set, so a
    write ceiling holding one fails start-up. The chart is unchanged. See
    machine-write-account.md section 8.
  • Machine write v2c (#379, T-021) LDAP fragments, opt-in and not yet used by the app: W_cred
    (write-cred.ldif, initial password on pending W_data-made accounts only, cleanup of the marker
    and pending policy values) and the W_data v2c rules (write-data-cred.ldif). Installing them
    requires olcAddContentAcl TRUE on the main database and narrows machine deleteUser/modrdn to
    entries W_data created, groups excepted (Q16). Guide docs/machine-write-account.md section 8; the read-only
    checker gains --cred-dn/--pending-policy-dn/--privileged-group/--wcred-unpinned; non-pinned
    providers get write-cred-deny.ldif. Image defaults unchanged.
  • Machine write v2c (#379, T-023) password limits and audit, still not opened:
    new MACHINE_WRITE_CRED_RATE_LIMIT_RPS (global password budget, 1–10, default 1,
    read only with MACHINE_WRITE_CRED_ENABLED=true) and a per-target serialization
    (entryUUID, shared across clients); either refusal is 429 machine_rate_limited
    with Retry-After. Audit lines gain compartment=cred (no password field).
  • Machine write v2c (#379, T-025) per-compartment emergency-block drill for the password
    compartment, tests and docs only, setPassword still closed (T-026 opens it). New
    scripts/test/test-machine-write-cred-drill.py and a live-tagged Go test measure the
    MACHINE_WRITE_CRED_ENABLED=false redeploy, the W_cred rotation (old credential refused,
    W_data/W_lock/read unaffected on running replicas, restarts fail until the new Secret is
    rolled out), the MACHINE_REQUEST_TIMEOUT bound of an in-flight request (8.0 s at 10 s, 2.0 s
    at 4 s), the W_cred lockout at pwdMaxFailure and the Revision 8 manual cleanup of a stuck
    entry, run live. docs/machine-auth-operations.md gains "Password compartment emergency
    block". A flag-only redeploy answers 503, not the 403 of AC-011 (a); AC-011 is corrected. The
    W_cred lockout policy is an open maintainer decision (CHANGE.md Q17).
  • Machine write v2c (#379, T-028) run-time premise checks and sweeps, still not
    opened
    (setPassword stays unreachable until T-026). With
    MACHINE_WRITE_CRED_ENABLED=true start-up now also requires
    MACHINE_WRITE_CRED_ACL_READER_DN/_PASSWORD (a cn=config reader, distinct from
    every other identity, in the protected set) and MACHINE_WRITE_CRED_CANARIES (one
    set of three locked canary entries inside every MACHINE_WRITE_CRED_SUBTREES DN), and
    reads MACHINE_WRITE_CRED_PENDING_TTL (default 24h, 1h–720h). The UI then checks,
    at start-up (failure: non-zero exit), before every password write (failure: that
    request 503, no write) and every 60 s (failure: password operations 503 until a
    passing cycle): the main database olcAccess against the guide's layout as text,
    olcAddContentAcl TRUE, the default and pending policy values against the floor
    (the image's default pwdMinLength 8 is below the floor 12: v2c deployments need
    LDAP_PASSWORD_MIN_LENGTH>=12
    , an intended operating cost), no cn=accesslog child
    readable by M, and the canary probes. The 60 s cycle also runs the stuck / TTL /
    orphan / foreign sweeps of the pending policy (W_cred cleans the first three, with a
    re-read retry; a cleanup that still fails closes the password operations and alerts;
    after a failed check the sweep only counts). Start-up sweeps read-only and refuses to
    start on a stuck entry; a cycle older than two periods closes the password operations.
    MACHINE_WRITE_CRED_POLICY_DNS has no effect yet (the pre-read admits only the pending
    policy). Only result codes and counts are logged. New
    fixtures config-reader.ldif, config-reader-main.ldif and guide sections 8.8–8.9
    (docs/machine-write-account.md). Chart:
    ui.machineAuth.write.cred.enabled (default false) exists only to refuse: next to
    audit.enabled=true (D379-14), next to an audit.accessLog.ops holding extended
    or all (F-7), and on its own until T-026.
  • Machine write v2c (#379, T-024) live proof of the password step, tests only, still
    not opened: scripts/test/test-machine-write-cred-secret-live.py runs the real
    pipeline (a live-tagged test server that adds setPassword to its own table) against
    slapd and scans responses, headers, the UI log and audit, the slapd log and
    cn=accesslog (rootdn and M) for the one-time sentinels and for hash patterns: 0
    in the configuration the chart and T-028 accept; with audit.enabled the auditlog
    carries hashes, which the chart refuses. The T-028 check-5 pre-hashed probe is now
    one fixed public constant ({SSHA} of zz, salt ldapium!; it was a random throwaway
    value): its refused Modify still leaves that value in cn=accesslog at start-up and
    ...
Read more

v0.1.1

Choose a tag to compare

@github-actions github-actions released this 24 Sep 09:03
82a7721

Maintenance release: dependency and base-image refreshes, plus the release
pipeline fix that let 0.1.0's supply-chain evidence run. No behaviour or configuration
changes; upgrading from 0.1.0 is an image/chart version bump.

Images

  • Base images re-pinned to new digests: debian:trixie-slim (a99cfc5, builder
    and runtime) and golang:1.27.1-bookworm (69a7b97, exporter builder). Both
    still cover linux/amd64 and linux/arm64.
  • UI frontend dependencies: lucide-react 1.47.0, react-router /
    react-router-dom 7.18.4, @types/node 26.6.2, oxlint 1.83.0.

Release pipeline and CI

  • The supply-chain licence gate installs Go/Node dependencies before running,
    so it no longer regenerates an empty inventory and fails (#191).
  • GitHub Actions bumped: docker/setup-buildx-action 4.4.1,
    docker/build-push-action 7.4.0 (includes an upstream fix for workflow
    command injection through metadata logs), github/codeql-action 4.38.1.
  • Workflow concurrency groups no longer cancel or get displaced on main
    push: a late-starting run for an older commit had cancelled HEAD's
    in-progress E2E after four PRs merged back-to-back (2026-09-24), leaving
    HEAD with no completed E2E. main/schedule/dispatch runs are now grouped
    per commit SHA instead of per ref, so they never share a concurrency group
    with another commit's run; cancel-in-progress stays scoped to
    pull_request only. Docs-only changes (**.md, docs/**, research/**)
    also no longer trigger the non-required E2E suites on push to main
    (metrics, replication chaos, security, SSSD, UI); ci.yml, e2e.yml,
    backup-restore.yml, and upgrade-e2e.yml still run on every push to
    main, since branch protection or release.yml's release-critical checks
    require their results on every commit that could be tagged.

Note on the dependency policy

These updates were merged before the seven-day cooling window in
docs/dependency-policy.md had elapsed, at the maintainer's request, after a
review of each upstream release (see #192–#195).

What's Changed

  • fix(ci): install Go/Node deps before the supply-chain licence gate by @dasomel in #191
  • docs(research): record 2026-09 verification evidence by @dasomel in #196
  • build(deps): bump debian from d7e1218 to a99cfc5 in /image by @dependabot[bot] in #193
  • build(deps): bump the actions group with 5 updates by @dependabot[bot] in #195
  • build(deps): bump the frontend-dependencies group in /ui/frontend with 4 updates by @dependabot[bot] in #194
  • build(deps): bump golang from 648f440 to 69a7b97 in /image by @dependabot[bot] in #192
  • chore(release): prepare v0.1.1 by @dasomel in #197

Full Changelog: v0.1.0...v0.1.1

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 23 Sep 00:59
accc6d3

First release, and a prototype: everything below was verified against
something running, but nothing has been run outside its author's environment.
helm test <release> is shipped with the chart so an install can be checked
rather than assumed.

Server image (ghcr.io/dasomel/ldapium)

  • OpenLDAP 2.6.14 compiled from the upstream tarball, pinned by version and
    sha256, on debian:trixie-slim. linux/amd64 and linux/arm64, each built
    on a native runner — OpenLDAP's configure uses runtime probes that are not
    reliable under emulation.
  • slapd runs as PID 1 as uid 999, configured entirely through cn=config.
  • No sample data and no default admin password. The container refuses to start
    without one; seeding is opt-in through a mounted LDIF directory.
  • Overlays enabled by default: memberof, refint, ppolicy, unique, and
    syncprov when replication is on. Also built and loadable: accesslog,
    auditlog, constraint, deref, dynlist, sssvlv, otp.
  • {ARGON2} password hashing, loaded as a module.
  • N-way multi-provider replication, including a cold-start election so several
    nodes booting at once cannot each create their own copy of the base DIT.
  • Tuned for large directories: paged results, configurable size/time limits,
    and an explicit file-descriptor limit (slapd reserves memory per descriptor,
    so an inherited 1M-descriptor limit alone cost ~650MB of RSS).

Management UI (ghcr.io/dasomel/ldapium-ui)

  • Go backend, React frontend, shipped as one distroless static image running as
    uid 65532.
  • DIT browser, user and group CRUD, group membership by search-and-select,
    password set and self-service change, account unlock, memberOf display,
    paged listings, and a password-policy view.
  • Every request binds as the logged-in user, so the directory's own ACLs decide
    what a session can do. The UI holds no service account.
  • Optional Keycloak SSO (OIDC with PKCE), gated on a realm role. The login
    state is bound to the browser that began it, so a state and code obtained
    elsewhere cannot be used to log somebody else's browser into the attacker's
    account. This is the one path that uses a dedicated LDAP service account,
    because a token carries no password to bind with.
  • Korean and English throughout, and inline explanations of LDAP terminology
    for people who do not work with directories daily.
  • Backup status is read from the directory itself, so the UI needs no
    Kubernetes access to show it.

Helm chart (oci://ghcr.io/dasomel/charts/ldapium)

  • StatefulSet with per-replica PVCs, headless Service, PDB, and topology
    spread. replicaCount: 1 runs standalone; above that, replication turns on
    and the peer list is wired automatically.
  • Refuses to render without an admin password.
  • Optional UI Deployment, and a backup CronJob that dumps the data tree and
    cn=config, prunes by retention, and records each run into ou=operations.

Also in this repo

  • docker-compose.yml for a non-Kubernetes deployment, and scripts/backup.sh
    covering the same ground as the CronJob outside Kubernetes.
  • scripts/get-credentials.sh for retrieving admin credentials from either a
    Compose or a Kubernetes deployment.

Known limitations

  • TLS is unverified end to end. The templates render and the entrypoint
    handles certificates and TLS-protected replication, but no one has watched it
    work against a live cluster. It is off by default. Reports welcome.
  • The settings page lists modules and overlays from configuration, not from
    the server.
    Those values live in cn=config, which has its own admin
    identity that no UI session can hold, so the list is kept in step with
    image/Dockerfile by hand.
  • No upgrade path is promised yet. 0.1.0 is the first release; there is
    nothing to upgrade from.
  • The e2e workflow has never run. It installs the chart into a kind
    cluster and runs helm test; the test script itself was developed against a
    live directory and its failure modes verified, but the CI wiring around it
    is new.

What's Changed

  • fix(ci): unbreak server image build and shellcheck lint by @dasomel in #37
  • test(tls): cover certificate expiry and rotation, and document the renewal runbook by @dasomel in #38
  • fix(backup): make scheduled backups verifiable, and recover a 3-node directory in CI by @dasomel in #39
  • test(upgrade): cross an actual version, measure the cost, and gate on a backup by @dasomel in #40
  • test(replication): partition the cluster, and kill the node that elects itself by @dasomel in #46
  • feat(supply-chain): make the air-gap path testable, and test it by @dasomel in #47
  • test(metrics): inject the faults and require the alerts to fire by @dasomel in #48
  • test(security): deny an authenticated user another user's entry, and stop the module inventory drifting by @dasomel in #50
  • fix(supply-chain): stop two build tools floating on latest, and verify the module graph by @dasomel in #51
  • fix(supply-chain): pin every base image by digest by @dasomel in #52
  • feat(audit): emit an attributable record for every write by @dasomel in #53
  • fix(chart): stop helm test from asking twice for one pod's logs by @dasomel in #55
  • test(ci): make the "make check runs what CI runs" claim checkable by @dasomel in #54
  • fix(release): require the tagged commit to have been tested by @dasomel in #56
  • feat(supply-chain): record artifact digests and OpenLDAP provenance by @dasomel in #57
  • fix(supply-chain): stop resolving build tools against @latest by @dasomel in #58
  • feat(supply-chain): audit build/test egress on the dependency-resolution jobs by @dasomel in #59
  • docs(security): draw the encryption-at-rest boundary, fix backup transport by @dasomel in #60
  • docs(interop): draw the client compatibility boundary by @dasomel in #61
  • feat(audit): instantiate accesslog for reads, unify export with auditlog by @dasomel in #62
  • feat(scale): local reproduction benchmarks for load, search, replication by @dasomel in #63
  • fix(supply-chain): close the remaining #49 gaps — rollback, offline cache, egress test by @dasomel in #64
  • fix(ci): actually mint the GitHub attestations docs already promise by @dasomel in #65
  • feat(ui): add a cn=Monitor health view (#45) by @dasomel in #66
  • test(ui): add this repo's first browser E2E (#45) by @dasomel in #67
  • feat(ui): add organizational metadata to users (#14) by @dasomel in #68
  • feat(ui): add admin lock (disable) for user accounts by @dasomel in #69
  • fix(security): redact internal error detail from HTTP responses by @dasomel in #70
  • feat(audit): capture bind events — including failures — in accesslog by @dasomel in #71
  • feat(security): enforce a TLS 1.2 floor via olcTLSProtocolMin by @dasomel in #72
  • feat(ui): add an unauthenticated LDAP provider health endpoint by @dasomel in #73
  • fix(security): redact userPassword from the DIT entry browser by @dasomel in #74
  • test(chaos): add a same-entry conflict scenario to replication chaos by @dasomel in #79
  • docs: add repo-wide CLAUDE.md, fix stale ui/README.md testing note by @dasomel in #80
  • feat(audit): export raw replication CSN discards + enable sync logging by @dasomel in #81
  • test(ldap): add LDAP injection fuzz coverage by @dasomel in #82
  • test(security): verify LDAP ACL deny-by-default boundaries by @dasomel in #83
  • feat(image): add opt-in mTLS SASL EXTERNAL mapping by @dasomel in #84
  • test(tls): prove the two-step CA rotation procedure live by @dasomel in #85
  • feat(ops): add cn=config drift detection script by @dasomel in #86
  • feat(security): enforce an explicit olcTLSCipherSuite baseline by @dasomel in #87
  • fix(ops): make detect-config-drift --check compare like-for-like by @dasomel in #88
  • refactor(ci): share the LDAPS availability probe between both rotation tests by @dasomel in #89
  • test(security): prove rootdn and user writes are distinct actors in the audit export by @dasomel in #90
  • feat(ui): throttle failed password logins per client IP by @dasomel in #91
  • test(session): make fakeClient.closed atomic so -race passes by @dasomel in #92
  • feat(image): opt-in LDAP_ANONYMOUS_READ_...
Read more