Visitar URL original
fix(#5029): authentication hardening - brute-force lockout, strong generated password, unified policy, constant-time compare by robfrank · Pull Request #5088 · ArcadeData/arcadedb · GitHub
Skip to content

fix(#5029): authentication hardening - brute-force lockout, strong generated password, unified policy, constant-time compare - #5088

Merged
robfrank merged 5 commits into
mainfrom
fix/5029-auth-hardening-bruteforce-lockout
Jul 7, 2026
Merged

robfrank merged 5 commits into
mainfrom
fix/5029-auth-hardening-bruteforce-lockout

Conversation

@robfrank

@robfrank robfrank commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #5029

Summary

Hardens password/credential handling in the server security module, addressing the four items from the 2026-07 server audit:

  1. Brute-force lockout on password auth (MEDIUM): ServerSecurity.authenticate() now applies the same per-principal failure-count/lockout already used for API tokens - lockout after 5 failures for 30s, keyed by a bounded SHA-256 prefix of the user name, checked before user lookup, incremented on unknown-user and wrong-password, and cleared on success. Stale entries are purged by the existing cleanup timer.
  2. Strong generated password (MEDIUM): DefaultCredentialsValidator.generateRandomPassword() now draws 24 characters from a 62-char alphanumeric alphabet via SecureRandom (~143 bits, >=128 bits), replacing the 8-char (~32-bit) truncated UUID used to auto-generate the root password on first run.
  3. Unified create-user policy (LOW): PostServerCommandHandler.createUser now routes through credentialsValidator.validateCredentials (min length 8, correct message) via a new ServerSecurity.getCredentialsValidator() accessor, replacing the divergent off-by-one length < 4 check whose message said "5 minimum characters". This matches the min-8 policy already enforced by the REST PostUserHandler.
  4. Constant-time compare (LOW): passwordMatch() compares encoded hashes with MessageDigest.isEqual instead of String.equals, matching the cluster-token path.

Keying decision

Lockout is keyed by a bounded 64-bit SHA-256 prefix of the user name, mirroring the API-token path exactly (which keys by token-hash prefix, no IP). Threading source IP would require changing the authenticate signature across 9 call sites in 6 modules; the acceptance criterion only requires per-principal lockout "like API tokens". IP-scoped keying is noted as a possible future refinement.

Test plan

  • mvn -pl server test -Dtest=ServerSecurityAuthHardeningTest - lockout trips after 5 failures (even the correct password is then throttled), other principals are unaffected, a successful auth clears the counter, and constant-time compare returns correct booleans (including a malformed stored hash).
  • mvn -pl server test -Dtest=DefaultCredentialsValidatorTest - generated passwords are >=22 chars, alphanumeric, unique, mixed-case, and validate.
  • mvn -pl server test -Dtest=ServerSecuritySaltCacheTest - salt-cache + passwordMatch correctness unchanged.
  • mvn -pl server test -Dtest=PostServerCommandHandlerIT,UserManagementIT,ServerSecurityIT - create-user and auth paths pass (the pre-existing restoreDatabaseCommand failure is unrelated: it needs the integration module's restore support, and fails identically on main).

…ot password, unified create-user policy, constant-time compare
@mergify

mergify Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

@codacy-production

codacy-production Bot commented Jul 7, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 6 complexity

Metric Results
Complexity 6

View in Codacy

🟢 Coverage 74.19% diff coverage · -6.86% coverage variation

Metric Results
Coverage variation ✅ -6.86% coverage variation
Diff coverage ✅ 74.19% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (639bfd1) 135513 101084 74.59%
Head commit (f00ffc7) 167390 (+31877) 113371 (+12287) 67.73% (-6.86%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#5088) 31 23 74.19%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request hardens server authentication by introducing a brute-force lockout mechanism for password authentication, increasing the entropy of auto-generated passwords using SecureRandom, unifying the minimum password length policy to 8 characters, and utilizing constant-time comparison for password hashes to prevent timing attacks. The review feedback highlights a potential thread-safety issue where concurrent reads and in-place mutations of the failure-tracking long[] array can lead to data races, and a potential NullPointerException if a user's password hash is null.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +238 to +243
if (ts - entry[1] > PASSWORD_LOCKOUT_MS) {
entry[0] = 1;
entry[1] = ts;
} else
entry[0]++;
return entry;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The passwordFailures map uses a mutable long[] array to track failure counts and timestamps [1]. While the compute method is thread-safe per key, other threads calling authenticate read from these arrays concurrently without synchronization (via passwordFailures.get(userName)) [1]. Mutating the array elements in-place can lead to data races, memory visibility issues, and word tearing (since 64-bit long writes are not guaranteed to be atomic on 32-bit architectures). Treating the array as immutable by returning a new array instance on every update ensures safe publication and thread-safe reads.

      if (ts - entry[1] > PASSWORD_LOCKOUT_MS)
        return new long[] { 1, ts };
      return new long[] { entry[0] + 1, entry[1] };

}

if (!passwordMatch(userPassword, su.getPassword()))
if (!passwordMatch(userPassword, su.getPassword())) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If a user has a null password (which is possible during first-run initialization or if a user is created without a password) [1], calling su.getPassword() will return null [1]. Passing this to passwordMatch will cause a NullPointerException when hashedPassword.split("\\$") is executed [1]. Adding a null check prevents this and ensures a proper ServerSecurityException is thrown [1].

Suggested change
if (!passwordMatch(userPassword, su.getPassword())) {
if (su.getPassword() == null || !passwordMatch(userPassword, su.getPassword())) {

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review: authentication hardening (#5029)

Overall this is a solid, well-scoped hardening PR. The changes closely mirror the existing API-token brute-force path, the entropy math for the generated password is documented and correct, and the constant-time compare is applied at the right spot. Test coverage for the new lockout behavior (trip, cross-user isolation, counter reset, malformed hash) is good. A few points worth considering before merge.

1. Username-keyed lockout is a targeted account-lockout DoS vector (design tradeoff)

Because the lockout is keyed purely by user name with no IP/source scoping, an attacker who knows a valid username - and root is always a valid, well-known target - can lock that account out of every protocol (http, bolt, postgresw, mongodbw, grpcw, MCP, and the internal ArcadeDBServer.authenticate) by sending 5 bad passwords, and can keep it locked indefinitely by repeating 5 attempts every 30s. This converts an online-guessing mitigation into an availability regression against known accounts.

The PR body acknowledges IP keying as future work, which is fair, but this is the more impactful side of the tradeoff and deserves an explicit note in the issue/changelog. One mitigation that neutralizes the DoS while keeping brute-force protection: during lockout, still evaluate the supplied password and let a correct password through (an attacker does not have it, so guessing stays blocked, but the legitimate user is not locked out). The cost is that you keep paying the PBKDF2 CPU during lockout - which is itself a consideration - so at minimum I would make the threshold/window operator-configurable rather than hard-coded 5/30_000.

2. passwordFailures map can grow unbounded from attacker-controlled keys

The unknown-user branch calls recordPasswordFailure(userName) with the raw, arbitrary-length, attacker-supplied username. An attacker can flood distinct random usernames and grow the map until the 30s cleanup timer fires, giving a memory-pressure vector bounded only by request rate x 30s. The token path caps its key at an 8-char hash prefix, which limits distinct keys; the username path has no such bound. Consider capping the map size, hashing/truncating the key, or only recording failures for existing users (weighed against user-enumeration timing).

3. Minor: data race on the long[] counter

The lockout check reads existing[0]/existing[1] outside compute() while recordPasswordFailure mutates the same array in place. Worst case is a slightly off count - benign, and it matches the existing token path - but worth a comment acknowledging the intentional relaxed consistency.

4. Minor / nits

  • recordPasswordFailure duplicates the inline tokenFailures.compute(...) lambda almost verbatim. Extracting a shared recordFailure(map, key, windowMs) helper would keep the two paths from drifting again (the whole PR exists because these paths drifted).
  • No test covers lockout expiry after the 30s window / the cleanup-timer purge. Understandable since it needs a clock seam, but the reset-after-window branch in recordPasswordFailure and the timer removal are currently untested.
  • Routing PostServerCommandHandler.createUser through validateCredentials also newly enforces username length (min 4 / max 256). That is an improvement and consistent, just flagging it as a behavior change for existing clients that created short-named users via this endpoint.
  • docs/5029-auth-hardening.md reads as internal audit/working notes - confirm you intend to ship it under docs/.

Verified correct

  • passwordMatch: hash from encodePassword(password, salt, iterations) is the full ALG$ITER$SALT$HASH string, same format/length as the stored hashedPassword, so MessageDigest.isEqual compares equal-length arrays as intended, and the parts.length != 4 guard short-circuits before isEqual for malformed hashes (no throw).
  • Lockout window does not self-extend: throttled attempts throw before recordPasswordFailure, so the timestamp is not refreshed and lockout expires exactly 30s after the 5th failure. Good.
  • passwordFailures cleanup is wired into the same guarded timer as tokenFailures.

Nice work - the core hardening is correct; my only real ask is to surface the account-lockout DoS tradeoff (item 1) and consider bounding the map (item 2).

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Code Review: PR #5088 - Authentication hardening

Overall this is a well-scoped, high-quality change. The four fixes are on-target for #5029, the code mirrors the existing API-token lockout path closely, the tradeoffs are documented, and the review-cycle findings (safe-publish of the long[], bounded map key, null-password guard) were folded in cleanly. Nice work. A few notes below, mostly minor.

Strengths

  • Brute-force lockout correctly checks the lockout before user lookup (fast reject, no user enumeration via this path) and records failures uniformly on unknown-user and wrong-password, so it doesn't leak whether a principal exists.
  • recordPasswordFailure publishes a fresh long[] on every update instead of mutating in place, so concurrent readers in authenticate always see a consistent snapshot. This is actually safer than the older authenticateByApiToken path, which still mutates entry in place (out of scope here, but a good future cleanup to unify).
  • generateRandomPassword is clean: single shared SecureRandom, preallocated StringBuilder, ~143 bits from a 62-char alphabet. SecureRandom.nextInt(62) handles modulo bias via rejection sampling, so no entropy skew.
  • Constant-time compare is sound: for a wrong password hash is recomputed with the same salt/iterations extracted from the stored value, so both operands are always the same length (fixed 256-bit PBKDF2 output -> fixed Base64 length), which is exactly the case where MessageDigest.isEqual gives real constant-time behavior.

Suggestions

  1. docs/review-deferred-f327302e.md probably shouldn't ship. It's a review-cycle scratch artifact named by commit hash, referencing "claude" and "gemini-code-assist" review passes - it doesn't match the repo's docs/NNNN-<name>.md convention (which 5029-auth-hardening.md correctly follows). The rationale it captures is already in 5029-auth-hardening.md. Consider dropping it from the PR to keep docs/ as durable design notes rather than per-cycle review logs.

  2. Account-lockout DoS on root. Already documented as an accepted tradeoff, and it does mirror the token path, so no objection to merging as-is. Worth emphasizing that because the key is per-principal and root is well-known, an attacker can hold root locked across all protocols (http, bolt, postgres, mongo, grpc) with 5 bad passwords every 30s - a broader blast radius than the token path in practice. Making MAX_PASSWORD_FAILURES / PASSWORD_LOCKOUT_MS operator-configurable (as the doc's follow-up notes) would let operators tune or disable this; a small, low-risk follow-up.

  3. No automated test for the PostServerCommandHandler.createUser policy change. The min-length-8 routing through validateCredentials is only covered by the manual PostServerCommandHandlerIT in the test plan. A cheap unit/handler test asserting a 4-7 char password is now rejected with the correct message would lock in the fix and guard the regression. Note this is a stricter contract - previously-accepted 4-7 char passwords and <4 char usernames will now be rejected - so it's worth an explicit test.

  4. passwordMatch malformed-hash robustness (pre-existing, minor). The new test asserts a malformed stored hash "must not throw", but only exercises the parts.length != 4 branch. A stored value like PBKDF2$notanumber$salt$hash (4 parts, non-numeric iterations) still throws NumberFormatException at Integer.parseInt(parts[1]). This predates the PR, but since you added the malformed-hash test and touched this method, wrapping the parse (or a try/catch -> return false) would make the "must not throw" guarantee hold for all malformed inputs.

Minor / FYI

  • The lockout window is anchored at the first failure in a window (entry[1] is preserved on increment), so the 30s clock starts at failure build support #1, not Bump jedis from 3.6.1 to 3.6.3 #5. This is identical to the token path, so it's consistent - just noting the window is "30s from first failure," not "30s from last failure."
  • Stale passwordFailures entries are only purged by the cleanup timer scheduled in loadUsers() (server != null path). Entries self-expire logically on read, so this is only a transient-memory concern bounded by the 30s cleanup interval - same as tokenFailures. Fine as-is.

Tests, style (final params, AssertJ assertThat(...).isTrue() form, no fully-qualified names), and comments all read consistently with the surrounding code. LGTM once the review-deferred doc question is resolved.

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review: authentication hardening (#5029)

Overall this is a solid, well-scoped security PR. The four hardening items are each implemented cleanly, mirror the existing API-token path, and come with focused regression tests. The tracking doc, the DoS tradeoff write-up, and the "deferred with rationale" notes make the intent easy to follow. Nice work.

Strengths

  • Brute-force lockout correctly checks the counter before user lookup and increments on both unknown-user and wrong-password, so it does not leak account existence via the lockout path.
  • recordPasswordFailure safe-publish: returning a fresh long[] per update (rather than the in-place mutation the token path still does) is the more correct concurrency pattern. Good catch during review.
  • Null stored-password guard folded into the credential-check branch prevents an NPE in passwordMatch and still returns the generic "User/Password not valid".
  • generateRandomPassword is now SecureRandom over a 62-char alphabet with a clearly-documented entropy budget - a real improvement over the ~32-bit truncated UUID.
  • passwordMatch correctly compares the full algorithm$iterations$salt$hash string (both same length/format), so MessageDigest.isEqual is a valid constant-time comparison, and the NumberFormatException guard on the iteration count is a good robustness add.
  • Good test coverage: lockout trip, cross-principal isolation, counter reset on success, and both malformed-hash failure modes.

Minor points (non-blocking)

  1. Lockout window is anchored to the first failure, not the last. recordPasswordFailure preserves entry[1] (first-failure timestamp) across increments, and the lockout check is now - entry[1] < PASSWORD_LOCKOUT_MS. So if the 5 failures are spread over, say, 24s, the effective lockout after tripping is only ~6s rather than a full 30s from the last attempt. This is consistent with the existing token path, so it is fine to keep for parity - but it is a "lockout since first failure" rather than the more common "lockout since last failure" semantics, and worth a one-line comment so a future reader does not mistake it for the latter.

  2. Duplicated constants. MAX_PASSWORD_FAILURES/PASSWORD_LOCKOUT_MS are byte-for-byte identical to MAX_TOKEN_FAILURES/TOKEN_LOCKOUT_MS, and the two compute lambdas / cleanup-timer removals are near-identical. You deliberately deferred the shared-helper refactor to avoid touching the tested token path, which is reasonable - but since the password path already improved on the token lambda (safe-publish), the two are now subtly different implementations of the same thing. A follow-up that unifies them (and back-ports the safe-publish fix to the token path, which still mutates long[] in place around lines 747-752) would be worthwhile.

  3. Behavior change: username length now validated on the server-command create-user path. Routing through validateCredentials means CREATE USER {...} now also enforces userName.length() >= 4, where before it validated only the password. This is the intended unification, but it is a stricter contract than before - fine given it is documented, just flagging for release notes.

  4. Distinguishable lockout message. During lockout the caller gets "Too many failed authentication attempts..." vs "User/Password not valid" otherwise. Since lockout applies to unknown users too, this does not enable enumeration, but it does confirm to an attacker that they have successfully triggered a lockout (useful for the documented targeted-account DoS). Acceptable given the accepted tradeoff.

Question

  • Should docs/5029-auth-hardening.md ship in the repo long-term? It follows the existing docs/NNNN-*.md convention, so this is just a confirmation - the content reads more like an internal audit/PR narrative than user-facing documentation.

Nothing here is blocking. The keying-by-principal DoS is inherent and well-documented, and making the threshold/window operator-configurable plus optional IP-scoped keying are the right follow-ups to capture in an issue.

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review: PR 5088 - Authentication hardening

Overall a high-quality, well-scoped security PR. The four fixes are correct, the code mirrors the existing API-token path closely, and the concurrency reasoning (safe-publish of fresh long[] snapshots, ThreadLocal SHA-256 digest, atomic compute) is sound. Tests and the docs/5029 write-up are thorough. A few observations, none blocking.

Strengths

  • Lockout check placed before user lookup, and failures recorded for both unknown-user and wrong-password with the same "User/Password not valid" message. This avoids leaking user existence, and since unknown users also get locked out, the distinct "Too many failed attempts" message does not reveal a valid account either.
  • Safe publication: recordPasswordFailure returns a fresh long[] per update instead of mutating in place, so the lock-free read at authenticate:206 always sees an internally-consistent snapshot. This is better than the pre-existing token path (authenticateByApiToken:751-754), which still mutates entry[0]++/entry[1] in place under a benign data race.
  • Null-password guard (su.getPassword() == null) before passwordMatch, and passwordMatch now returns false (rather than throwing) on both malformed part-count and non-numeric iterations - both well tested.
  • Constant-time compare and the 62-char/24-length SecureRandom password are correct; the entropy math checks out (~143 bits).

Points worth considering

  1. Behavior change in the server-command create-user path (compat). Routing through validateCredentials now also enforces userName min-length 4 (and empty/too-long checks), where previously the server-command path only checked json.has("name"). Existing automation creating users with 1-3 char names via POST /server will now get a 403. Arguably the intended "unified policy," but it is a wire-visible change beyond just the password rule - worth a release note.

  2. Internal/service-account self-lockout. Because lockout is per-principal across all protocols, a service or HA/replication account that authenticates frequently and occasionally supplies a stale credential could lock itself out for 30s cluster-wide. The DoS tradeoff doc covers the attacker case; the accidental self-lockout of automated callers is worth a line in the doc.

  3. Divergence between the two failure-tracking paths. The password path uses the safer fresh-array pattern while the token path still mutates in place. Keeping them subtly different invites future drift. Consider back-porting the fresh-array pattern to authenticateByApiToken (low risk, same shape) or extracting a shared recordFailure(map, key, max, window) helper. The PR intentionally deferred this to avoid touching tested token code - reasonable, but the inconsistency is now baked in.

  4. Unbounded entry count between cleanup cycles. Hashing the key bounds per-entry size, but an attacker spraying distinct usernames still creates one map entry each until the 30s cleanup timer runs. Same as the token path, so acceptable, but the doc phrase "cannot inflate per-entry memory" could be misread as bounding total memory, which it does not.

Test coverage
Good coverage of the core paths. Gaps that would strengthen it:

  • Lockout window expiry - no test that auth succeeds again after PASSWORD_LOCKOUT_MS elapses; the counter-reset-on-stale-window branch in recordPasswordFailure is currently unexercised.
  • Unknown-user lockout - tests only lock out an existing user; asserting repeated unknown usernames also trip the lockout (with the same generic message) would lock in the non-enumeration property.
  • Minor: PostServerCommandHandlerIT.createUserRejectsShortPassword leads with DROP USER on a user that may not exist - confirm that is idempotent so the test is not order-dependent.

Nit: the PR summary says lockout is "keyed by user name," while the code/doc key by a SHA-256 prefix - a description mismatch only; the code is correct.

Nice work - the security reasoning and concurrency care here are above the bar.

@claude

claude Bot commented Jul 7, 2026

Copy link
Copy Markdown

Review: authentication hardening (#5029)

Overall this is a well-scoped, well-tested, and unusually well-documented change. The four hardening items are implemented cleanly, the API-token path is mirrored faithfully, and the review-cycle fixes (safe-publish of the counter, bounded key, null-password guard, malformed-hash guard) resolved the substantive correctness issues. Nice work.

A few observations, mostly non-blocking.

Correctness / security

  1. Per-principal lockout is a real cross-protocol DoS on well-known accounts. Already documented in docs/5029-auth-hardening.md, but worth restating as the most important tradeoff: an unauthenticated attacker who knows a valid name (notably root) can hold that account in lockout indefinitely by sending 5 bad passwords every 30s, and because authenticate() is the shared entry point for HTTP, Bolt, Postgres, Mongo, gRPC and MCP, the lockout denies that principal across all protocols at once. Mirroring the API-token behavior is defensible, but I'd prioritize the deferred IP-scoped keying, since root lockout has a materially higher blast radius than an opaque API token. At minimum, consider not locking out the built-in root account on principal alone.

  2. User-enumeration timing oracle is untouched. shouldLockOutUnknownUserWithGenericMessage correctly locks in the identical message, but the timing still differs: an unknown user hits recordPasswordFailure and throws immediately, while a known user with a wrong password runs a full PBKDF2 (passwordMatch) first. That's an observable enumeration side channel. It's pre-existing (the original if (su == null) throw had it) and arguably out of scope, but since the PR is titled "auth hardening" and already touches this method, it deserves at least a follow-up note.

  3. Failure-map memory is bounded per-entry but not in aggregate. The doc acknowledges this honestly. Since hashing already gives a fixed-size key, a hard cap on passwordFailures.size() (skip new keys past a ceiling, or evict oldest) would cheaply close the spray-distinct-usernames vector that the 30s cleanup timer alone doesn't fully bound. Optional.

Maintainability

  1. The token and password paths are now near-duplicate but diverge on the one thing that matters. recordPasswordFailure publishes a fresh long[] (safe), while the inline token lambda in authenticateByApiToken still mutates entry[0]++ in place - the exact race fixed here. Deferring the shared-helper extraction is reasonable, but the safe-publish fix arguably should be back-ported to the token path too, otherwise it retains the latent torn-read the reviewers flagged. A tracked follow-up (already noted in the doc) is fine.

Docs / housekeeping

  1. Consider whether the review-process meta-commentary belongs in a durable docs/ file. The design rationale in docs/5029-auth-hardening.md is genuinely useful, but the "Review cycles" section (e.g. "gemini-code-assist ... re-reviews inconsistently", "max-cycles-reached (4/4)") is process chatter that will age poorly in the repo. I'd trim to the technical rationale and drop the reviewer-behavior notes.

  2. Minor: comment vs. test mismatch. createUserRejectsShortPassword uses a 5-char password ("short"), but the Javadoc and PR description say "7-char". Cosmetic, but worth aligning.

Nits (non-blocking)

  • The PR test plan says generated passwords are "mixed-case", but that's probabilistic and (correctly) not asserted by the test; the wording overstates the guarantee.
  • MessageDigest.isEqual short-circuits on length mismatch, but since the computed hash reuses the salt parsed from the stored value, lengths always match for a well-formed hash, so the content comparison stays constant-time. Good.

Test coverage is solid for the in-scope behavior (lockout trip, isolation between principals, counter reset, unknown-user parity, malformed-hash handling, create-user policy). The only meaningful gap is the window-expiry path, which the doc reasonably explains away as needing a clock seam or a 30s wait.

@robfrank
robfrank merged commit 054d8bc into main Jul 7, 2026
22 of 26 checks passed
@robfrank
robfrank deleted the fix/5029-auth-hardening-bruteforce-lockout branch July 7, 2026 10:37
@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 61.29032% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 65.97%. Comparing base (639bfd1) to head (f00ffc7).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
...a/com/arcadedb/server/security/ServerSecurity.java 66.66% 5 Missing and 3 partials ⚠️
...curity/credential/DefaultCredentialsValidator.java 33.33% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #5088      +/-   ##
============================================
+ Coverage     65.61%   65.97%   +0.35%     
- Complexity      841      847       +6     
============================================
  Files          1690     1690              
  Lines        135513   135587      +74     
  Branches      28987    29000      +13     
============================================
+ Hits          88920    89450     +530     
+ Misses        34466    33897     -569     
- Partials      12127    12240     +113     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

robfrank added a commit that referenced this pull request Aug 14, 2026
…nerated password, unified policy, constant-time compare (#5088)

(cherry picked from commit 054d8bc)
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.

[server audit] Authentication hardening: no brute-force lockout on password auth, weak generated root password, inconsistent policy, non-constant-time compare

1 participant