Skip to content

Security: kotyzap/EdgeMCP-for-Axis

Security

SECURITY.md

Security model — EdgeMCP for Axis

The most common question about an on-camera MCP server is: "aren't you opening my camera to hackers?" This document explains exactly what the server can and cannot do, and which guardrails are enforced in code.

What the MCP server can never do

  • Create or modify users. There is no user-management tool. The set_param write allowlist (Image, ImageSource, Brand, Time, AudioSource, Event, Overlay) excludes Users, Security, Network, System and everything else — an AI cannot change credentials, open ports, or alter network configuration, even indirectly.
  • Reach the internet. The server only talks to VAPIX on the camera's own loopback interface. Nothing leaves the LAN unless the operator deliberately tunnels it.
  • Bypass camera authentication. The sanctioned endpoint (/local/axis_mcp/mcp) sits behind the camera's reverse proxy, which enforces admin digest auth — the camera itself, not this app, is the gatekeeper.

Guardrails enforced in code

Access levels (operator-controlled, default: Operate)

A three-position switch on the settings page decides what connected agents may do. Every tool call is checked centrally (src/guardrails.ts) before the tool runs; blocked calls return a clear "disabled by operator" error and are logged.

Level Adds
Read-only Status, device info, params (read), snapshots, stream URLs, lists
Operate (default) PTZ, focus, image settings, overlays, streams, I/O, recordings
Full reboot_camera, factory_default, set_param, control_app

Unknown/future tools default to Full (fail-closed).

Rate limits and caps

  • 60 tool calls per minute overall.
  • 20 state-changing calls per minute.
  • PTZ preset cap: ptz_preset_save refuses once the camera has 20 presets — an agent loop cannot create "400 PTZ presets".

Destructive-action confirmation

reboot_camera and factory_default additionally require an explicit confirm: true argument (and Full access level).

Direct port hardening

  • The direct LAN port is disabled by default.
  • Enabling it without a bearer token auto-generates one (shown once); the "open port, no auth" configuration can no longer be created from the UI.
  • Legacy installs that had the port open without a token keep working but log a prominent SECURITY: warning on every start.

Transparency

Every request, every MCP tool call, and every guardrail refusal appears in the Live Log on the settings page and is mirrored to the AXIS system log — the operator can always see exactly what an agent did.

Credentials at rest

The VAPIX account this app uses is stored as plain JSON in the package's own writable data directory (PERSISTENT_DATA_PATH, i.e. /usr/local/packages/edgemcp/localdata/settings.json), as is the direct port's bearer token. They are not encrypted.

The exposure is bounded by the fact that reading that file already requires administrative access to the camera — SSH or an equivalent — and anyone who has that can obtain the same access directly. It is called out here because it is a real property of the design rather than an oversight: an ACAP has no device keystore to hold a secret an unattended service must be able to read back. Treat the account as scoped to this camera, and prefer a dedicated service account over a shared administrator login.

The settings endpoint never returns either secret — settings.cgi reports only hasPassword and hasBearerToken booleans.

Network exposure of the direct port

The optional direct MCP listener speaks plain HTTP, so its bearer token crosses the LAN in the clear on every call. It is off by default, and enabling it without a token generates one automatically. It is additionally protected against DNS rebinding: a request carrying a browser Origin header that is not loopback is refused with 403 before the token is compared. Requests with no Origin at all are allowed, because the MCP clients this port exists for are not browsers and never send one.

Where the network is not trusted, prefer the reverse-proxy path (/local/edgemcp/mcp), which the camera itself protects with admin digest authentication over the camera's own TLS.

Reporting a vulnerability

Please open a private security advisory on GitHub (https://github.com/kotyzap/EdgeMCP-for-Axis/security) or contact the author directly rather than filing a public issue.

There aren't any published security advisories