Conversation
* [core] Add interface allowlist to gate module app/api registration Adds an optional, presence-activated allowlist in switch.conf.xml that controls which modules may register application / api / json_api / chat-application interfaces. With no <interface-allowlist> configured nothing is enforced; when at least one <allow> entry is present, only listed interfaces register at load time and all others are refused (the module still loads and switch_loadable_module_process still returns SUCCESS -- the blocked interface is simply never exposed). Entries match at three levels of precision: mod_commands - whole module mod_commands.system - any interface named "system" mod_commands.system.api - a specific type (app|api|json_api|chat_app) Enforcement lives in switch_loadable_module_process() so every module, at boot and at runtime `load`, is subject to the same policy. This gives operators a way to disable the "system"/"spawn" shell-exec API commands (and equivalents) system-wide. Also adds the `interface_allowlist_dump [modules] [plain]` API, which walks the loaded modules and prints their interfaces in the allowlist key format so the current state can be captured and pruned offline into config. * [mod_commands] Add tests for the interface allowlist New test_interface_allowlist boots the core with an active <interface-allowlist> (conf_interface_allowlist/) that permits only a couple of mod_commands interfaces, then loads mod_commands and verifies: - listed commands register and run (status, version) while unlisted and shell-exec commands are refused (system, spawn, uptime) -- refusal surfaces as switch_api_execute returning FALSE / command-not-found, with the command function never invoked; - a "module.name.type" entry gates by type: the API "status" loads while the JSON API of the same name stays blocked; - interface_allowlist_dump prints the config format in its xml, modules and plain variants, and reflects module capabilities (system appears in the dump even though it was blocked from registering). * [config] Fix interior -- in interface-allowlist comment breaking XML parse The explanatory comment used -- as em-dash pairs. The XML parser treats -- inside a comment as the comment close, causing an "unclosed <!--" error that prevents the whole freeswitch.xml from parsing (boot and reloadxml both fail). Replace the -- pairs with ordinary punctuation. * update .gitignore * [core] Warn when interface-allowlist section is present but parses no entries
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.
Add an interface allowlist to control which module apps/APIs load
Motivation
FreeSWITCH's system, bg_system, spawn, and bgsystem commands execute arbitrary shell/binaries, and they're reachable from every API dispatch surface — the event socket,
XML-RPC, the fsapi/execute JSON gateways, scripting languages, and the dialplan. On a hardened or multi-tenant deployment that's a large, awkward-to-close attack surface,
and disabling it piecemeal (per module, per transport) is error-prone.
This adds a single, global mechanism to declare exactly which module interfaces a FreeSWITCH instance is allowed to expose. Anything not on the list is never registered,
so it cannot be dispatched from any surface.
What it does
A new optional section in switch.conf.xml gates the registration of application, api, json_api, and chat_application interfaces for every module,
enforced centrally in switch_loadable_module_process() (so it applies at boot and to runtime load).
as one entry exists, enforcement is on and anything not listed is refused.
switch_loadable_module_process() still returns success — the interface just isn't exposed. This is more airtight than a per-call deny (the function pointer never enters
the hash) and has no partial-init side effects, since the check runs after the module's load function has already returned.
This lets you, for example, permit the dialplan system app while still blocking the system API.
Companion API for building the config
interface_allowlist_dump [modules] [plain] (mod_commands) walks the loaded modules and prints their interfaces in the allowlist key format, so you can capture the current
state and prune it offline:
The dump reflects module capabilities (everything a module registered), independent of what enforcement blocked — that's what makes it a useful starting point. Intended
workflow: boot with no allowlist → run the dump → delete the entries you want blocked → paste into switch.conf.xml → restart.
Example
Lock the box down to a known set, minus the shell-exec commands:
...The vanilla switch.conf.xml ships the section commented out with documentation and a WARNING that enabling it is aggressive and system-wide (you must list every
module/interface you rely on, including management commands and dialplan apps).
Scope / limitations
This gates interface registration only, so it covers the system/spawn APIs, the system/bgsystem dialplan apps, and any other module command. It intentionally does not
touch:
system() spool-move in switch_core_file.c. Those need separate mitigation (e.g. gating the switch_system/switch_spawn core primitives).
Testing
New test_interface_allowlist FST test (in mod_commands/test, with its own conf_interface_allowlist/) boots the core with an active allowlist, loads mod_commands, and
verifies:
returning FALSE with the command function never invoked;