Node Access Control
A noBGP node is ready to be operated remotely: your AI assistant can run commands and read or write files on it, and the event bus can watch paths and dispatch work. Settings on the machine let its owner narrow what the node serves.
These settings answer "what will this machine serve?" They apply to every caller: Owners, Admins, the node's owner and noBGP support. "Who may ask?" is noBGP's question, and Roles & Permissions answers it.
The three settings
| Setting | What it controls | Default |
|---|---|---|
allow-tools | Which capability domains the node serves: fs, command, logs | all three |
allow-roots | Which paths the file tools and the fs event source may touch when they do not run as root. The agent's own directory is never served | / |
allow-root | Whether any work may run as root (administrator on Windows) | true |
The domains of allow-tools are:
fs:file,fs_read,fs_write,fs_subscribeand the other file tools.command:command,command_subscribe, and the shell behind a published terminal.logs:node_logs, the agent's own log.
A fourth key, user, names the account that work runs as when it does not run as root. A node with no usable user cannot run such work at all. See Work without root never runs as root. The NOBGP_USER environment variable of the agent also sets it, and wins over the config file while it is set.
Every setting must say yes. Any one of them saying no is a refusal.
# Read-only node: files may be read and watched, nothing may be executed
sudo nobgp config --allow-tools=fs,logs
# Confine file access to one tree
sudo nobgp config --allow-roots=/var/log,/etc/myapp
# No root: work runs as the account named by --user
sudo nobgp config --allow-root=false --user=deploy
# Read them back at any time
nobgp show
nobgp status # includes an `allow:` block
Changes take effect without a restart: the agent watches its own config. No tool changes these settings. Only a person at the machine does.
A node that upgraded into a denied state would need someone physically at it. So the settings ship open, and the node's owner tightens them.
⚠ allow-tools refuses a value it cannot read. An entry that is not a domain (fs_read, commands) is dropped. If nothing valid is left, the node serves nothing. nobgp config --allow-tools refuses such a value, and nobgp status reports allow.tools_invalid for a profile that holds one.
The old name: allow-admin
allow-root was called allow-admin. The old key, the --allow-admin flag and NOBGP_ALLOW_ADMIN still work.
- ⚠ When both keys are set, the stricter value applies. A profile with
allow-admin: falsethat later getsallow-root: truestays atfalseuntil you change the old key too.nobgp showandnobgp statusname the key that each value came from. nobgp config --allow-root=falsewrites both keys, so an older agent keeps the veto. Setting it back totrueremoves both.
Two gates, not one
A root call must pass two gates:
| Gate | Question | Who answers |
|---|---|---|
| noBGP | Does this caller hold root on this node? | Roles & Permissions: the node's controllers, a root entry on its access list, a name on a root terminal |
The machine (allow-root) | May anyone run as root here? | the node's owner, on the machine |
Both refuse. Neither downgrades the call to the configured account. noBGP's refusal is forbidden. The machine's refusal, like every other setting on this page, is permission_denied. A node with allow-root: false also cannot be given a root access-list entry.
allow-tools and allow-root bind everyone. allow-roots binds only work that does not run as root: a root caller can undo such a confinement with one command anyway. To bound what root may do, set allow-root: false.
What these settings govern
They govern every remote path onto the node: the MCP tools, the event-bus sources, and published terminals. On a node with allow-tools empty:
| Access path | Result |
|---|---|
command | refused |
fs_read and the other file tools | refused |
| Event-bus source (fs watch, dispatched command) | refused, with the reason |
| Browser terminal from a published service | refused |
node_logs | refused |
allow-root: false refuses anything that would run as uid 0, whoever asks and whichever path it comes by. It also covers a user that is itself a superuser account: the node checks the uid, not the name.
One exception: node_logs reads the log as the agent itself, so allow-root does not bind it. The logs domain of allow-tools turns it off. Who may ask for the log is noBGP's question: control of the node.
Another node can connect directly to a port that this machine serves. That is ordinary network reach, and no setting above narrows it. It includes services bound to 127.0.0.1: a database with no password on loopback is reachable by every node in the network. Authenticate such a service, or set peer-loopback: false, which refuses a peer every port bound only on this machine's loopback. See How another node reaches this machine's own services.
The shared drive and the node's storage area live in noBGP storage. noBGP serves them without the agent, so no setting on the machine applies. A node's storage area needs control of the node instead. allow-roots still governs the mount point where the shared drive appears on the machine.
What these settings do not protect against
The settings are a file on the machine, and the agent reads it again when it changes. A caller who can run commands as an account that can write that file can change it. Root can always write it. The file tools cannot reach it: see The agent's own directory is never served.
To keep the settings fixed against remote callers, set allow-root: false and a user that cannot write the config directory. To close remote execution completely, remove command from allow-tools: this also closes the browser terminal. Before you do either, read Recovering a node you have locked down.
Work without root never runs as root
A call with root: "false" runs as the account named by user. If that account is uid 0, or there is no account and the agent runs as root, the node refuses the call. It never runs it as root instead.
| Node state | root: "false" | root: "true" |
|---|---|---|
| An account is set, exists, and is not uid 0 | runs as that account, inside allow-roots | runs as root, if both gates allow it |
| No account, or one that no longer exists, and the agent runs as root | refused | unchanged |
user: root, or another uid-0 account | refused | unchanged |
| No account, and the agent does not run as root | runs as the agent's own account | the same account: there is no root to give |
An omitted root runs as root where the node reports no account or user: root and allows root. root_used is then true. Everywhere else it runs as the configured account.
The refusal is failed_precondition, and it is not retryable. The message names both ways out:
# Give work without root an account to run as
sudo nobgp config --user deploy
…or the caller sends root: "true" if root is what they mean.
- Omitting
rootdoes not help on a node withallow-root: falseand no account. That node refuses both identities. - On a machine that has no account to configure, such as appliance or router firmware that ships only
root, the refusal says so. There every call runs as root or not at all. - Pick an account that can do the work.
--user nobodytrades one clear refusal for many confusing permission failures. - Read the answer on the machine.
nobgp statusreportsallow.without_root(the account) orallow.without_root_refusal(why there is none). See The fourth line is not a veto.
On Windows the agent runs as LocalSystem, which is uid 0 by this rule. So a Windows node with no account runs every call as root, and a configured user gives it a second identity.
A second identity on Windows
Set user on a Windows node, and work without root runs as that account: file operations, commands, terminals and dispatched commands. The node gets the account's token by name. No password is asked for, and no credential is stored on the machine.
# C:\ProgramData\nobgp\default.yml
user: alice
A file written without root is owned by that account, and a process started without root is that account's, with its own %USERPROFILE%, %APPDATA% and HKCU. If the node cannot get the token or load the profile, it refuses the call with failed_precondition. It never runs the call as LocalSystem instead.
The Windows installer records the account signed in at the desktop in user. A headless install records none, and the node keeps one identity until you set one.
- A profile directory is created. The first command as an account that never signed in at the machine creates
C:\Users\<account>.nobgp uninstallleaves it. - Network resources are not reachable as that account. The token holds no network credentials, so a command that reads a file share works with
root: "true"and fails without it. - That account is often an administrator. Then
root: "false"drops from LocalSystem to an administrator, and no further.network_directoryreports this asinfo.user_is_admin, andnobgp statusasallow.without_root_is_administrator.allow-rootsstill binds its file access. The token's privileges are removed, so it reaches only what the account's permissions allow. - A name the node cannot get a token for, such as a deleted account, leaves the node with one identity, and the agent logs why.
The agent's own directory is never served
The file tools and fs event sources refuse every path inside the agent's own configuration directory, whatever the settings say:
| Platform | Directory |
|---|---|
| Linux | /etc/nobgp |
| macOS | /usr/local/etc/nobgp |
| Windows | C:\ProgramData\nobgp |
allow-roots cannot widen this, and root does not open it. The directory holds the node's key, its JWT and the settings file. The refusal names itself, so do not answer it by widening allow-roots.
- A symlink is checked twice: where it points, and where it is. Both must be allowed.
- A tool that walks a tree (
fs_listwithrecursive,fs_glob,fs_grep) checks each directory it enters, and skips a refused one. The listing names the refused directory, but nothing inside it. - This rule bounds the file tools, not the machine. A
commandsession as an account that can read those files still can.allow-toolswithoutcommand, orallow-root: false, closes that.
The agent also closes the directory to other accounts. On Windows, only SYSTEM and Administrators can read it, so run nobgp commands that read the config from an Administrator prompt. On Linux and macOS, root owns it, so those commands need sudo.
Recovering a node you have locked down
If you drop command from allow-tools, or set allow-root: false on a node with no account to fall back to, remote execution is gone, published terminals included. Only a change on the machine brings it back. On a machine behind CGNAT, that means console, RDP or SSH access.
The usual answer is to keep the veto and give work an account: sudo nobgp config --allow-root=false --user=deploy refuses root and leaves the node operable as deploy. The account must exist on the machine and must not be uid 0.
Confirmed before it is written
nobgp config checks the changes that can close the last door before it saves the file. A refusal or a declined prompt leaves the node as it was.
| Change | Interactive | Non-interactive |
|---|---|---|
--allow-tools dropping command | asks | refused unless --yes |
--allow-root=false with no account to fall back to | asks | proceeds, with a warning in the log |
--user "" (clearing the account) | asks | proceeds, with a warning |
--user naming an account that does not exist, or one that is uid 0 | refused | refused |
--allow-tools naming something that is not a domain | refused | refused |
--local-mcp with a value other than on or off | refused | refused |
Dropping command while fs is still served and allow-root is true also prints a warning: a root fs_write can still write a cron job, a systemd unit or a launchd plist. The warning names --allow-root=false as the way to close that too.
Clear the account with --user "". An empty value without the --user flag reads as "never set", and the install account comes back. A reinstall or a new registration records the install account again.
Getting back in
On the machine itself (console, RDP, SSH, local keyboard):
sudo nobgp config --allow-tools=fs,command,logs
sudo nobgp config --allow-root=true
# Or keep the veto and name an account for work without root
sudo nobgp config --user=deploy
The last one is the better fix on a node that you want to keep locked down.
node_config_set is not a way around a settingnode_config_get / node_config_set read and write a node's settings over its control channel, so they work on a node whose sessions are all refused. They serve only operational settings. The node refuses every setting on this page, the keys that name its own identity, and its owner's other settings (auto-upgrade, enabled, mount, lan-names, the mDNS keys, local-mcp, peer-loopback and windows-firewall). They exist to fix a setting that broke the node's sessions, such as a wrong fs: backend.
Diagnostics keep answering: presence events come from noBGP, and net_peers / net_metrics are served outside the allow-tools domains. So you can see that the node is alive while its tools are off.
Checking what a node will refuse
nobgp status reports the effective settings, and warns when a node has narrowed its own remote access:
allow:
tools: [fs, logs]
roots: [/]
root: true
root_source: default
without_root: deploy
warning: 'allow-tools excludes "command": remote execution is off on this node,
published terminal services included.'
root_source names the key that root came from. The old field names (admin, unelevated, unelevated_refusal, unelevated_is_admin) are still reported with the same values. The full table is in the CLI reference.
From the calling side, a refused tool call answers permission_denied, not retryable, with the node's own words naming the setting. A refused bus source reports refused, with the reason. The work-without-root refusal uses a different code on purpose, failed_precondition: it is not a verdict on the caller, and its fix is on the machine or in the next call.
The fourth line is not a veto
The allow: block carries without_root, the account that a root: "false" call runs as, or without_root_refusal when there is none. Exactly one of the two is present. The agent also prints the old names beside them (admin, unelevated, unelevated_refusal).
allow:
tools: [fs, command, logs]
roots: [/]
root: true
root_source: default
without_root_refusal: 'host "web-server-1" is not configured for unelevated
execution: no account is set, so it would run as the superuser. Configure an
account with `nobgp config --user <account>` on the node, or send root: "true"
to run as root'
The text is the refusal that a caller would get. Every call on such a node runs as root: send root: "true", or omit root.
On Windows a fifth line, without_root_is_administrator: true, appears when the account in without_root is an administrator. It is present only when true.
These lines come from the gate, not from the user key. A deleted account or user: root looks fine in the config file and is refused at the call. nobgp show prints what you configured; these lines print what the node will do. The node's local status tool reports the same block. For other nodes, read network_directory.
Other settings only the machine's owner sets
These settings bound what the node does for the network. They follow the same rule: set on the machine, and node_config_set refuses each one.
| Setting | What it controls | Default |
|---|---|---|
peer-loopback | Whether another node may reach a port bound only on this machine's loopback (127.0.0.1, [::1]) | true |
local-mcp | Whether this node runs its local MCP server at all, whatever node_grant says | on |
lan-names | Which host names on this machine's LAN it resolves for the network | * (every name) |
mdns-report-services | Whether the node tells the network which services its own host offers | true |
mdns-announce | Whether other nodes and their services are shown to this machine's own apps (macOS and Linux) | false |
windows-firewall | Whether the agent keeps an inbound allow rule in Windows Firewall | allow-overlay |
The node does not resolve a name outside lan-names for the network. The setting does not change what the machine's own apps resolve. See LAN Names and Local Discovery.
# Serve two devices on this LAN to the network, and nothing else
sudo nobgp config --lan-names 'nas*,printer'
# Keep loopback-only services off the network
sudo nobgp config --peer-loopback=false
# Never serve MCP on 127.0.0.1, whatever noBGP grants
sudo nobgp config --local-mcp=off
Related
- Roles & Permissions: who may ask
- Interactive Terminals: the terminal paths these settings govern
- Publishing Services: how terminal services are created and opened
- CLI Reference: every flag
- MCP Reference → Execution identity: the
rootfield per tool