Permissions¶
reyn's permission system gates access to file paths, shell, MCP tools, named tools, and Python preprocessor steps. Defaults are conservative; anything outside the defaults requires either a workflow-level declaration plus user approval, OR a project-wide pre-approval in reyn.yaml.
Default grants (no declaration needed)¶
| Op | Scope |
|---|---|
file.read / file.glob / file.grep |
Any path under the project root (CWD). |
file.write / file.edit / file.delete |
Only under <CWD>/.reyn/ or <CWD>/reyn/. |
Anything outside these defaults must be declared.
Workflow declarations (permissions: in skill.md frontmatter)¶
Phase-level permissions: was removed. All permission declarations belong in skill.md frontmatter — see skill-md.md. Phases inherit whatever the workflow declares.
---
type: skill
name: example
entry: main
final_output: result
permissions:
shell: true
mcp: [my_server]
tool: [web_search]
file:
read:
- path: ~/notes
scope: recursive
write:
- path: /tmp/output
scope: just_path
http.get:
- host: api.github.com # specific host: startup_guard prompts once, runtime silent
- host: "*" # wildcard: runtime per-host 4-layer prompt for any URL
secret.write:
- GITHUB_TOKEN # specific key, or
- "*" # wildcard for runtime-determined keys (= user-prompt is the gate)
python:
- module: stats
function: compute
mode: safe
timeout: 30
---
shell¶
true to enable the shell Control IR op for this phase. Off by default.
mcp, tool¶
List of MCP server names / named tool ids the phase may call.
file.read / file.write¶
For paths outside the default zones. Each entry has:
path— absolute, or relative to CWD.~is expanded.scope—just_path(this exact path) orrecursive(this path and everything below it).
file.write covers write, edit, and delete ops.
python¶
Per-(module, function) declarations for python preprocessor steps. See reference/dsl/preprocessor.md.
module,function— must match the corresponding preprocessor step.mode—safe(sandboxed) orunsafe(no AST sandbox; needs--allow-unsafe-pythonat runtime).timeout— wall-clock seconds before the parent SIGKILLs the child. Default30.
http.get¶
Per-host HTTP allowlist for reyn.api.safe.http.* (workflow-internal) AND for web_fetch (LLM-driven) — both surfaces share one axis.
- Specific host (
http.get: [{host: "api.github.com"}]) —startup_guardprompts once per<skill, host>; runtime is silent after approval. Same model asfile.writeoutside the default zone. - Wildcard (
http.get: [{host: "*"}]or["*"]) — host set is unknown at write-time (= LLM picks at runtime); the 4-layer prompt fires insiderequire_http_getat the actual host gate; ALWAYS / NEVER persists per host. - No declaration — legacy
web.fetchcompat fallback withDeprecationWarninguntil the migration window closes.
reyn.api.safe.http (subprocess path) accepts only specific hosts; wildcard requires the async web_fetch op route.
secret.write¶
Per-key allowlist for ~/.reyn/secrets.env writes (= called by the mcp_install op handler when persisting isSecret env vars).
- Specific key (
secret.write: ["GITHUB_TOKEN"]) — authorises that exact env-var name. - Wildcard (
secret.write: ["*"]) — runtime-determined key set (= mcp_install readsisSecretenv vars from the registry response). The operator's per-value prompt at op-execution time is the actual security gate.
Web ops¶
web_search is Tier 1: passes through by default without any declaration. Restrict project-wide via permissions.web.search: deny.
web_fetch is unified under the http.get axis (same per-host gate as safe.http). The chat router injects http.get: [{host: "*"}] so LLM-driven fetches keep working with per-host prompts replacing the old per-URL prompts. Legacy permissions.web.fetch: allow / deny config keys remain honored as backward-compat aliases during the migration window.
permissions:
web.search: deny # block all web_search ops
web.fetch: deny # legacy alias — overrides http.get wildcard, raises immediately
web.fetch: allow # legacy alias — pre-approves any host (= equivalent to ALWAYS for all hosts)
This differs from Tier 2-3 ops (shell, mcp) which require an explicit declaration in skill.md before the op is even attempted.
Approval flow (interactive)¶
When a phase declares a non-default permission, reyn shows a single startup prompt:
[approval] my_skill/file.write needs:
/tmp/output (just_path)
[y] allow this run only
[j] persist approval for this exact path + skill
[r] persist approval for the parent dir (recursive) + skill
[N] deny
Persistent choices land in .reyn/approvals.yaml keyed by <skill>/<op>/<path> (with a trailing / for recursive grants). External skills cannot reuse another skill's approvals — keys are skill-scoped to prevent privilege escalation.
Project-wide pre-approval (reyn.yaml)¶
permissions:
shell: allow
file.write: allow # grants ALL write-class ops for ALL skills
python:
safe: allow # auto-approve all safe-mode python steps
unsafe: allow # also requires --allow-unsafe-python at runtime
allowed_modules:
- math
- statistics
- mypackage
Use allow only when the project is trusted. ask (the default) prompts; deny rejects.
Non-interactive runs (CI, eval)¶
reyn eval runs non-interactively — there is no prompt. Approvals must be pre-arranged either in reyn.yaml or .reyn/approvals.yaml (e.g. by running the target workflow once interactively first).
Inspecting and revoking¶
See also¶
- reyn-yaml.md — full project config
-
state-dir.md —
.reyn/approvals.yamllocation -
Reference: control-ir — which ops need permissions