fix(gateway): match SimpleX allowlist on local alias, not spoofable profile name - #41246
tanebrae-yz wants to merge 1 commit into
Conversation
…rofile name ## What does this PR do? I noticed the SimpleX allowlist could be bypassed by anyone who can message the bot. A recent change taught `SIMPLEX_ALLOWED_USERS` to accept display names (490c486), but the name it matched on — `source.user_name` — is the contact's *self-asserted profile display name*. That field is fully controlled by the remote contact and isn't unique, so if an operator listed a name in the allowlist (which the docs actively encourage, since the SimpleX UI never shows the numeric contactId), any other contact could just rename their own profile to that string and get full agent access. This was turning a stable-ID check into a guess-the-name check where the attacker controls both sides. The fix keeps the name convenience but anchors it to something the remote party can't forge: the contact's *local* display name — the alias the SimpleX CLI assigns and deduplicates locally (`alice`, `alice_1`, ...). The adapter now carries that alias in `user_id_alt` (the field already used for stable platform alt-IDs like the Signal UUID and Feishu union_id), and the gateway matches the allowlist against that instead of the profile name. A later contact who renames their profile to a taken alias gets `alice_1` locally, so they can't collide with a trusted contact. The numeric contactId via `user_id` still works and stays the most robust option. ## Related Issue N/A ## Type of Change - [x] 🔒 Security fix ## Changes Made - `gateway/run.py`: in `_is_user_authorized`, match `SIMPLEX_ALLOWED_USERS` against `source.user_id_alt` (the stable local alias) instead of `source.user_name` (the attacker-controlled profile display name). - `plugins/platforms/simplex/adapter.py`: capture `localDisplayName` for both DM contacts and group members and pass it through `build_source` as `user_id_alt`; update the module docstring and the interactive-setup prompt. - `tests/gateway/test_unauthorized_dm_behavior.py`: rework the display-name test to authorize via the local alias and add a regression test proving a spoofed profile name is rejected. - `website/docs/user-guide/messaging/simplex.md`: clarify that the allowlist matches the local alias / contactId, not the spoofable profile name. ## How to Test 1. `scripts/run_tests.sh tests/gateway/test_unauthorized_dm_behavior.py` — 34 pass, including the new `test_simplex_allowlist_rejects_spoofed_profile_name`. 2. `scripts/run_tests.sh tests/gateway/test_simplex_plugin.py` — 28 pass. 3. Repro before the fix: set `SIMPLEX_ALLOWED_USERS=alice`, message the bot from an untrusted contact whose profile name is "alice" but whose local alias is "alice_1" — old code authorized them, new code denies them. ## Checklist ### Code - [x] I've read the Contributing Guide - [x] My commit messages follow Conventional Commits (`fix(scope):`, etc.) - [x] I searched for existing PRs to make sure this isn't a duplicate - [x] My PR contains only changes related to this fix (no unrelated commits) - [x] I've run the gateway tests and they pass - [x] I've added tests for my changes (required for bug fixes) - [x] I've tested on my platform: macOS 15.5 ### Documentation & Housekeeping - [x] I've updated relevant documentation (docs/, docstrings) - [x] I've updated `cli-config.yaml.example` if I added/changed config keys — N/A - [x] I've updated `CONTRIBUTING.md` or `AGENTS.md` if I changed architecture or workflows — N/A - [x] I've considered cross-platform impact (Windows, macOS) - [x] I've updated tool descriptions/schemas if I changed tool behavior — N/A
|
Positive verification — security review Reviewed the full diff (gateway/run.py, plugins/platforms/simplex/adapter.py, tests, docs). No issues found. Key observations:
Clean security fix — approving for merge. |
|
Thanks for the focused security fix. The underlying authorization issue is still present on current main: Problems
Suggested changes
Automated hermes-sweeper review. |
|
I measured this against a real daemon, and the security rationale here does not hold — the local alias can be forced to collide. Setup: three Result at the bot prompt: Each remote sent a marker so the mapping isn't an assumption:
The daemon does deduplicate, but it suffixes the late arrival. The unsuffixed name is first-come-first-served, and the party that arrives first is not the one the operator controls. Auto-accept is on by default and the bot's contact link is public by construction, so the attacker connecting first is the normal setup order, not a race. Since this PR matches on #44741, which removes the display-name identifier from the authorization set entirely, does not have this problem. Tested against the Analysis conducted with AI assistance (Claude, Anthropic); the measurement above is a real run and reproducible from the steps given. |
What does this PR do?
I noticed the SimpleX allowlist could be bypassed by anyone who can message
the bot. A recent change taught
SIMPLEX_ALLOWED_USERSto accept displaynames (490c486), but the name it matched on —
source.user_name— is thecontact's self-asserted profile display name. That field is fully
controlled by the remote contact and isn't unique, so if an operator listed
a name in the allowlist (which the docs actively encourage, since the SimpleX
UI never shows the numeric contactId), any other contact could just rename
their own profile to that string and get full agent access. This was turning
a stable-ID check into a guess-the-name check where the attacker controls
both sides.
The fix keeps the name convenience but anchors it to something the remote
party can't forge: the contact's local display name — the alias the
SimpleX CLI assigns and deduplicates locally (
alice,alice_1, ...). Theadapter now carries that alias in
user_id_alt(the field already used forstable platform alt-IDs like the Signal UUID and Feishu union_id), and the
gateway matches the allowlist against that instead of the profile name. A
later contact who renames their profile to a taken alias gets
alice_1locally, so they can't collide with a trusted contact. The numeric contactId
via
user_idstill works and stays the most robust option.Related Issue
N/A
Type of Change
Changes Made
gateway/run.py: in_is_user_authorized, matchSIMPLEX_ALLOWED_USERSagainst
source.user_id_alt(the stable local alias) instead ofsource.user_name(the attacker-controlled profile display name).plugins/platforms/simplex/adapter.py: capturelocalDisplayNamefor bothDM contacts and group members and pass it through
build_sourceasuser_id_alt; update the module docstring and the interactive-setup prompt.tests/gateway/test_unauthorized_dm_behavior.py: rework the display-nametest to authorize via the local alias and add a regression test proving a
spoofed profile name is rejected.
website/docs/user-guide/messaging/simplex.md: clarify that the allowlistmatches the local alias / contactId, not the spoofable profile name.
How to Test
scripts/run_tests.sh tests/gateway/test_unauthorized_dm_behavior.py—34 pass, including the new
test_simplex_allowlist_rejects_spoofed_profile_name.scripts/run_tests.sh tests/gateway/test_simplex_plugin.py— 28 pass.SIMPLEX_ALLOWED_USERS=alice, message the botfrom an untrusted contact whose profile name is "alice" but whose local
alias is "alice_1" — old code authorized them, new code denies them.
Checklist
Code
fix(scope):, etc.)Documentation & Housekeeping
cli-config.yaml.exampleif I added/changed config keys — N/ACONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/A