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 |
9 | get_journal_logs, get_syslog, get_kernel_logs, get_app_logs, get_event_log, get_powershell_script_blocks, get_apparmor_events, get_selinux_events, get_seccomp_events |
hooks |
20 | 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, socket, file and hardware event history, event monitor status, NTFS USN journal |
sensitive |
31 | 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, plus Windows execution evidence (prefetch, BAM, ShimCache, UserAssist, ShellBags, Office MRU), logon sessions, process events, SSH keys, known hosts and client config, account password status, shell history, kernel keys, DNS cache, process environment names, container environments, print jobs, macOS account policy and QuickLook cache |
hardware |
38 | hardware summary, USB, PCI, block devices, battery, fans, firmware, CPU topology and vulnerabilities, hwmon sensors, IOMMU, EDAC, SMBIOS tables, chassis, OEM strings, memory layout, ACPI tables, CPU features, device firmware, Intel ME, security MSRs, power sensors, macOS IOKit, T2 and displays |
resources |
13 | IPC resources, namespaces, cgroups, capabilities, process tree, zombies, blocked and orphan processes, file descriptor and thread summaries, priorities, process pipes, physical memory map |
state |
57 | 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, crash and kernel panic reports, macOS preferences and running apps, CUPS destinations, container file changes, processes, networks and image metadata, LXD/Incus, Azure, EC2 and Yandex instance metadata |
software |
124 | 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, Chromium, Firefox, Safari and VS Code-family extensions, macOS package receipts and files, Portage packages and config, RPM package files |
security |
71 | 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; Windows persistence (WMI subscriptions, autoruns, AppCompat shims), security products, Security Center health, EDR sensors, named pipes, speculation control, binary ACLs and Authenticode; file metadata, process image and launch binary integrity, trusted roots; macOS application firewall, authorization rights, Input Monitoring, Gatekeeper rules, launchd overrides, sandboxes, NVRAM, XProtect signatures and events, managed policies, shared folders, AD binding and Location Services; screen-lock policy; container and Docker daemon security; process memory maps |
triage |
25 | incident summaries: recent reboots, service failures, kernel and critical events, config changes, auth failure summary, exposed services, resource limits, posture snapshots |
windows |
127 | registry, IIS, DCOM and COM security, Active Directory, VSS and shadow copies, WMI health, event log inventory, SMB sessions, system identity and boot, the full service inventory, named kernel objects, and the other Windows-only collectors |
storage |
25 | SMART health, I/O latency, volume status, mount and filesystem events, storage deep-dive, device partitions, Time Machine backups |
network |
29 | connection tracking, DNS statistics, deep firewall analysis, Wi-Fi metrics, get_network_latency, extended network collectors, /etc/protocols and /etc/services, OS connectivity verdict, Wi-Fi scan cache |
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 |
27 | Bluetooth, audio, printers, displays and other workstation diagnostics, Internet Explorer extensions, legacy browser plug-ins, URL scheme handlers |
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 661 tools are registered with --enable-sensitive, and 630 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: 31 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: 661 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-28. Found a mistake? Tell us.