Skip to content
37 changes: 35 additions & 2 deletions reference/configuration/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -397,11 +397,44 @@ agent:
- `autoApprove` — Run without per-action approval gates; _Default_: `false`
- `allowDestructive` — Include the tools marked destructive in the agent's toolset: `write_file`, the inspector's code-evaluation tools, and any operations tool carrying MCP's [`destructiveHint`](../mcp/tool-metadata.md) (`drop_table`, `delete`, `restart`, `set_configuration`, ...). When `false` they are removed entirely rather than gated. That hint comes from a curated set in core which does not cover every damaging operation, so this is not a complete safety boundary on its own — see [Agent operations](../operations-api/operations.md#agent); _Default_: `false`
- `user` — Harper user the agent's **operations** tools run as; the filesystem, HTTP, schedule, and inspector tools always run at process privilege regardless. If it cannot be resolved and it is not the default, the agent fails closed and runs with no operations tools; _Default_: `hdb_agent`, which falls back to a `super_user` bootstrap identity
- `componentsScope` — Filesystem write scope for component edits, relative to `rootPath`; _Default_: the full `componentsRoot`
- `componentsScope` — The agent's `components` filesystem scope, which it reads and, with `allowDestructive`, writes; absolute or relative to `rootPath`. Read at startup only; _Default_: the full `componentsRoot`
- `configScope` <VersionBadge version="v5.4.0" /> — The agent's read-only `config` filesystem scope: a file or a directory, absolute or relative to `rootPath`. Read at startup only. See [Filesystem scopes](#filesystem-scopes); _Default_: the Harper config file only
- `httpFetch` <VersionBadge version="v5.3.2" /> — Whether the agent has its `http_fetch` tool, and which hosts it may reach: `true`, `false`, or `{ allow: [...] }`. Read at startup only. See [Restricting `http_fetch`](#restricting-http_fetch); _Default_: `true`
- `systemPromptAppend` — Operator text appended to the agent's system prompt

`enabled`, `provider`, `model`, `maxTurns`, `maxCostUsd`, `autoApprove`, `allowDestructive`, and `systemPromptAppend` can also be changed at runtime with [`set_agent_config`](../operations-api/operations.md#set_agent_config), which applies in memory only. `enabled` is the exception worth knowing: it cannot switch the agent on, because with the agent disabled at startup no agent operation is registered at all.
`enabled`, `provider`, `model`, `maxTurns`, `maxCostUsd`, `autoApprove`, `allowDestructive`, and `systemPromptAppend` can also be changed at runtime with [`set_agent_config`](../operations-api/operations.md#set_agent_config), which applies in memory only. `enabled` is the exception worth knowing: it cannot switch the agent on, because with the agent disabled at startup no agent operation is registered at all. `httpFetch`, `componentsScope`, and `configScope` are read at startup only, and `set_agent_config` rejects them.

### Filesystem scopes

<VersionBadge type="changed" version="v5.4.0" />

The agent's read tools (`read_file`, `list_dir`, `grep_files`, and `tail_file`) take a `root` naming one of three scopes. `write_file`, available when `allowDestructive` is on, takes no `root` and always writes to `components`.

| Scope | Reaches | Access |
| ------------ | ------------------------------------------------- | -------------- |
| `components` | `componentsRoot`, or `componentsScope` when set | read and write |
| `logs` | the log directory | read |
| `config` | the Harper config file, or `configScope` when set | read |

Before v5.4.0, `config` was the directory holding `harper-config.yaml`. On a default install that is `rootPath` itself, so the agent could read `keys/`, `database/`, and the rest of the root. It is now the config file alone, and `list_dir` shows only that file.

To point the agent somewhere else, such as a directory of extra configuration, set `configScope` to a file or a directory, absolute or relative to `rootPath`. It replaces the config file as the scope: the config file stays readable only if the new target contains it, as `rootPath` does. It is read at startup only. A path that names no file or directory at startup leaves the `config` scope unavailable and logs an error.

```yaml
agent:
enabled: true
configScope: /etc/harper/extra
```

Whatever `componentsScope` and `configScope` say, every scope refuses:

- **Harper's key directories**, `<rootPath>/keys` (TLS and JWT keys) and `<rootPath>/ssh` (git deploy keys): nothing in them is read, listed, or written. Paths are compared after resolving symlinks.
- **Key file names**, `*.pem`, `*.key`, and `.jwtPass`: not read. `list_dir` still shows the names, and `write_file` can still create such a file outside the key directories.
- **Text holding a PEM private key** (`-----BEGIN ... PRIVATE KEY-----`), such as an inline `tls.privateKey` in the config file: `read_file` refuses the file, `tail_file` refuses lines that hold one or a file that ends partway through one, and `grep_files` skips the file.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we fix the core tail_file boundary before promising that a file ending partway through a PEM private key is refused? Its implementation scans only the final 1 MiB plus 64 bytes for the BEGIN marker. If a PEM block in a non-key-named file such as log.txt starts earlier and continues to EOF, tail_file can return trailing base64 key text. This is a code-trace finding; I did not execute a test. A focused check is such a file with BEGIN more than 1 MiB before EOF: tail_file should refuse it. Please make core fail closed for that case, or state the actual limit here and in the release note.


A key with neither a key file name nor PEM armor, such as a `.p12` bundle or raw DER, is not recognized outside the key directories.

The config file can still hold secrets that are not PEM keys, such as a model `apiKey` or storage credentials, and the agent reads it. Setting `configScope` to `rootPath` also restores read access to the raw database files and backups.

### Restricting `http_fetch`

Expand Down
4 changes: 2 additions & 2 deletions reference/operations-api/operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -1674,7 +1674,7 @@ Anyone who can call `agent_prompt` can direct whatever the agent does. Understan

- `agent.user` (default: a `super_user` bootstrap identity) governs only the **operations** tools. Setting it to a restricted user narrows those, and nothing else.
- The agent's other tools — scoped filesystem access, outbound `http_fetch`, followup scheduling, and the V8 inspector — run at the Harper process's own privilege, whatever `agent.user` is. (The inspector tools additionally need `threads.debug`, and fail with an explanatory error without it.)
- By default, `http_fetch` blocks only cloud-metadata hosts and the IPv4 link-local range `169.254.0.0/16`, and it checks host names, not resolved addresses: a name that resolves to a blocked address is not caught. Since v5.3.2, it also checks every redirect hop before following it. Every other host, including anything private or internal the server can route to, is reachable. Reading is ungated too: `read_file` covers the log and configuration directories as well as the component tree, so an enabled agent puts a read path and an egress path in the same toolset. Unless the agent needs the network, set [`agent.httpFetch`](../configuration/options.md#restricting-http_fetch) to `false` to remove the tool, or to an allow-list of the hosts it needs. Otherwise, treat it as an outbound network client and apply egress policy to the host.
- By default, `http_fetch` blocks only cloud-metadata hosts and the IPv4 link-local range `169.254.0.0/16`, and it checks host names, not resolved addresses: a name that resolves to a blocked address is not caught. Since v5.3.2, it also checks every redirect hop before following it. Every other host, including anything private or internal the server can route to, is reachable. Reading is ungated too: `read_file` covers the component tree, the log directory, and the Harper config file (before v5.4.0, the whole directory holding it), and further wherever `componentsScope` or `configScope` widens a scope (every scope refuses Harper's key directories, key file names, and PEM private keys; see [Filesystem scopes](../configuration/options.md#filesystem-scopes)), so an enabled agent puts a read path and an egress path in the same toolset. Unless the agent needs the network, set [`agent.httpFetch`](../configuration/options.md#restricting-http_fetch) to `false` to remove the tool, or to an allow-list of the hosts it needs. Otherwise, treat it as an outbound network client and apply egress policy to the host.
Comment thread
DavidCockerill marked this conversation as resolved.
Outdated
- With the default `agent.allowDestructive: false`, destructive tools (including filesystem writes) are removed from the toolset entirely. Turning it on admits component writes, and component code is executed by the Harper process — a write is effectively code execution at process privilege.
- Leave `agent.autoApprove` off so any destructive call that is admitted still pauses for [approval](#approve_agent_action). The gate covers only the tools marked destructive — filesystem writes, the inspector's code-evaluation tools, and the operations on MCP's [curated destructive set](../mcp/tool-metadata.md) (`drop_table`, `delete`, `restart`, `set_configuration`, ...). `http_fetch` and followup scheduling are not gated, so an outbound POST and a self-rescheduling run proceed without an approval prompt.
- That set is an explicit list in core rather than a prefix match, and it is not a list of every damaging operation, so `allowDestructive` and `autoApprove` are not a boundary by themselves. The component operations are the ones to know about: `drop_component` and `deploy_component` are both off the set, so opting either into [`mcp.operations.allow`](../mcp/configuration.md#mcpoperationsallow) puts it in the agent's toolset where `allowDestructive: false` does not remove it and no approval gates it — and `deploy_component` writes code the Harper process then executes. Vet anything you add to that allow list on its own merits rather than assuming these two settings cover it.
Expand Down Expand Up @@ -1809,7 +1809,7 @@ One gap is worth knowing: changing `allowDestructive` with [`set_agent_config`](

### `set_agent_config`

Updates agent settings and returns the resulting configuration. Accepts any of `enabled`, `provider`, `model`, `maxTurns`, `maxCostUsd`, `autoApprove`, `allowDestructive`, and `systemPromptAppend`; keys not supplied are left unchanged. Each field is described under [`agent`](../configuration/options.md#agent). A request that includes `httpFetch` is rejected with a 400 and nothing in it is applied: the [`http_fetch` policy](../configuration/options.md#restricting-http_fetch) is read at startup only.
Updates agent settings and returns the resulting configuration. Accepts any of `enabled`, `provider`, `model`, `maxTurns`, `maxCostUsd`, `autoApprove`, `allowDestructive`, and `systemPromptAppend`; keys not supplied are left unchanged. Each field is described under [`agent`](../configuration/options.md#agent). A request that includes `httpFetch`, `componentsScope`, or `configScope` is rejected with a 400 and nothing in it is applied: the [`http_fetch` policy](../configuration/options.md#restricting-http_fetch) and the [filesystem scopes](../configuration/options.md#filesystem-scopes) are read at startup only.

```json
{ "operation": "set_agent_config", "autoApprove": false, "maxTurns": 20 }
Expand Down
6 changes: 6 additions & 0 deletions release-notes/v5-lincoln/5.4.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,9 @@ A durable MQTT session now resumes through the same checked replay. When a recon
### Certifying a Release in a Canary Worker

A `deploy_component` with `restart: true` or `restart: "rolling"` now checks its release before rolling it out. The first worker started on the release, the canary, is held out of traffic until it reports whether the deployed component loaded, and the rollout goes on only if it did. Each later replacement is checked the same way. When the node that received the deploy rejects the release, the deploy fails with `400`. The release it replaced is made live again, or, with nothing to go back to, the component fails closed on that node. A new `certification` field in the response reports the outcome, and says when a restarting deploy went out unchecked (`uncertified` or `unavailable`, as for a `package: file:<dir>` deploy). With `"rolling"`, each other node then certifies the release with a canary of its own, and a node that rejects it fails the deploy's job, not the deploy: poll its `restartJobId` with `get_job` for the outcome. A node running a version before 5.4.0 activates without a canary. A deploy that restarts nothing is not test-loaded. If its release throws when it loads, `get_status` reports the failure. See [Certifying a release in a canary worker](/reference/v5/operations-api/operations#certifying-a-release-in-a-canary-worker).

## Agent

### The `config` Scope Is the Config File

The built-in agent's read-only `config` filesystem scope used to be the directory holding `harper-config.yaml`. On a default install that is `rootPath`, so the agent could read `keys/`, `ssh/`, and the raw database files. It is now the config file alone. The new `agent.configScope` replaces it at startup with another file or directory, which keeps the config file readable only if it contains it. In every scope, the agent's filesystem tools now refuse to read Harper's key directories (`<rootPath>/keys` and `<rootPath>/ssh`), files named `*.pem`, `*.key`, or `.jwtPass`, and text holding a PEM private key, and they refuse to write into the key directories. `set_agent_config` now rejects `configScope` and `componentsScope` with a `400`, as it already did `httpFetch`. An agent that read other files through `config` needs `configScope` set to a directory that holds them. See [Filesystem scopes](/reference/v5/configuration/options#filesystem-scopes) ([harper#3041](https://github.com/HarperFast/harper/issues/3041)).
Loading