unifi-security-tools
Skill294 starsChanged 18 days agoApache-2.0View on GitHub
Use when adding, changing or testing a UniFi security MCP tool (legacy firewall rules, zone-based firewall policies and zones, firewall groups, ACL rules, traffic matching lists, content filtering, port forwarding, RADIUS and guest portal) - covers the three URL spaces, zone and network ID mapping, full-object policy updates, risk tiers, test fixtures and known gaps.
We split this skill into the rules it gives an agent
6 rules. About 0 are common, 0 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 28
Every public async def is a tool (register_module_tools() in src/tool_registry.py).
- Very specific: line 29
All these modules are in _LOCAL_TOOL_MODULES (src/main.py).
- Very specific: line 31
Run-time checks: firewall_policies, firewall_groups and content_filtering refuse non-local modes with _ensure_local_api() …
- Very specific: line 32
No code checks for Network 9.0+.
- Very specific: line 58
Read models are permissive on purpose: string fields instead of enums, and extra="allow".
- Very specific: line 76
When a new tool can cut off traffic, decide with the owner whether it belongs in DESTRUCTIVE_TOOLS.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
UniFi Security Tools
These tools change who can reach what on the network, so a mistake can lock users or the operator out. Read this before you change one. For the general add-a-tool workflow, see unifi-mcp-tool-builder. For zone-based firewall (ZBF) background, see zbf-implementation-guide.md in this folder.
Module map
| Module | Tools | API | Examples |
src/tools/firewall.py | 4 | legacy | list_firewall_rules, create_firewall_rule |
src/tools/firewall_policies.py | 7 | v2 and integration | list_firewall_policies, create_firewall_policy, get_zone_policy_matrix |
src/tools/firewall_zones.py | 7 | integration | assign_network_to_zone, unassign_network_from_zone |
src/tools/firewall_groups.py | 7 | legacy | create_port_group, create_address_group, delete_firewall_group |
src/tools/acls.py | 5 | integration | list_acl_rules, create_acl_rule |
src/tools/traffic_matching_lists.py | 5 | integration | list_traffic_matching_lists, create_traffic_matching_list |
src/tools/content_filtering.py | 4 | v2 | list_content_filters, update_content_filter |
src/tools/port_forwarding.py | 4 | legacy | list_port_forwards, create_port_forward |
src/tools/radius.py | 17 | legacy | list_radius_profiles, configure_guest_portal |
src/tools/zbf_matrix.py holds 7 stubs that raise NotImplementedError. src/main.py does not import it, so none of them is registered. The UniFi API has no zone-matrix endpoint; API.md lists those tools as removed.
Registration and modes
- Every public
async defis a tool (register_module_tools()insrc/tool_registry.py). Name helpers with a leading_. - All these modules are in
_LOCAL_TOOL_MODULES(src/main.py). - The
securityprofile coversacls,content_filtering,firewall,firewall_groups,firewall_policies,firewall_zonesandport_forwarding, and alsosite_vpnandvpn.radiusandtraffic_matching_listsare in thesystemprofile. - Run-time checks:
firewall_policies,firewall_groupsandcontent_filteringrefuse non-local modes with_ensure_local_api()(NotImplementedError). Thefirewall_zonesversion of that helper raisesValidationError. - No code checks for Network 9.0+. If the v2 zone list is empty,
list_firewall_zones_v2raisesAPIErrorwith the message "ZBF does not appear to be configured".
Legacy rules or zone-based policies
- Legacy rules (
rest/firewallrule) are read and written bysrc/tools/firewall.py. In local mode,list_firewall_rulesfalls back to the v2firewall-policieslist when the legacy list is empty. - Zone-based policies are read and written only by the
*_firewall_policytools. Nothing picks the right family for the caller, so a new tool must state in its docstring which family it changes.
URL spaces
| Space | Path helper | Used by |
| Legacy | write /ea/sites/{site}/rest/...; UniFiClient rewrites it for local mode | firewall rules, groups, port forwards, RADIUS, guest portal |
| v2 | settings.get_v2_api_path(site_id) (local only) | firewall policies, the v2 zone list, content filtering |
| Integration | settings.get_integration_path(f"sites/{site_uuid}/...") | zones, ACL rules, traffic matching lists, get_zone_policy_matrix |
The integration API needs a site UUID; get it with await client.resolve_site_id(site_id). The integration API is served both on the local console (/proxy/network/integration/v1/) and through the cloud connector; the spec snapshot scripts/scraped-api-spec-v10.6.106.json lists both servers.
IDs
- There are two zone ID spaces. v2 uses MongoDB ObjectIds; the integration API uses UUIDs.
_resolve_zone_id()insrc/tools/firewall_policies.pyaccepts a name, a UUID or an ObjectId, and caches the zone index per site in the module-level_zone_cache. - Network IDs are mapped from ObjectId to UUID by
_resolve_network_uuid()insrc/tools/firewall_zones.py. get_zone_policy_matrixreturns v2 and integration zone IDs, which cannot be joined directly.
Models
- Models:
src/models/firewall_policy.py,src/models/firewall_group.py,src/models/firewall_zone.py,src/models/acl.py,src/models/traffic_matching_list.py,src/models/radius.py. - Read models are permissive on purpose: string fields instead of enums, and
extra="allow". New firmware adds values, and a strict model would break reads. ACLRulerequiresactionand mapsidfrom_id(populate_by_name=True).
Write tools
- Signature:
confirm: bool | str = False, dry_run: bool | str = False. Callvalidate_confirmation(confirm, "<operation>", dry_run)first, and return the dry run before you open a client. - The registry wrapper audits every mutating call, and with
UNIFI_AUDIT_FAIL_CLOSEDon (the default) it refuses the call if the audit record cannot be written. Some modules also calllog_audit()oraudit_action()themselves; do not add a third path. - Full-object policy updates. The v2 policy PUT needs the whole object.
update_firewall_policyGETs the policy, merges the changes, removes_idandpredefined, then PUTs.create_firewall_policyalways sends a schedule with modeALWAYS. - Lockout protection is thin. The only guard is that predefined policies cannot be updated or deleted. Nothing stops a rule that blocks management access. When you add a tool that can block traffic, make the dry run show exactly what will be blocked.
- Zones. The API rejects a zone
description(api.request.unknown-property), so the tool drops it. Every zone PUT must includenetworkIdsandname. - In
update_firewall_policy,portandport_group_idcannot both be set. - Content filters cannot be created; the v2 endpoint returns 405 on POST.
Risk tiers
tool_tier()(src/tool_registry.py) makes a tool destructive when its name starts withdelete_,remove_orbulk_delete_, or when it is inDESTRUCTIVE_TOOLS.- Tools that change firewall policy are in
DESTRUCTIVE_TOOLS, so only the admin role can call them:create_firewall_policy,update_firewall_policy,update_zbf_policy,create_firewall_rule,update_firewall_rule,assign_network_to_zoneandunassign_network_from_zone(test_firewall_policy_changes_need_admin). - Everything else that mutates is
writetier, which the operator role can call. That includes ACL rules, port forwards, firewall groups and zones, traffic matching lists and the content filter. - When a new tool can cut off traffic, decide with the owner whether it belongs in
DESTRUCTIVE_TOOLS. - A tool with a risky word in its name (
block,disableand others) must be inDESTRUCTIVE_TOOLSor inRISKY_NAME_EXEMPTIONS(tests/unit/fleet/test_tiers.py). - Regenerate the snapshot after any tool change:
UPDATE_TOOL_TIERS=1 pytest tests/unit/fleet/test_tiers.py.
Tests
- Files:
tests/unit/tools/test_<module>_tools.py, plustests/unit/tools/test_firewall_policies.pyandtests/unit/tools/test_firewall_policy_helpers.py. - Patch the client in the module:
patch("src.tools.firewall_policies.UniFiClient"). Set__aenter__to return the client and__aexit__to returnFalse. A bareAsyncMock()for__aexit__returns a truthy value, theasync withblock then swallows every exception, and the tool returnsNone. - Build real settings with
monkeypatch.setenv("UNIFI_API_TYPE", "local")andSettings()when the path matters. Mocked cloud-ea settings hid the local-mode ACL bug from April to October 2026: the tests ran in cloud-ea mode, but the tools were registered only in local mode. - Tests that use
_zone_cacheseed it by hand and clear it afterwards.
Known defects and gaps (not fixed)
- The confirmation checks are not the same everywhere.
firewall_policies,firewall_groupsandcontent_filteringraiseValueError, while modules that usevalidate_confirmation()raiseValidationError. - Dry-run and delete replies have different shapes in different modules (
{"dry_run": True, ...}or{"status": "dry_run", ...}). update_firewall_policywrites no audit entry of its own for dry runs or failures; the wrapper still audits the call.delete_firewall_zoneauthenticates before it checksdry_run, so a dry run still makes one request.- The ACL and traffic-matching-list local-mode fix (commit
aaf3dd8) is verified against mocks and the spec, not on hardware.
In this repository52 other files it gives its agents.
- AGENTS.mdAGENTS.md
- .agents/skills/fastmcp/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- .claude/skills/fastmcp/CLAUDE.mdCLAUDE.md
- .cursor/rules/common-mistakes.mdcCursor rule
- .cursor/rules/core-principles.mdcCursor rule
- .cursor/rules/environment-setup.mdcCursor rule
- .cursor/rules/mcp-tools.mdcCursor rule
- .cursor/rules/project-context.mdcCursor rule
- .cursor/rules/README.mdCursor rule
- .cursor/rules/unifi-api.mdcCursor rule
- .cursor/rules/workflow.mdcCursor rule
- FastMCP DevelopmentSkill
- mcp-builderSkill
- source-command-build-dockerSkill
- source-command-check-depsSkill
- source-command-cleanSkill
- source-command-docsSkill
- source-command-formatSkill
- source-command-lintSkill
- source-command-prSkill
- source-command-securitySkill
- source-command-setupSkill
- source-command-testSkill
- source-command-unifi-api-docs-update-2Skill
- source-command-unifi-api-docs-updateSkill
- source-command-unifi-mcp-add-toolSkill
- source-command-unifi-mcp-release-prepSkill
- source-command-unifi-mcp-test-coverageSkill
- source-command-unifi-mcp-update-docsSkill
- unifi-cloud-fleet-toolsSkill
- unifi-mcp-tool-builderSkill
- unifi-network-config-toolsSkill
- unifi-protect-toolsSkill
- FastMCP DevelopmentSkill
- mcp-builderSkill
- unifi-cloud-fleet-toolsSkill
- unifi-mcp-tool-builderSkill
- unifi-network-config-toolsSkill
- unifi-protect-toolsSkill
- unifi-security-toolsSkill
- unifiSkill
- unifi-channel-planningSkill
- unifi-cloud-fleet-toolsSkill
- unifi-devicesSkill
- unifi-mcp-tool-builderSkill
- unifi-network-config-toolsSkill
- unifi-networkSkill
- unifi-protect-toolsSkill
- unifi-securitySkill
- unifi-security-toolsSkill
- unifi-systemSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

