Security
Scopes and authorization
Every tool carries one scope; registration-time policy decides which tools exist and per-request checks decide which an authenticated identity may call.
Every tool in Secronyx is registered with exactly one scope label. Authorization happens at two points: when the server starts, a scope policy decides which tools are registered at all; on the HTTP transport, every tools/call is then checked against the scopes carried by the authenticated identity. This page lists the scopes, explains both stages, and shows what a denial looks like.
The scope list
There are nineteen scopes. The counts below come from starting the current build with --scopes <name> --enable-sensitive and reading tools/list; the authoritative per-tool listing is the Tool reference.
| Scope | Tools | What it unlocks |
|---|---|---|
core |
8 | get_cpu_info, get_memory_info, get_disk_info, get_network_info, get_processes, get_processes_sampled, get_uptime, get_temperature |
logs |
5 | get_journal_logs, get_syslog, get_kernel_logs, get_app_logs, get_event_log |
hooks |
16 | scheduled tasks, cron, startup items, systemd services, kernel modules and drivers, DNS servers, routes, firewall rules, listening ports, ARP table, network stats, mounts, disk I/O, open files, inode usage |
sensitive |
9 | get_auth_logs, get_env_vars, get_user_accounts, get_sudo_config, get_ssh_config, get_mac_status, get_certificates, get_process_environ, get_macos_tcc_permissions |
hardware |
25 | hardware summary, USB, PCI, block devices, battery, fans, firmware, CPU topology and vulnerabilities, hwmon sensors, IOMMU, EDAC and similar |
resources |
11 | IPC resources, namespaces, cgroups, capabilities, process tree, zombies, blocked and orphan processes, file descriptor and thread summaries, priorities |
state |
38 | VM detection, timezone, NTP, core dumps, power state, NUMA, login sessions and history, password policy, kernel parameters, fstab, locale, systemd boot analysis and unit inventory, macOS launchd and system settings |
software |
115 | package inventories for the OS and every language ecosystem, lock-file parsers, SBOM generation, vulnerability lookups, application detection, get_app_config, Docker containers and images, database and web servers, language runtimes and developer tooling |
security |
40 | platform security controls: Defender, firewall profiles, BitLocker, AppLocker and WDAC, Credential Guard, RDP, WinRM and applied GPO, FileVault, Gatekeeper, SIP, XProtect, auditd, kernel lockdown, MAC detail, package repositories, auto-updates |
triage |
25 | incident summaries: recent reboots, service failures, kernel and critical events, config changes, auth failure summary, exposed services, resource limits, posture snapshots |
windows |
125 | registry, IIS, DCOM and COM security, Active Directory, VSS and shadow copies, WMI health, event log inventory, SMB sessions, system identity and boot, and the other Windows-only collectors |
storage |
23 | SMART health, I/O latency, volume status, mount and filesystem events, storage deep-dive |
network |
25 | connection tracking, DNS statistics, deep firewall analysis, Wi-Fi metrics, get_network_latency, extended network collectors |
analytics |
4 | historical metrics, anomaly detection, capacity forecast, trend analysis |
alerts |
3 | alert status, remediation suggestions, runbook recommendations |
compliance |
5 | security scan, compliance check, hardening recommendations, forensic snapshot, audit trail |
consumer |
24 | Bluetooth, audio, printers, displays and other workstation diagnostics |
enhanced |
27 | GPU, container runtimes (Docker, Podman, Compose, Kubernetes node), kernel and memory internals (slab, vmstat, hugepages, pressure stall, OOM events, interrupts), swap and zram, WSL |
report |
2 | generate_system_report and generate_iis_report |
In total 530 tools are registered with --enable-sensitive, and 521 without it. An earlier document described a scope named sbom; the code registers those tools under software.
Stage one: registration policy
ScopePolicy in internal/mcp/authz.go is installed before any tool is registered:
type ScopePolicy struct {
// AllowedScopes, when non-empty, is the only set of scopes whose tools
// are registered. Empty means every scope except "sensitive".
AllowedScopes []string
// EnableSensitive permits registration of tools in the "sensitive"
// scope. It is required even when AllowedScopes lists "sensitive".
EnableSensitive bool
}The command line maps onto it as follows:
| Flag | Environment variable | Effect |
|---|---|---|
--scopes core,logs |
SECRONYX_SCOPES |
Register only tools in the listed scopes. The flag wins when both are set. |
--enable-sensitive |
SECRONYX_ENABLE_SENSITIVE=1 |
Permit registration of sensitive tools. Required even when --scopes names sensitive. |
A tool the policy refuses is simply never registered. It is absent from tools/list on every transport, including stdio, and calling it returns the ordinary tool-not-found error, so a client cannot tell the difference between a tool that does not exist and one that was withheld:
{"jsonrpc":"2.0","id":1,"error":{"code":-32602,"message":"Tool not found","data":"get_env_vars"}}At start-up the server logs how many tools the policy withheld, and a separate warning when sensitive tools are on:
Scope policy: 9 tools not registered (sensitive=false, scopes="")
WARNING: sensitive-scope tools are enabled
Two consequences worth knowing. First, a scope name that matches nothing registers nothing: --scopes bogus starts a server with zero tools and logs Scope policy: 530 tools not registered. Second, --scopes core,sensitive without --enable-sensitive registers the eight core tools only.
# Core metrics only
secronyx --scopes core
# Core, logs and hooks
secronyx --scopes core,logs,hooks
# Everything, including sensitive tools (both are required)
secronyx --scopes core,logs,hooks,sensitive --enable-sensitive
# The same through the environment, for a service unit
SECRONYX_SCOPES=core,logs SECRONYX_ENABLE_SENSITIVE=1 secronyxOn stdio the registration policy is the only control. Nothing in the stdio protocol carries a caller identity, so the process that spawns the server is trusted to the extent of whatever was registered. The Command line page covers the --query mode, which does not go through the registry at all.
Stage two: per-request checks
When the HTTP transport authenticates a request it places an Identity in the request context:
type Identity struct {
Subject string
ClientID string
Scopes []string
// Method names the authenticator that established the identity.
Method string
}tools/call then runs IdentityAllows(identity.Scopes, tool.Scope) before the handler executes. A grant matches when it is:
- the wildcard
*; - the tool's scope name itself, for example
logs; or - the namespaced form
mcp:tools:<scope>, for examplemcp:tools:logs, so an authorization server whose scope namespace is shared with other resources can issue distinct values.
The bare audience value mcp:tools grants nothing on its own. An identity with an empty scope list can authenticate but can call nothing. tools/list is filtered with the same function, so a caller sees only what it may invoke; the .well-known/oauth-protected-resource document advertises scopes_supported as the scopes that map to registered tools.
A request with no identity in its context (stdio, or HTTP with no authentication configured, which the listener guardrails permit only on loopback or with --allow-unauthenticated) is governed by the registration policy alone.
Where identity scopes come from
Each authentication method produces the scope list differently. Details of configuring each are in Authentication.
| Method | Source of scopes |
|---|---|
Static bearer token (--token) |
Always ["*"]. The static token is full access to whatever is registered. |
OIDC (--oidc-issuer) |
The JWT's scope claim (space-separated string), else scp (array, as Azure AD issues), else scopes (array). |
| SaaS JWKS validator (hybrid mode) | The same three claims, read by internal/agent/jwks.go. |
OAuth introspection (--auth-server) |
The introspection response's mcp_scopes array; if absent or empty, the space-separated scope string. |
Built-in token server (secronyx-token-server) |
Each client record's allowed_scopes becomes the token's mcp_scopes claim and is returned from /introspect as both scope and mcp_scopes. |
API key (--api-keys-file) |
The key record's scopes, set by secronyx apikey create --scopes ... (default core). The CLI refuses to issue *. |
Mutual TLS (--mtls-identity-file) |
The matching identity entry's scopes; if nothing matches, --mtls-default-scopes, else the certificate is denied. * is rejected when the identity file is loaded. |
SSH request signing (--ssh-authorized-keys) |
The scopes="a,b" option on the key's line. For certificates, the CA line's scopes=; if the CA line has none, the certificate extension secronyx-scopes. Unknown options are rejected so a typo cannot widen access. |
Whatever the method, sensitive in a grant only takes effect if the server was started with --enable-sensitive; a grant cannot register a tool.
What a denial looks like
A call outside the identity's grant is refused with JSON-RPC error -32003, which sits in the implementation-defined server error range. The HTTP status is 200 because the request was authenticated and well-formed; the refusal is at the JSON-RPC layer.
{"jsonrpc":"2.0","id":2,"error":{"code":-32003,"message":"Forbidden","data":"tool \"get_journal_logs\" requires scope \"logs\""}}The denial is audited as a tools/call event with result denied and error scope denied, and counted in metrics under the error type scope_denied. Abridged — the written record also carries timestamp, seq, event_id and the hash-chain fields:
{"action":"tools/call","resource":"get_journal_logs","identity":"demo","client_ip":"127.0.0.1","params":{"lines":5},"result":"denied","error":"scope denied"}Worked example with an API key
Issue a key scoped to core, start a loopback listener, and exercise both stages. Loopback is the one configuration that may run without TLS; see Remote access over HTTP for anything else.
secronyx apikey create --file ./keys.json --name demo --scopes core
# Created API key 78ef7122 for "demo" (scopes: core)
# This key is shown once and cannot be recovered:
# msk_78ef7122_...
secronyx --transport http --listen 127.0.0.1:18080 \
--api-keys-file ./keys.json \
--audit-output ./audit.jsonlThe key sees eight tools, may call one of them, and is refused on another:
KEY=msk_78ef7122_...
curl -s -X POST http://127.0.0.1:18080/ -H "X-API-Key: $KEY" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools | length'
# 8
curl -s -X POST http://127.0.0.1:18080/ -H "X-API-Key: $KEY" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_journal_logs","arguments":{"lines":5}}}'
# {"jsonrpc":"2.0","id":2,"error":{"code":-32003,"message":"Forbidden","data":"tool \"get_journal_logs\" requires scope \"logs\""}}
curl -s -X POST http://127.0.0.1:18080/ -H "X-API-Key: $KEY" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"get_uptime"}}'
# {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"{\n \"boot_time\": ..."}]}}The audit file records the authentication (with the granted scopes in metadata), the denial and the success in one chain. The full walk-through, including expiry and CIDR restrictions, is in Tutorial: per-operator API keys.
Choosing a policy
- Start with
--scopes core,logs,hooksfor general diagnostics and widen per host as needed. Everything else can be added without restarting clients, but the server must restart: the policy is evaluated at registration and tools registered earlier are not re-evaluated. - Keep
sensitiveoff unless the host is isolated and the identity that will call those tools is individually accountable.get_env_varsandget_process_environredact secret-looking values before returning them, but the scope as a whole —get_sudo_config,get_ssh_config,get_user_accounts,get_auth_logs— still describes how the host authenticates and authorises. - On HTTP, issue narrow grants per identity rather than widening the registration policy. A
core-only API key on a server registered with every scope still sees eight tools. - Prefer
mcp:tools:<scope>grants from an enterprise IdP whose scope namespace is shared, and plain names from the API key, mTLS and SSH files, which are Secronyx-specific.
For the wire format of the error see JSON-RPC API; for what the audit trail records about denials see Audit logging.
Built 2026-09-19. Source: levantar-ai/secronyx. Found a mistake? Tell us.