Skip to content

Stop rejecting long AD passphrases before the LDAP bind - #209

Merged
Sev7eNup merged 1 commit into
mainfrom
fix/ldap-long-passphrase-login
Aug 15, 2026
Merged

Stop rejecting long AD passphrases before the LDAP bind#209
Sev7eNup merged 1 commit into
mainfrom
fix/ldap-long-passphrase-login

Conversation

@Sev7eNup

Copy link
Copy Markdown
Owner

The 72-byte password gate at the top of Login() is BCrypt's truncation limit, but it ran before TryLdapLoginAsync. An AD account with a passphrase longer than ~72 ASCII characters received a generic 401 with the audit reason invalid_password_length and never reached the directory — and a ~12-word passphrase is exactly what the security-conscious orgs deploying LDAP SSO hand out. AD itself permits up to 256 characters, and a directory password never touches BCrypt.

Change

  • New MaxDirectoryPasswordBytes = 256 bounds the login payload at the entrance. The anonymous-endpoint amplifier stays closed; [RequestSizeLimit(8 KiB)] on the endpoint already carried most of that weight anyway.
  • The BCrypt-specific 72-byte reject moves to the local branch, placed ahead of the lockout reservation so an unauthenticatable length still costs no throttle budget and still audits as invalid_password_length.
  • The bootstrap path is untouched: it already runs ValidatePasswordPolicy, which caps at the same 72 bytes and returns the descriptive 400 instead of a bare 401.
  • The M-32 comment no longer claims the "every password-setting path runs ValidatePasswordPolicy" premise holds for LDAP — it only ever held for local accounts.

Tests

tests/NodePilot.Api.Tests/Controllers/AuthControllerLdapTests.cs:

  • OverlongPassword_IsRejectedBeforeLdapOrThrottleWork now pins the short-circuit at the directory maximum.
  • LongPassphrase_PastBcryptsLimit_StillReachesTheLdapBind — a 100-byte passphrase reaches the bind and arrives unshortened (FakeLdapConnectionAdapter gained LastPassword).
  • OverlongPassword_AgainstLocalAccount_IsStillRejectedWithoutSpendingLockoutBudget — the local half still rejects, audits invalid_password_length, and leaves FailedLoginCount at 0.

Run: dotnet test tests/NodePilot.Api.Tests --filter "FullyQualifiedName~AuthControllerLdapTests" → 21 passed.

Docs updated in step: docs/ldap-windows-sso.md (security invariants) and the docs-site security overview.

Closes #182

The 72-byte password gate at the top of Login() is BCrypt's truncation
limit, but it ran ahead of TryLdapLoginAsync. An AD account with a
passphrase longer than ~72 ASCII characters got a generic 401 with the
audit reason invalid_password_length and never reached the directory —
the very orgs that deploy LDAP SSO are the ones handing out 12-word
passphrases.

The entrance now bounds the payload at 256 bytes (Active Directory's own
maximum), which keeps the anonymous-endpoint amplifier closed alongside
the existing 8 KiB request-size limit. The BCrypt-specific 72-byte
reject moves to the local branch, ahead of the lockout reservation, so
an unauthenticatable length still costs no throttle budget and still
audits as invalid_password_length. The bootstrap path is unaffected: it
already runs ValidatePasswordPolicy, which caps at the same 72 bytes and
returns the descriptive 400.

Closes #182
@Sev7eNup
Sev7eNup merged commit ecab3ba into main Aug 15, 2026
9 checks passed
@Sev7eNup
Sev7eNup deleted the fix/ldap-long-passphrase-login branch August 15, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PR-Review #167: 72-byte password reject sits ahead of the LDAP bind and can lock out long-passphrase AD users

1 participant