repo.box Spec: Permissions
Overview
Permissions control what identities and groups can do — which branches they can push to, which files they can modify, and how. Rules are defined in .repobox/config.yml (YAML), enforced both locally (by the repobox CLI) and server-side (on push). No bypass possible.
Core Principles
- Enforced everywhere. The git shim enforces permissions locally. An agent cannot merge to main on its own machine. The server also rejects non-compliant pushes as defense in depth.
- No bypass. Git hooks can be skipped with
--no-verify. The shim cannot — it IS thegitcommand in the agent's environment. - Implicit deny per target. If any rule allows a subject for a specific verb+target, other identities are implicitly denied for that target. Targets not mentioned by any rule remain open (or closed, depending on
default). - Top-to-bottom priority. First matching rule wins. Higher position = higher authority.
- No overrides. There is no sudo, no admin escape hatch, no emergency bypass.
Two Types of Checks
Every git operation may trigger two independent checks:
- Branch check — can you perform this branch operation? (
push,merge,create,delete,force-push) - File check — can you modify these files? (
edit,write,append)
Both must pass. Having push permission on a branch doesn't automatically grant edit on every file. But if no edit rules exist at all, file editing is unrestricted (with default: allow).
This means a minimal config can just use branch rules and skip file rules entirely:
permissions:
default: allow
rules:
- founders push >*
- founders merge >*
- agents push >feature/**
- agents create >feature/**
No edit/write/append rules → no file restrictions. Anyone who can push can modify any file. Add file rules only when you need file-level control.
Rule Syntax
Rules can be written in three formats. All are equivalent — use whichever feels natural, and mix freely.
Format A: Flat list (one-liners)
rules: is a YAML list. Each entry is a string: <subject> [not] <verb> <target>
rules:
- founders edit *
- founders push >*
- agents not merge >main
- evm:0xBBB...456 push >feature/**
Format B: Subject-grouped (subject → list of "verb target" strings)
rules: is a YAML mapping. Each key is a subject, value is a list of "verb target" strings:
rules:
founders:
- push >*
- merge >*
- edit *
agents:
- not merge >main
- push >feature/**
- push >fix/**
- create >feature/**
- create >fix/**
Format C: Verb-mapping (subject → verb → targets)
rules: is a YAML mapping. Each key is a subject, value is a mapping of verb → target list:
rules:
agents:
push:
- ">feature/**"
- ">fix/**"
- ">chore/**"
create:
- ">feature/**"
- ">fix/**"
- ">chore/**"
merge:
- ">chore/**"
append:
- "./.repobox/config.yml"
Mixing formats
In Format A (list), individual entries can be flat strings OR nested mappings (C-style):
rules:
- founders push >*
- founders merge >*
- agents:
push:
- ">feature/**"
append:
- "./.repobox/config.yml"
Formats B and C both use a top-level mapping for rules:. Write it however feels natural.
Subjects
groupname— group reference (defined ingroups:section)evm:0x...— individual identity
Verbs
Branch verbs (control branch operations):
| Verb | Meaning |
|---|---|
push |
Push commits to a branch |
merge |
Merge into a branch |
create |
Create a new branch matching pattern |
delete |
Delete a branch |
force-push |
Rewrite history (force push) |
File verbs (control file modifications):
| Verb | Meaning |
|---|---|
edit |
Full file modification — add, change, or remove lines |
write |
Add lines only — no deletions allowed |
append |
Add lines strictly at the end of the file only |
Prefix any verb with not to deny: agents not merge >main
edit vs write vs append
Three levels of file modification, each a subset of the previous:
edit: Full power. Add, modify, delete any line.write: Can add new lines anywhere, but cannot remove or modify existing lines. Diff may only contain+lines, no-lines.append: Can only add lines at the end of the file. Preserves the entire existing file and only extends it.
The shim validates at commit time by inspecting the diff:
write: rejects any diff hunk containing-lines for that fileappend: rejects if any+lines appear before the last existing line of the file
Targets
>main— branch named "main">feature/*— branches matching glob (one level)>feature/**— branches matching glob (recursive)*— all files / all branches (context-dependent:push >*= all branches,edit *= all files)contracts/**— file path glob (recursive)package.json— specific filecontracts/** >dev— combined: file path + branch (both must match)
Combined Targets (Path + Branch)
A target can combine a file path and branch:
devs write contracts/** >feature/*
This means: "devs can write to files matching contracts/** on branches matching feature/*." Both the path and branch must match for the rule to apply.
If only a branch is specified (>main), the rule applies to all files on that branch.
If only a path is specified (contracts/**), the rule applies on all branches.
Config Format
Minimal (branch control only, no file restrictions)
groups:
founders:
- evm:0xAAA...123
agents:
- evm:0xBBB...456
permissions:
default: allow
rules:
- founders push >*
- founders merge >*
- founders create >*
- agents:
push:
- >feature/**
- >fix/**
create:
- >feature/**
- >fix/**
Agents can push and create feature/fix branches, edit any files on those branches. Only founders can push/merge/create on main and other branches.
With file restrictions
permissions:
default: allow
rules:
- founders push >*
- founders merge >*
- founders create >*
- founders edit .repobox/config.yml
- agents:
push:
- >feature/**
- >fix/**
create:
- >feature/**
- >fix/**
edit:
- * >feature/**
- * >fix/**
append:
- .repobox/config.yml
Now agents can edit files, but only on feature/fix branches. And .repobox/config.yml is locked to founders for full edits — agents can only append to it.
The default field
allow(default if omitted) — if no rule covers a verb+target combination at all, the action is permitteddeny— if no rule covers a verb+target combination at all, the action is denied
Important: default only matters for verb+target combinations with zero matching rules. The moment any rule mentions a verb for a target, implicit deny handles identities not covered by that rule.
How Implicit Deny Works
This is the most important concept to understand.
Implicit deny is scoped to the target, not the verb globally.
When you write founders edit .repobox/config.yml, the system learns: "someone has explicit edit access to .repobox/config.yml." Any identity NOT matched by an edit rule for .repobox/config.yml is implicitly denied. But files NOT mentioned by any edit rule are unaffected — they follow default.
Example: selective file protection
permissions:
default: allow
rules:
- founders edit .repobox/config.yml
| Action | Result | Why |
|---|---|---|
| founders edit .repobox/config.yml | ✅ permit | Rule matches |
| agents edit .repobox/config.yml | ❌ deny | Implicit deny: rule exists for edit .repobox/config.yml, agents not matched |
| agents edit src/app.rs | ✅ permit | No edit rule mentions src/app.rs → default: allow |
| agents edit package.json | ✅ permit | No edit rule mentions package.json → default: allow |
Only .repobox/config.yml is protected. Everything else is open.
Example: broad file lockdown
permissions:
default: allow
rules:
- founders edit *
- agents edit * >feature/**
| Action | Result | Why |
|---|---|---|
| founders edit anything | ✅ permit | founders edit * matches all files |
| agents edit src/app.rs on feature/fix | ✅ permit | agents edit * >feature/** matches |
| agents edit src/app.rs on main | ❌ deny | edit * has rules, agents only matched on >feature/** → implicit deny on main |
Here founders edit * covers all files, so implicit deny applies to all files for non-founders. But agents edit * >feature/** carves out an exception for agents on feature branches.
Evaluation Algorithm
Given an action (subject, verb, target):
- Collect all rules for this verb whose target pattern matches the actual target
- If zero rules match this verb+target:
default: allow→ permitdefault: deny→ deny
- If matching rules exist, walk them top-to-bottom:
- For each rule, check if subject matches
- First subject match wins:
- Allow rule → permit
not(deny) rule → deny
- If no rule matched the subject but rules exist for this verb+target → deny (implicit deny)
Deny ordering
Explicit not rules must come before broader allows to take effect:
rules:
# ✅ Correct: deny first, then allow
- agents not push >main
- agents push >*
# ❌ Wrong: push >* matches first, deny never reached
- agents push >*
- agents not push >main
Priority Model
Top-to-bottom, first match wins. Rules higher in the file have higher priority.
Why this works safely
- Founders have full
editaccess to.repobox/config.yml. They write the top rules — highest priority. - Agents can only
appendto.repobox/config.yml. Their rules land at the bottom — lowest priority. They can never override founder rules.
Append-only + top-wins = permission escalation is structurally impossible.
Branch-Scoped Agent Permissions
Agents can grant sub-agents temporary access on feature branches:
- Agent on
feature/fixgenerates a key for a sub-agent - Agent appends a direct permission rule to
.repobox/config.yml:evm:0xSub write >feature/fix/* - Commits the change → ✅ (has append permission on feature branch)
- Sub-agent works within its scope
- Work done. Agent reverts
.repobox/config.ymlchanges. - Merges clean code to
main→ ✅ (no.repobox/config.ymlchanges in diff)
If the agent forgets to revert:
❌ Blocked: merge contains .repobox/config.yml changes.
claude cannot edit .repobox/config.yml on >main.
Permissions are branch-scoped: sub-agent access exists only while the feature branch exists.
Permission smuggling prevention
The server evaluates permissions from the .repobox/config.yml on the target branch, not incoming changes. Modifying .repobox/config.yml to grant yourself access doesn't work — the pre-push version is used for evaluation.
Which .repobox/config.yml applies?
| Operation | Which .repobox/config.yml? |
|---|---|
git commit |
Current branch |
git push |
Current branch |
git merge X into Y |
Y (target branch) |
Enforcement Layers
Layer 1: Git shim (local)
- Intercepts
git commit,git merge,git push,git checkout -b,git branch - Validates every commit against
.repobox/config.ymlbefore delegating to real git - Validates
write/appendconstraints by inspecting the diff - Cannot be bypassed — it IS the
gitcommand in the agent's environment - Read-only commands (
git status,git log,git diff, etc.) pass through unchanged
Layer 2: Server (remote, post-hackathon)
- Validates every push against
.repobox/config.ymlon the target branch - Defense in depth — catches raw
gitusage that bypasses the shim
Tooling
git repobox check <identity> <verb> <target>
Answers "can this identity do this?" against the current .repobox/config.yml. Shows which rule matched and why.
git repobox check evm:0xBBB...456 push >main
# ❌ denied — implicit deny (rules exist for 'push >main', no match for this identity)
git repobox check evm:0xBBB...456 push >feature/fix
# ✅ allowed — rule: agents push >feature/**
git repobox lint
- Validates
.repobox/config.ymlsyntax - Detects undefined group references
- Warns about ordering issues (allow below deny for same target)
- Detects unreachable rules (shadowed by earlier broader rules)
git repobox diff
Shows permission changes between two .repobox/config.yml versions. Used in code review.
What Permissions Does NOT Cover
- Who is in a group — See 02-groups.md
- PR/merge workflows — See 04-workflows.md
- Read access — Future consideration
- Tag permissions — Future addition