fix: let a connection open when CLIENT SETINFO is denied - #685
Open
vishal-bala wants to merge 1 commit into
Open
fix: let a connection open when CLIENT SETINFO is denied#685vishal-bala wants to merge 1 commit into
vishal-bala wants to merge 1 commit into
Conversation
vishal-bala
marked this pull request as ready for review
August 13, 2026 08:33
nkanu17
force-pushed
the
fix/acl-drop-echo-identification-fallback
branch
from
August 14, 2026 14:53
6d91657 to
93e9fb3
Compare
vishal-bala
force-pushed
the
fix/acl-drop-echo-identification-fallback
branch
from
August 14, 2026 14:58
93e9fb3 to
d532270
Compare
RedisVL announces itself on connect with `CLIENT SETINFO LIB-NAME` and, if that
is refused, sends the same string through `ECHO`. The `ECHO` call was
unguarded, so `NoPermissionError` escaped while the connection was being
created, before any index operation could be attempted:
NoPermissionError: User <name> has no permissions to run the 'echo' command
An application role assembled from `@read`/`@write` hits this. `CLIENT SETINFO`
is tagged `@connection` and `@slow`, `ECHO` is `@connection` and `@fast`, and
neither is in `@read` or `@write` -- so a rule built up from those categories
never grants either. Subtracting `@dangerous` is not what denies them: measured
on Redis 8.4.5, `+@ALL -@dangerous` permits both, which is why the shape that
fails is a hand-written `+@READ +@write` rule rather than a broad rule with
exclusions. Note also that `+@READ +@Write +@slow` permits `CLIENT SETINFO`
while still denying `ECHO`, so the fallback was never a reliable second chance.
The `ECHO` fallback is deleted rather than guarded. It was added in 934d269
(#155) as a telemetry breadcrumb for servers older than Redis 7.2, where
`CLIENT SETINFO` does not exist, so the library name would at least appear in
`MONITOR` or the slowlog. It earns nothing today: it fires only when
`CLIENT SETINFO` errors, its argument reaches nothing that reads `lib-name`
(`CLIENT LIST` and `CLIENT INFO` take that field from `SETINFO` alone, and it
would only enter the slowlog with `slowlog-log-slower-than` near zero), and
redis-py already covers the old-server case one layer down, sending its own
`CLIENT SETINFO` during the connection handshake under
`try/except ResponseError: pass`.
The `hasattr(client, "echo")` guard goes with it, along with the comment
claiming `RedisCluster` has no `echo`; both `redis.cluster.RedisCluster` and
`redis.asyncio.cluster.RedisCluster` expose it.
The four duplicated blocks are now one sync and one async helper. The explicit
`client_setinfo` call is kept deliberately -- redis-py's handshake reports its
own name, and this overwrites it with the composed
`redis-py(redisvl_v...;<wrapper>)` string that adoption metrics read -- and the
helper docstring says so, so it does not read as redundant with the handshake.
`except ResponseError` is also deliberate. In the connection-factory path this
is the first command on a freshly created connection and therefore the de-facto
connectivity check, so broadening to `RedisError` would swallow
`ConnectionError` and defer a real failure to some later command.
`AuthenticationError` subclasses `ConnectionError`, not `ResponseError`, so
`WRONGPASS` and `NOAUTH` keep propagating either way.
## Tests
- `tests/unit/test_client_identification.py` -- 16 cases over both twins.
Mutation-checked: restoring the `ECHO` fallback fails six unit cases plus the
integration test, and broadening the `except` to `Exception` fails two. Both
refusals are covered, since only one is a permission problem: a plain
`ResponseError` stands for a pre-7.2 server, which is the case that lets the
fallback go. Identification is asserted on all three URL shapes -- sentinel,
cluster, standalone -- because it sits after that fan-out and moving it into
one branch otherwise goes unnoticed; live cluster tests need
`--run-cluster-tests` and never run in CI.
- An integration test opening a connection under `+@READ +@write`, with the
premise pinned: `CLIENT SETINFO` must raise `NoPermissionError` for that user,
so the test cannot go vacuous if Redis ever grants it to that role. It lives
in `test_connection.py` beside the other identification tests.
- ACL user setup moves into an `acl_user` fixture, reused by the existing
`-@admin` test in `test_search_index.py`. Rules are applied after `reset`
because `ACL SETUSER` is additive and usernames are derived from the test's
node id; the user is dropped before the connections it authenticated; and the
fixture skips on deployments that reject `ACL SETUSER`.
## Docs
`docs/user_guide/installation.md` said a credential permitted to run neither
command fails at connection time. That is no longer true. The replacement names
`+client|setinfo` for anyone who wants RedisVL attributed in `CLIENT LIST`, and
is explicit that this labels only the connection RedisVL opens -- redis-py
labels the rest of the pool as plain `redis-py`.
It also gains a cluster caveat found while verifying this fix: `CLUSTER SLOTS`
is tagged `@slow` only, so `RedisCluster.from_url` cannot discover the topology
under a `+@READ +@write` rule and the connection fails before identification is
even attempted, reported as `Redis Cluster cannot be connected`. Such a
deployment needs `+cluster|slots` as well.
## Not in scope
The rest of the ACL documentation pass. `installation.md:189`, `:208`, `:210`
and `:223` are stale for a different reason -- they predate the
`create_index=False` opt-out -- and are corrected alongside it, not here.
Identification still reaches only one connection. The explicit call labels
whichever pooled connection it borrows, not the rest of the pool or any
reconnect, and on a cluster redis-py routes it to the default node alone.
Passing the composed name in as redis-py's `lib_name`/`driver_info` would fix
that, but `redis>=5.0,<8.0` straddles the deprecation of `lib_name` in favour
of `driver_info` and needs version-conditional handling.
vishal-bala
force-pushed
the
fix/acl-drop-echo-identification-fallback
branch
from
August 14, 2026 15:18
d532270 to
ce519f7
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
RedisVLannounces itself on connect withCLIENT SETINFO LIB-NAMEand, if that is refused, sends the same string throughECHO. TheECHOcall was unguarded, soNoPermissionErrorescaped while the connection was being created, before any index operation could be attempted:An application role assembled from
@read/@writehits this.CLIENT SETINFOis tagged@connectionand@slow,ECHOis@connectionand@fast, and neither is in@reador@write— so a rule built up from those categories never grants either.Subtracting
@dangerousis not what denies them. Measured on Redis 8.4.5,+@all -@dangerouspermits both, so the shape that fails is a hand-written+@read +@writerule rather than a broad rule with exclusions — which also means Redis Cloud's predefined Read-Write shape is unaffected, while a@read-shaped Read-Only rule would be affected. Note too that+@read +@write +@slowpermitsCLIENT SETINFOwhile still denyingECHO, so the fallback was never a reliable second chance.Why the fallback is deleted rather than guarded
It was added in 934d269 (#155) as a telemetry breadcrumb for servers older than Redis 7.2, where
CLIENT SETINFOdoes not exist, so the library name would at least appear inMONITORor the slowlog. It earns nothing today:CLIENT SETINFOerrors, and the ACL rule that denies one denies the other.lib-name—CLIENT LISTandCLIENT INFOtake that field fromSETINFOalone, and an O(1)ECHOonly enters the slowlog withslowlog-log-slower-thannear zero.CLIENT SETINFOduring the connection handshake undertry/except ResponseError: pass.The
hasattr(client, "echo")guard goes with it, along with the comment claimingRedisClusterhas noecho; bothredis.cluster.RedisClusterandredis.asyncio.cluster.RedisClusterexpose it.What is kept, deliberately
The four duplicated blocks become one sync and one async helper. The explicit
client_setinfocall stays: redis-py's handshake reports its own name, and this overwrites it with the composedredis-py(redisvl_v…;<wrapper>)string that adoption metrics read. The helper docstring says so, so it does not read as redundant with the handshake.except ResponseErroralso stays narrow. In the connection-factory path this is the first command on a freshly created connection and therefore the de-facto connectivity check, so broadening toRedisErrorwould swallowConnectionErrorand defer a real failure to some later command.AuthenticationErrorsubclassesConnectionError, notResponseError, soWRONGPASSandNOAUTHkeep propagating either way.Tests
tests/unit/test_client_identification.py— 16 cases over both twins. Mutation-checked: restoring theECHOfallback fails six unit cases plus the integration test, and broadening theexcepttoExceptionfails two.Both refusals are covered, since only one is a permission problem: a plain
ResponseErrorstands for a pre-7.2 server, which is the case that lets the fallback go. Identification is asserted on all three URL shapes — sentinel, cluster, standalone — because it sits after that fan-out and moving it into one branch otherwise goes unnoticed; live cluster tests need--run-cluster-testsand never run in CI.One integration test opens a connection under
+@read +@write, with the premise pinned:CLIENT SETINFOmust raiseNoPermissionErrorfor that user, so the test cannot go vacuous if Redis ever grants it to that role.ACL user setup moves into an
acl_userfixture, reused by the existing-@admintest. Rules are applied afterresetbecauseACL SETUSERis additive and usernames are derived from the test's node id; the user is dropped before the connections it authenticated; and the fixture skips on deployments that rejectACL SETUSER.Docs
docs/user_guide/installation.mdsaid a credential permitted to run neither command fails at connection time. That is no longer true. The replacement names+client|setinfofor anyone who wants RedisVL attributed inCLIENT LIST, and is explicit that this labels only the connection RedisVL opens — redis-py labels the rest of the pool as plainredis-py.It also gains a cluster caveat found while verifying this fix:
CLUSTER SLOTSis tagged@slowonly, soRedisCluster.from_urlcannot discover the topology under a+@read +@writerule and the connection fails before identification is attempted, reported as the misleadingRedis Cluster cannot be connected. Such a deployment needs+cluster|slotsas well.Not in scope
The rest of the ACL documentation pass. Four statements in
installation.mdare stale for a different reason — they predate acreate_index=Falseopt-out for the extension constructors — and are corrected alongside it in a follow-up PR.Identification still reaches only one connection: the explicit call labels whichever pooled connection it borrows, not the rest of the pool or any reconnect, and on a cluster redis-py routes it to the default node alone. Passing the composed name in as redis-py's
lib_name/driver_infowould fix that, butredis>=5.0,<8.0straddles the deprecation oflib_namein favour ofdriver_infoand needs version-conditional handling.Note
Low Risk
Narrow change to cosmetic client identification on connect; real connectivity and auth errors still propagate, with broad unit and integration coverage.
Overview
Fixes connection setup for ACL roles built from
+@read +@write, whereCLIENT SETINFO(and the oldECHOfallback) were denied and could abort connect before any index work.Connection behavior: Duplicated identification logic is consolidated into
_identify_client/_aidentify_client, which still callCLIENT SETINFO LIB-NAMEfor adoption metrics but ignoreResponseError(ACL denial or pre-7.2 servers) and log at debug. TheECHOfallback is removed so a deniedECHOno longer surfaces asNoPermissionError.ConnectionErroron that first command is not swallowed.Tests: New
acl_userfixture for temporary ACL users; integration test that+@read +@writecan connect without identification permission; largetest_client_identification.pyunit suite (sync/async, URL shapes, no ECHO).Docs:
installation.mdnow states connect succeeds withoutCLIENT SETINFO, documents optional+client|setinfo, and adds cluster+cluster|slotsfor+@read +@writeroles.Reviewed by Cursor Bugbot for commit ce519f7. Bugbot is set up for automated code reviews on this repo. Configure here.