Policy reference¶
The policy decides what agents may do on each kind of environment, what the PreToolUse guard counts as a change and what counts as a secret. Behaviour: SPEC §7; design: 0013; a worked example: Full access on a trusted local machine.
Layers¶
A later layer wins: the built-in preset named by the global layer → [permission_defaults]
and guard_mode from config.toml (legacy, only when set) → the global file
(policy.json in the data directory, setting policy_file) → the project layer → the
environment layer (both stored in the database, edited in Settings → Policy). The
environment's own level and per-capability overrides on Stands & access win over every layer.
Objects merge key by key; scalars and lists replace. A list key may also be written key+ (add
items; a pattern with an existing id replaces it) or key- (remove items by value or id);
key is applied first, then key+, then key-. $schema and description are ignored;
preset is read from the global layer only.
The global file is read on start (a broken file stops relaybay serve with the path and the
problem) and again whenever it changes; a file that breaks later is logged, shown on the Policy
page, and the last valid version stays in effect. A broken project or environment layer is
logged and ignored.
Custom regular expressions (guard.patterns[].command / change, secrets.allow): at most 200
characters, no back-references, no quantified group holding a quantifier ((a+)+) or an
alternation ((a|aa)*), at most two unbounded quantifiers (*, +, {n,}), and fast on long
hostile input when the layer is loaded. They run case-insensitively with a hard time limit on at
most 8 KB of text; a call that times out fails closed (a guard pattern counts as matched, an
allow pattern does not allow) and is logged at most once a minute per pattern.
JSON Schema: policy.schema.json (the same file as
src/relaybay/policy/schema.json).
Keys¶
| Key | Type | Meaning |
|---|---|---|
$schema |
string | Editor hint; ignored. |
description |
string (at most 500 characters) | A note for people; ignored. |
preset |
one of default, strict, trusted |
Built-in starting point under every other layer. Global layer only. |
levels |
object | Agents' permission level per environment kind, used where an environment sets none itself. |
levels.local |
object | default: the level of the kind; a capability key: the level for that capability. |
levels.local.default |
one of none, read, ask, full |
Level of the kind. |
levels.local.sign_in |
one of none, read, ask, full |
Signing in and remembering the login. |
levels.local.ssh |
one of none, read, ask, full |
ssh, scp, rsync, sftp. |
levels.local.kubectl |
one of none, read, ask, full |
kubectl. |
levels.local.db |
one of none, read, ask, full |
Database clients. |
levels.local.deploy |
one of none, read, ask, full |
git push, helm, terraform, GitOps. |
levels.test |
object | default: the level of the kind; a capability key: the level for that capability. |
levels.test.default |
one of none, read, ask, full |
Level of the kind. |
levels.test.sign_in |
one of none, read, ask, full |
Signing in and remembering the login. |
levels.test.ssh |
one of none, read, ask, full |
ssh, scp, rsync, sftp. |
levels.test.kubectl |
one of none, read, ask, full |
kubectl. |
levels.test.db |
one of none, read, ask, full |
Database clients. |
levels.test.deploy |
one of none, read, ask, full |
git push, helm, terraform, GitOps. |
levels.live |
object | default: the level of the kind; a capability key: the level for that capability. |
levels.live.default |
one of none, read, ask, full |
Level of the kind. |
levels.live.sign_in |
one of none, read, ask, full |
Signing in and remembering the login. |
levels.live.ssh |
one of none, read, ask, full |
ssh, scp, rsync, sftp. |
levels.live.kubectl |
one of none, read, ask, full |
kubectl. |
levels.live.db |
one of none, read, ask, full |
Database clients. |
levels.live.deploy |
one of none, read, ask, full |
git push, helm, terraform, GitOps. |
guard |
object | The PreToolUse guard. |
guard.mode |
one of off, log, warn, block |
off: no checks; log: live journal only, no messages; warn: a warning to the agent; block: deny (ask stays ask in every mode but log). |
guard.disable |
list of one of kubectl-change, helm-change, ssh-remote-change, scp-to-host, sftp-session, sql-change, git-push, terraform-change, mcp-tool-name, at most 50 |
Built-in mutating rules to switch off, by id (replaces the list). |
guard.disable+ |
list of one of kubectl-change, helm-change, ssh-remote-change, scp-to-host, sftp-session, sql-change, git-push, terraform-change, mcp-tool-name, at most 50 |
Rule ids to add to the inherited list. |
guard.disable- |
list of one of kubectl-change, helm-change, ssh-remote-change, scp-to-host, sftp-session, sql-change, git-push, terraform-change, mcp-tool-name, at most 50 |
Rule ids to remove from the inherited list. |
guard.patterns |
list of object, at most 50 | Custom mutating patterns (replaces the list). |
guard.patterns+ |
list of object, at most 50 | Patterns to add; one with an inherited id replaces it. |
guard.patterns- |
list of string, at most 50 | Ids of inherited patterns to remove. |
secrets |
object | What counts as a secret in environment fields, access entries and proposals. |
secrets.disable |
list of one of private-key, github-token, github-pat, gitlab-token, openai-key, aws-access-key, slack-token, jwt, url-credentials, password-flag, curl-user, auth-header, token-header, sshpass, mysql-password, docker-login-password, redis-password, secret-assignment, secret-field-name, user-password, at most 50 |
Built-in secret detectors to switch off, by id (replaces the list). The journal still masks them. |
secrets.disable+ |
list of one of private-key, github-token, github-pat, gitlab-token, openai-key, aws-access-key, slack-token, jwt, url-credentials, password-flag, curl-user, auth-header, token-header, sshpass, mysql-password, docker-login-password, redis-password, secret-assignment, secret-field-name, user-password, at most 50 |
Detector ids to add to the inherited list. |
secrets.disable- |
list of one of private-key, github-token, github-pat, gitlab-token, openai-key, aws-access-key, slack-token, jwt, url-credentials, password-flag, curl-user, auth-header, token-header, sshpass, mysql-password, docker-login-password, redis-password, secret-assignment, secret-field-name, user-password, at most 50 |
Detector ids to remove from the inherited list. |
secrets.allow |
list of string (at most 200 characters), at most 50 | Regular expressions: a value (or a detected secret) that matches one whole is accepted (replaces the list). |
secrets.allow+ |
list of string (at most 200 characters), at most 50 | Allow patterns to add. |
secrets.allow- |
list of string (at most 200 characters), at most 50 | Allow patterns to remove. |
secrets.test_secrets |
object | Whether access fields may hold harmless test values marked {"value": "...", "test": true}. There is no live key: never on live. |
secrets.test_secrets.local |
boolean | Allowed on local environments. |
secrets.test_secrets.test |
boolean | Allowed on test environments. |
secrets.expose_test_secrets_to_agents |
boolean | Show test secret values to agents in environments_get (otherwise only their names). |
A custom pattern (guard.patterns, guard.patterns+):
| Key | Type | Meaning |
|---|---|---|
id |
string | Stable id, not one of the built-in rule ids. |
capability |
one of sign_in, ssh, kubectl, db, deploy, null |
Capability whose level applies; null or absent: any identifier. |
command |
string (at most 200 characters) | Optional: the command the change must follow in the same command segment. |
change |
string (at most 200 characters) | What makes the call a change (case-insensitive). |
description |
string (at most 200 characters) | Shown in the guard's reason, after "this looks like a". |
Built-in guard rules¶
| Id | Capability | Reason says |
|---|---|---|
kubectl-change |
kubectl |
kubectl change |
helm-change |
deploy |
helm change |
ssh-remote-change |
ssh |
remote change over ssh |
scp-to-host |
ssh |
file copy to a host |
sftp-session |
ssh |
file transfer session |
sql-change |
db |
SQL change |
git-push |
deploy |
git push |
terraform-change |
deploy |
terraform change |
mcp-tool-name |
any | MCP tool whose name says it changes something |
Built-in secret detectors¶
private-key, github-token, github-pat, gitlab-token, openai-key, aws-access-key, slack-token, jwt, url-credentials, password-flag, curl-user, auth-header, token-header, sshpass, mysql-password, docker-login-password, redis-password, secret-assignment, secret-field-name, user-password.
Switching a detector off affects only the check on write; the live-actions journal still masks every shape it knows.
Presets¶
default¶
relaybay's behaviour without a policy file.
{
"description": "relaybay's behaviour without a policy file.",
"levels": {
"local": {
"default": "full"
},
"test": {
"default": "full"
},
"live": {
"default": "read"
}
},
"guard": {
"mode": "warn",
"disable": [],
"patterns": []
},
"secrets": {
"disable": [],
"allow": [],
"test_secrets": {
"local": false,
"test": false
},
"expose_test_secrets_to_agents": false
}
}
strict¶
Agents ask on test and stay off live; the guard blocks.
{
"description": "Agents ask on test and stay off live; the guard blocks.",
"levels": {
"local": {
"default": "full"
},
"test": {
"default": "ask"
},
"live": {
"default": "none"
}
},
"guard": {
"mode": "block",
"disable": [],
"patterns": []
},
"secrets": {
"disable": [],
"allow": [],
"test_secrets": {
"local": false,
"test": false
},
"expose_test_secrets_to_agents": false
}
}
trusted¶
A trusted machine: full access everywhere, the guard only journals, test secrets on local and test.
{
"description": "A trusted machine: full access everywhere, the guard only journals, test secrets on local and test.",
"levels": {
"local": {
"default": "full"
},
"test": {
"default": "full"
},
"live": {
"default": "full"
}
},
"guard": {
"mode": "log",
"disable": [],
"patterns": []
},
"secrets": {
"disable": [],
"allow": [],
"test_secrets": {
"local": true,
"test": true
},
"expose_test_secrets_to_agents": false
}
}