Skip to main content

Default deny

Steerholm uses an allowlist permission model. An agent with no policy gets zero tools — Steerholm creates an empty policy that denies everything.

How policy evaluation works

When an agent calls a tool, Steerholm checks three things in order:
1

Tool lookup

Steerholm resolves which server owns the requested tool. If the tool doesn’t exist on any server the agent has access to → AUTHORIZATION_DENIED.
2

Tool check

Does any tool permission in the policy match the requested tool name? If not → AUTHORIZATION_DENIED.
3

Argument check

Each argument policy is checked only when the call actually includes that argument — a tool that doesn’t take the argument is unaffected. If a provided argument fails its policy → AUTHORIZATION_DENIED. So --tool "*" --args "path=..." constrains the path argument wherever it appears, without rejecting tools that have no path.
If all checks pass, the request is forwarded to the MCP server.

Tool filtering

list_tools only returns tools that match at least one entry in the policy. Agents never see tools they aren’t allowed to use.

Argument policies

Argument policies restrict the values of specific tool arguments. Two match types are supported: A glob pattern without wildcards is an exact match — mode=readonly matches only "readonly".

CLI examples

Agents

Agents are created with holm add agent <name>. Each gets:
  • A name (stored in config.json)
  • A key prefix for display (stored in config.json)
  • A bcrypt-hashed access key (stored in system keyring — never in config files)
The agent authenticates by sending the access key as Authorization: Bearer <key> to the daemon’s Streamable HTTP MCP endpoint. The daemon resolves the agent by checking the access key against all stored hashes.
The caller does not declare which agent it is. Steerholm resolves the agent from the access key, so the caller cannot influence how it is identified.