Skip to content

Add ACL DIGEST command - #4446

Open
melancholictheory wants to merge 2 commits into
valkey-io:unstablefrom
melancholictheory:acl-digest
Open

Add ACL DIGEST command#4446
melancholictheory wants to merge 2 commits into
valkey-io:unstablefrom
melancholictheory:acl-digest

Conversation

@melancholictheory

Copy link
Copy Markdown
Contributor

Fixes #4355

A controller that manages a server's ACL by writing an aclfile and calling ACL LOAD has no way
to confirm which revision the server actually loaded. Comparing users and password hashes works,
since ACL GETUSER returns the hashes verbatim, but comparing rules does not. ACL GETUSER
reports the server's normalized form while the controller only holds the original file text, so
matching the two means reimplementing the ACL parser and keeping it in step with the server. A
permission-only change, where the users and the passwords stay the same and only the rules move,
cannot be confirmed at all.

ACL DIGEST returns a fingerprint of the rules currently in effect, as a hex string:

127.0.0.1:6379> ACL DIGEST
"24ece0a4daea0bcb936b438ae6c9f36db76f57dfc70e00def7b2c26041c49822"

Read it before and after a LOAD and you know whether the revision you wrote is the one running,
and the comparison needs no rule parsing on the client side.

How it works

For every user the SHA256 of its name and its rule string is computed, and the per user digests
are combined with XOR, as @zuiderkwast suggested in the issue.

XOR is commutative, so the result does not depend on the order the users are visited in. They are
kept in a radix tree and are already sorted, but the digest does not have to rely on that. XOR
also cancels out two equal values, so hashing the rules alone would let a pair of users with the
very same rules contribute nothing. The name is hashed together with the rules to keep each user
distinct, and there is a test for that case.

What gets hashed per user is the line ACL LIST reports for it, minus the leading user keyword,
so it covers the flags, the passwords, the commands, the keys and the channels. Any edit to any
user moves the digest, and the reply stays the same size however large the ACL gets.

The reply of ACL LOAD is left alone, since changing it would break existing clients.

ACL DIGEST carries the same flags and categories as ACL LIST and ACL USERS. It is built from
the same data, and anyone who can run ACL LOAD already has those permissions.

The hex encoding loop moved out of ACLHashPassword into a small helper, so the new code reuses
it rather than repeating it.

Testing

New cases in tests/unit/acl.tcl:

  • the digest is stable across calls and has a fixed size
  • it follows a user's rules, and goes back to the previous value when a rule is put back
  • it follows a user's passwords
  • two users with identical rules do not cancel each other out
  • an ACL LOAD of an equivalent file, written in a different order and with the aliases of the
    same rules, leaves the digest untouched, while a permission-only edit moves it
  • arity and permission checks

Documentation for the command will follow in a valkey-doc PR.

A controller that manages a server's ACL by writing an aclfile and
calling ACL LOAD has no way to confirm which revision the server loaded.
Users and password hashes can be compared, since ACL GETUSER returns the
hashes verbatim, but rules cannot: ACL GETUSER reports the server's
normalized form while the controller only holds the original file text.
A permission only change, same users and same passwords with different
rules, cannot be confirmed at all.

ACL DIGEST replies with a fingerprint of the rules currently in effect,
as a hex string, so two revisions can be told apart without parsing any
rule on the client side.

For every user the SHA256 of its name and its rule string is computed,
and the per user digests are combined with XOR. XOR is commutative, so
the result does not depend on the order the users are visited in and the
digest does not have to rely on the radix tree keeping them sorted. XOR
also cancels out two equal values, so the name is hashed together with
the rules to keep a pair of users having the very same rules from
contributing nothing.

The hashed content of a user is the line ACL LIST reports for it without
the leading "user " keyword, so it covers the flags, the passwords, the
commands, the keys and the channels. Any edit to any user moves the
digest, and the reply stays the same size however large the ACL gets.

The reply of ACL LOAD is left unchanged, since changing it would break
existing clients.

Signed-off-by: melancholictheory <selimvhorst@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 3cdd41db-c9c1-4659-a4e6-30d66b962462

📥 Commits

Reviewing files that changed from the base of the PR and between f1b114d and 459ccb5.

📒 Files selected for processing (1)
  • src/acl.c
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/acl.c

Included review availability: Your plan includes up to 10 reviews per rolling hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The PR adds ACL DIGEST, which returns a lowercase hexadecimal fingerprint of effective ACL rules. It adds shared digest formatting, aggregates per-user SHA256 digests, registers command metadata, and tests ACL changes and reloads.

Changes

ACL digest

Layer / File(s) Summary
Digest computation
src/acl.c
Adds ACLHexDigest for lowercase hexadecimal output. Adds ACLDigest, which XORs per-user SHA256 digests derived from usernames and ACL descriptions.
Command exposure
src/acl.c, src/commands.def, src/commands/acl-digest.json
Registers ACL DIGEST, adds command metadata, and updates ACL synopsis and help output.
Digest behavior validation
tests/unit/acl.tcl
Tests output format, rule and password changes, reversions, argument validation, permissions, and ACL file reload behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Merge Risk: ⚪ Minimal · up to 459cc

The ACL DIGEST change is merge-ready after normal checks and review; no actionable merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ACLCommand
  participant ACLDigest
  participant ACLUsers
  Client->>ACLCommand: Execute ACL DIGEST
  ACLCommand->>ACLDigest: Compute ACL fingerprint
  ACLDigest->>ACLUsers: Read user names and ACL descriptions
  ACLUsers-->>ACLDigest: Return effective ACL state
  ACLDigest-->>ACLCommand: Return hexadecimal fingerprint
  ACLCommand-->>Client: Return ACL digest
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: adding the ACL DIGEST command.
Description check ✅ Passed The description explains the ACL DIGEST command, its purpose, implementation, permissions, and tests.
Linked Issues check ✅ Passed The changes satisfy issue #4355 by providing a stable, order-independent ACL fingerprint that detects permission-only changes without client-side ACL parsing.
Out of Scope Changes check ✅ Passed The command implementation, metadata, refactoring, and tests are directly related to the ACL DIGEST feature and linked issue #4355.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@valkey-review-bot valkey-review-bot 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.

The digest implementation and focused coverage look sound. One command-metadata issue remains for cluster-aware clients.

"LOADING",
"STALE",
"SENTINEL"
],

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.

This is node-local state, but there is no request-policy tip telling cluster clients to query every node. ACL SETUSER, ACL DELUSER, and ACL SAVE all declare REQUEST_POLICY:ALL_NODES, and read-only node-local introspection such as SLOWLOG GET pairs that with NONDETERMINISTIC_OUTPUT. Without the same metadata here, a cluster-aware client can route ACL DIGEST to one arbitrary node and miss an ACL revision that differs elsewhere. Add command_tips with REQUEST_POLICY:ALL_NODES and NONDETERMINISTIC_OUTPUT.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The comparisons are accurate: ACL SETUSER, ACL DELUSER and ACL SAVE do carry REQUEST_POLICY:ALL_NODES, and SLOWLOG GET does pair it with NONDETERMINISTIC_OUTPUT. I left both tips off on purpose.

NONDETERMINISTIC_OUTPUT would say the wrong thing here. The definition in command-tips is that calls "may yield different results with the same arguments and data", which is what INFO, CLIENT LIST and TTL do. ACL DIGEST is the opposite: the same ACL state always produces the same reply. That is the contract of the command and there is a test asserting it. The tip tells a client the reply is not meant to be compared, and comparing it is the only thing the command is for.

REQUEST_POLICY:ALL_NODES is a fairer question, but I do not think it belongs on this command alone. The rest of the ACL read family carries no tips at all: ACL LIST, ACL USERS, ACL GETUSER, ACL WHOAMI, and ACL LOAD too. ACL DIGEST is node local in exactly the way ACL LIST is. Tagging only the digest puts a cluster client in an odd spot, where it fans out ACL DIGEST, finds two nodes disagreeing, and then cannot fan out ACL LIST the same way to see what differs. There is also no useful RESPONSE_POLICY for a set of hex strings, so it would end up as SPECIAL.

If node-local ACL introspection should be fanned out, I would rather do it as one change across the whole family than on the single new command, and I can open that separately. If a maintainer wants the tip here now, say so and I will add it.

@melancholictheory

Copy link
Copy Markdown
Contributor Author

A few things I checked while writing this, so a reviewer does not have to chase them.

ACLDescribeUser increments the refcount on both paths, the cached one and the one that builds the string, so the caller owns a reference. ACLDigest releases it the same way the two existing callers do, in ACLSaveToFile and in the ACL LIST / ACL USERS branch, which read the value with objectGetVal and then decrRefCount the object.

The space between the name and the rules is safe as a separator because the server rejects a username containing one. ACL SETUSER fails with "Usernames can't contain spaces or null characters", and the aclfile path validates the same way, so the pair cannot be read two ways.

The command flags and ACL categories are copied from acl-list.json on purpose. ACL DIGEST is built from the same data those commands expose, so anything that would let a client read the rules through ACL LIST already covers reading a fingerprint of them.

since is set to 9.2.0 on the assumption that this lands in the next feature release off unstable. Happy to change it if the target is different.

The valkey-doc PR is on me, I will open it once the command shape here is settled.

@zuiderkwast zuiderkwast 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.

Very nice PR.

Very thorough tests. (I would have accepted less. 😆)

Comment thread src/acl.c Outdated
@zuiderkwast zuiderkwast added the release-notes This issue should get a line item in the release notes label Aug 17, 2026
@zuiderkwast zuiderkwast moved this from Todo to Needs Review in Valkey 9.2 Aug 17, 2026
@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.79%. Comparing base (5d3fd68) to head (459ccb5).
⚠️ Report is 3 commits behind head on unstable.

Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #4446      +/-   ##
============================================
+ Coverage     78.77%   78.79%   +0.01%     
============================================
  Files           170      170              
  Lines         89782    89807      +25     
============================================
+ Hits          70725    70761      +36     
+ Misses        19057    19046      -11     
Files with missing lines Coverage Δ
src/acl.c 92.76% <100.00%> (+0.10%) ⬆️
src/commands.def 100.00% <ø> (ø)

... and 26 files with indirect coverage changes

🚀 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.

The reasoning about ordering and about the shape of the hashed string
belongs in the pull request and the issue rather than in the code. Keep
the note about the username being hashed along with the rules, since
that is the part a later change could drop without an obvious reason.

Signed-off-by: melancholictheory <selimvhorst@gmail.com>

@zuiderkwast zuiderkwast 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.

LGTM, thanks!

We'll need for the majory decision in the issue before merging. (We'll auto-approve after two weeks if no TSC members have any objections.)

@enjoy-binbin enjoy-binbin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Top comment LGTM, did not review the code.

@madolson

Copy link
Copy Markdown
Member

+1 for the context of core team approval, API seems fine.

@melancholictheory

Copy link
Copy Markdown
Contributor Author

Correcting myself: I wrote above that the valkey-doc PR was still to come, but it was already open by then. It is valkey-io/valkey-doc#469, and it has had no review yet.

It adds commands/acl-digest.md and the matching entries in resp2_replies.json and resp3_replies.json. The example output on the page came from a server built from this branch rather than being written by hand.

Nothing breaks if it lands first or sits: the doc Makefile builds the intersection of the Markdown pages and the command JSON under VALKEY_ROOT/src/commands, so the page stays out of the build until this PR adds acl-digest.json. Merging them together is the tidiest, though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

major-decision-pending-auto-approve release-notes This issue should get a line item in the release notes

Projects

Status: Needs Review

Development

Successfully merging this pull request may close these issues.

Expose a digest of the loaded ACL (e.g. ACL DIGEST) so controllers can confirm a live ACL LOAD

4 participants