Summary
The AMI action dispatcher authorizes on authentication only. It never consults the authenticated user's write_perm before executing dangerous actions. AmiUser::can_write / AmiSession::write_perm exist but are dead outside tests — every authenticated session can run every action.
Location
crates/asterisk-ami/src/actions.rs:66-96 — ActionRegistry::dispatch
- The only gate is
crates/asterisk-ami/src/actions.rs:75:
if name_lower != "login" && name_lower != "challenge" && !session.authenticated {
return AmiResponse::error("Permission denied") ...
}
write_perm is set on login (session.rs:87-88) then read nowhere. Proof:
$ grep -rn 'can_write\|write_perm' crates/asterisk-ami/src
# every hit is a definition, a field assignment, or a test — no call site in dispatch/handlers
Attack
A user authenticated with any credentials (including one an operator intended as read-only) can invoke:
Originate (actions.rs:489) — place arbitrary calls / run dialplan apps.
Command (actions.rs:1005, execute_cli_command) — CLI execution surface.
Redirect / Hangup / Bridge / Park / SetVar / UpdateConfig / QueueAdd/Remove/Pause / Atxfer / ConfBridgeKick/Mute / PJSIPNotify — call control, dialplan-variable writes, config writes.
There is no per-action authority check, so a low-privilege AMI account is equivalent to a full-privilege one.
Impact
Privilege escalation / missing authorization on the manager interface. Combined with # (all users are created with write_perm = ALL anyway), every AMI user today is effectively omnipotent. This is the AMI half of the PBX trust boundary.
Recommended fix (auth-model change — needs a human/coordinator call, do NOT silently auto-merge)
- Give each dangerous action a required write
EventCategory (mirroring Asterisk's per-action authority): Command→COMMAND, UpdateConfig→CONFIG, Originate/Redirect/Hangup/Bridge/Park/SetVar/Atxfer/Queue→CALL (or SYSTEM for Originate, per Asterisk's system/call split), ConfBridge→CALL, PJSIPNotify→SYSTEM.
- In
dispatch, after the auth gate, look up the required category and reject with Permission denied if !session.write_perm.contains(required).
- Read-only status actions (Ping, Core*Status, CoreShowChannels, List, Show) stay ungated for any authenticated session, matching Asterisk.
Risk note: this changes the AMI authorization model. It is backwards-compatible for existing deployments only if the default remains write_perm = ALL (today every configured user is ALL — see the manager.conf-perms issue), so no current user loses access; it only lets operators create genuinely restricted users. Ship together with honoring read=/write= in manager.conf. A ready patch + red-able tests (a write_perm = CALL user is denied Command; an ALL user is allowed) can be provided.
Filed by the security/authz hardening pass. Not auto-merged because it alters the auth model.
Summary
The AMI action dispatcher authorizes on authentication only. It never consults the authenticated user's
write_permbefore executing dangerous actions.AmiUser::can_write/AmiSession::write_permexist but are dead outside tests — every authenticated session can run every action.Location
crates/asterisk-ami/src/actions.rs:66-96—ActionRegistry::dispatchcrates/asterisk-ami/src/actions.rs:75:write_permis set on login (session.rs:87-88) then read nowhere. Proof:Attack
A user authenticated with any credentials (including one an operator intended as read-only) can invoke:
Originate(actions.rs:489) — place arbitrary calls / run dialplan apps.Command(actions.rs:1005,execute_cli_command) — CLI execution surface.Redirect/Hangup/Bridge/Park/SetVar/UpdateConfig/QueueAdd/Remove/Pause/Atxfer/ConfBridgeKick/Mute/PJSIPNotify— call control, dialplan-variable writes, config writes.There is no per-action authority check, so a low-privilege AMI account is equivalent to a full-privilege one.
Impact
Privilege escalation / missing authorization on the manager interface. Combined with # (all users are created with
write_perm = ALLanyway), every AMI user today is effectively omnipotent. This is the AMI half of the PBX trust boundary.Recommended fix (auth-model change — needs a human/coordinator call, do NOT silently auto-merge)
EventCategory(mirroring Asterisk's per-actionauthority): Command→COMMAND, UpdateConfig→CONFIG, Originate/Redirect/Hangup/Bridge/Park/SetVar/Atxfer/Queue→CALL(orSYSTEMfor Originate, per Asterisk'ssystem/callsplit), ConfBridge→CALL, PJSIPNotify→SYSTEM.dispatch, after the auth gate, look up the required category and reject withPermission deniedif!session.write_perm.contains(required).Risk note: this changes the AMI authorization model. It is backwards-compatible for existing deployments only if the default remains
write_perm = ALL(today every configured user is ALL — see the manager.conf-perms issue), so no current user loses access; it only lets operators create genuinely restricted users. Ship together with honoringread=/write=inmanager.conf. A ready patch + red-able tests (awrite_perm = CALLuser is deniedCommand; anALLuser is allowed) can be provided.Filed by the security/authz hardening pass. Not auto-merged because it alters the auth model.