docs: document branch-limited tokens
- commit
- cb46f91616b1f5e91258ec6ad332c6892bdfb3d3
- author
- brice · · 2026-10-12 15:00 UTC
- committer
- brice · · 2026-10-12 15:01 UTC
- parent
- 977138d
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +17 -3
M README.md +13 -1
| @@ -37,8 +37,10 @@ The same binary drives a running server over its API. It reads `ERMITE_URL` (the | |||
| 37 | 37 | ```sh | |
| 38 | 38 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | |
| 39 | 39 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | |
| 40 | - | ermite token create <label> [--preset cli|agent|ci] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once | |
| 40 | + | ermite token create <label> [--preset cli|agent|ci] [--scope repo:read …] [--repo name …] [--branch-prefix agent/x/ …] [--expires 30d] # prints the secret once | |
| 41 | 41 | ermite token list | revoke <id> | |
| 42 | + | # a token for an AI agent that cannot touch main: | |
| 43 | + | ermite token create claude --preset agent --scope repo:read --repo myrepo --branch-prefix agent/claude/ | |
| 42 | 44 | ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff] | |
| 43 | 45 | ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment> | |
| 44 | 46 | ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n> | |
| @@ -62,6 +64,16 @@ git push http://u:<token>@git.example.com/<repo>.git main | |||
| 62 | 64 | ||
| 63 | 65 | Use the admin token (`ERMITE_ADMIN_TOKEN`) or a stored token as the password; the username is ignored. Store it in a Git credential helper rather than in the URL. Pushing to a new name creates a private repository. Repository names are lowercase, `[a-z0-9._-]`. TLS is terminated by your proxy; list its network in `ERMITE_TRUSTED_PROXIES` so that failed-login limits see the real client address (`X-Forwarded-For`). | |
| 64 | 66 | ||
| 67 | + | ## Branch-limited tokens | |
| 68 | + | ||
| 69 | + | For an AI agent that should propose changes, not publish them: `--branch-prefix agent/claude/` (repeatable, up to 8, each ending with `/`) limits a token to the branches under its prefixes. | |
| 70 | + | ||
| 71 | + | - It can push, force-push and delete `refs/heads/agent/claude/…`. Any other ref (`main`, other branches, tags, notes, anything outside `refs/heads/`) refuses the whole push, and the message names the ref. | |
| 72 | + | - It cannot create a repository (push to a new name, or `repo create`) nor delete one, never has the rights of the owner (it cannot open web sessions even with every scope), and can only create tokens whose prefixes lie within its own. | |
| 73 | + | - It opens, comments on and closes merge requests as usual; with `mr:merge` it can merge only into a branch under its prefixes, and delete only a source branch under them. You review and merge the agent's request. | |
| 74 | + | - `ermite token list` shows a `branches` column (`all` when unlimited); `--json` has `branch_prefixes`. | |
| 75 | + | - This build stores tokens in storage schema 2. Once it has started on your data, an older ermite refuses to start on it (it would read limited tokens as unrestricted): to go back, restore the backup taken before the upgrade. | |
| 76 | + | ||
| 65 | 77 | ## Merge requests | |
| 66 | 78 | ||
| 67 | 79 | A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch). | |
| @@ -37,8 +37,10 @@ The same binary drives a running server over its API. It reads `ERMITE_URL` (the | |||||
| 37 | ```sh | 37 | ```sh | ||
| 38 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | 38 | export ERMITE_URL=https://git.example.com ERMITE_TOKEN=ermite_... | ||
| 39 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | 39 | ermite repo list | show <name> | create <name> [--description …] [--visibility public] | edit <name> [--description …] [--visibility …] [--default-branch …] | delete <name> [--yes] | ||
| 40 | - | ermite token create <label> [--preset cli|agent|ci] [--scope repo:read …] [--repo name …] [--expires 30d] # prints the secret once | 40 | + | ermite token create <label> [--preset cli|agent|ci] [--scope repo:read …] [--repo name …] [--branch-prefix agent/x/ …] [--expires 30d] # prints the secret once |
| 41 | ermite token list | revoke <id> | 41 | ermite token list | revoke <id> | ||
| 42 | + | # a token for an AI agent that cannot touch main: | |||
| 43 | + | ermite token create claude --preset agent --scope repo:read --repo myrepo --branch-prefix agent/claude/ | |||
| 42 | ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff] | 44 | ermite mr create <repo> --source <branch> --title … | list <repo> | show <repo> <n> [--diff] | ||
| 43 | ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment> | 45 | ermite mr comment <repo> <n> -m … [--path … --line … --side old|new] | resolve <repo> <n> <comment> | ||
| 44 | ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n> | 46 | ermite mr merge <repo> <n> --head <id> [--squash] [--delete-branch] | close <repo> <n> | ||
| @@ -62,6 +64,16 @@ git push http://u:<token>@git.example.com/<repo>.git main | |||||
| 62 | 64 | ||||
| 63 | Use the admin token (`ERMITE_ADMIN_TOKEN`) or a stored token as the password; the username is ignored. Store it in a Git credential helper rather than in the URL. Pushing to a new name creates a private repository. Repository names are lowercase, `[a-z0-9._-]`. TLS is terminated by your proxy; list its network in `ERMITE_TRUSTED_PROXIES` so that failed-login limits see the real client address (`X-Forwarded-For`). | 65 | Use the admin token (`ERMITE_ADMIN_TOKEN`) or a stored token as the password; the username is ignored. Store it in a Git credential helper rather than in the URL. Pushing to a new name creates a private repository. Repository names are lowercase, `[a-z0-9._-]`. TLS is terminated by your proxy; list its network in `ERMITE_TRUSTED_PROXIES` so that failed-login limits see the real client address (`X-Forwarded-For`). | ||
| 64 | 66 | ||||
| 67 | + | ## Branch-limited tokens | |||
| 68 | + | ||||
| 69 | + | For an AI agent that should propose changes, not publish them: `--branch-prefix agent/claude/` (repeatable, up to 8, each ending with `/`) limits a token to the branches under its prefixes. | |||
| 70 | + | ||||
| 71 | + | - It can push, force-push and delete `refs/heads/agent/claude/…`. Any other ref (`main`, other branches, tags, notes, anything outside `refs/heads/`) refuses the whole push, and the message names the ref. | |||
| 72 | + | - It cannot create a repository (push to a new name, or `repo create`) nor delete one, never has the rights of the owner (it cannot open web sessions even with every scope), and can only create tokens whose prefixes lie within its own. | |||
| 73 | + | - It opens, comments on and closes merge requests as usual; with `mr:merge` it can merge only into a branch under its prefixes, and delete only a source branch under them. You review and merge the agent's request. | |||
| 74 | + | - `ermite token list` shows a `branches` column (`all` when unlimited); `--json` has `branch_prefixes`. | |||
| 75 | + | - This build stores tokens in storage schema 2. Once it has started on your data, an older ermite refuses to start on it (it would read limited tokens as unrestricted): to go back, restore the backup taken before the upgrade. | |||
| 76 | + | ||||
| 65 | ## Merge requests | 77 | ## Merge requests | ||
| 66 | 78 | ||||
| 67 | A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch). | 79 | A merge request proposes a branch for its target (the default branch unless set). The merge is refused when the branch moved since the reviewed head (`--head`) or when the target is not an ancestor of it. Fast-forward moves the target; squash writes one commit signed by the server's SSH key (`ermite signing-key show` prints the `allowed_signers` line for `git verify-commit`). `ermite repo edit --merge-modes` limits the modes per repository. `--delete-branch` (a checkbox on the web) deletes the source branch after the merge, unless it is the default branch or another open request uses it. Tokens need `mr:write` to open and comment, and `mr:merge` to merge (and delete the merged branch). | ||
M skills/ermite/CLI.md +4 -2
| @@ -20,7 +20,7 @@ Repository JSON: `name`, `description`, `visibility`, `default_branch`, `created | |||
| 20 | 20 | ||
| 21 | 21 | | Command | Scope | Notes | | |
| 22 | 22 | |---|---|---| | |
| 23 | - | | `token create <label> [--preset cli\|agent\|ci] [--scope S] [--repo R] [--expires 30d\|2w\|12h\|2027-01-31] [--json]` | `token:admin` | secret printed once on standard output; there is no default scope | | |
| 23 | + | | `token create <label> [--preset cli\|agent\|ci] [--scope S] [--repo R] [--branch-prefix P] [--expires 30d\|2w\|12h\|2027-01-31] [--json]` | `token:admin` | secret printed once on standard output; there is no default scope | | |
| 24 | 24 | | `token list [--json]` | `token:admin` | secrets are never shown; the admin token is not listed | | |
| 25 | 25 | | `token revoke <id>` | `token:admin` | | | |
| 26 | 26 | ||
| @@ -44,7 +44,9 @@ Scopes: | |||
| 44 | 44 | | `token:admin` | create, list and revoke tokens | | |
| 45 | 45 | | `ci:write`, `secrets:write`, `runner:register` | reserved for CI | | |
| 46 | 46 | ||
| 47 | - | A token can never grant more than the token that creates it. `--repo` (repeatable) limits it to those repositories; the default is all. Labels are free text up to 100 bytes; `owner` and `root` are reserved. Token JSON: `id`, `label`, `scopes`, `repos`, `expires_at`, `created_at`, `last_used_at`. | |
| 47 | + | A token can never grant more than the token that creates it. `--repo` (repeatable) limits it to those repositories; the default is all. Labels are free text up to 100 bytes; `owner` and `root` are reserved. Token JSON: `id`, `label`, `scopes`, `repos`, `branch_prefixes`, `expires_at`, `created_at`, `last_used_at`. | |
| 48 | + | ||
| 49 | + | `--branch-prefix` (repeatable, up to 8, each ending with `/`, for example `agent/claude/`) limits the branches the token may write; the default is all. A limited token pushes, force-pushes and deletes only `refs/heads/<prefix>…`: any other ref (`main`, tags, notes) refuses the whole push, naming the ref. It cannot create or delete a repository, merges only into branches under its prefixes (and deletes only a source branch under them), never has owner rights, and creates only tokens within its own prefixes. A token for an agent: `ermite token create claude --preset agent --scope repo:read --repo <repo> --branch-prefix agent/claude/`. If you hold such a token, work on a branch under your prefix and open a merge request; do not try `main` or tags. | |
| 48 | 50 | ||
| 49 | 51 | ## Merge requests | |
| 50 | 52 | ||
| @@ -20,7 +20,7 @@ Repository JSON: `name`, `description`, `visibility`, `default_branch`, `created | |||||
| 20 | 20 | ||||
| 21 | | Command | Scope | Notes | | 21 | | Command | Scope | Notes | | ||
| 22 | |---|---|---| | 22 | |---|---|---| | ||
| 23 | - | | `token create <label> [--preset cli\|agent\|ci] [--scope S] [--repo R] [--expires 30d\|2w\|12h\|2027-01-31] [--json]` | `token:admin` | secret printed once on standard output; there is no default scope | | 23 | + | | `token create <label> [--preset cli\|agent\|ci] [--scope S] [--repo R] [--branch-prefix P] [--expires 30d\|2w\|12h\|2027-01-31] [--json]` | `token:admin` | secret printed once on standard output; there is no default scope | |
| 24 | | `token list [--json]` | `token:admin` | secrets are never shown; the admin token is not listed | | 24 | | `token list [--json]` | `token:admin` | secrets are never shown; the admin token is not listed | | ||
| 25 | | `token revoke <id>` | `token:admin` | | | 25 | | `token revoke <id>` | `token:admin` | | | ||
| 26 | 26 | ||||
| @@ -44,7 +44,9 @@ Scopes: | |||||
| 44 | | `token:admin` | create, list and revoke tokens | | 44 | | `token:admin` | create, list and revoke tokens | | ||
| 45 | | `ci:write`, `secrets:write`, `runner:register` | reserved for CI | | 45 | | `ci:write`, `secrets:write`, `runner:register` | reserved for CI | | ||
| 46 | 46 | ||||
| 47 | - | A token can never grant more than the token that creates it. `--repo` (repeatable) limits it to those repositories; the default is all. Labels are free text up to 100 bytes; `owner` and `root` are reserved. Token JSON: `id`, `label`, `scopes`, `repos`, `expires_at`, `created_at`, `last_used_at`. | 47 | + | A token can never grant more than the token that creates it. `--repo` (repeatable) limits it to those repositories; the default is all. Labels are free text up to 100 bytes; `owner` and `root` are reserved. Token JSON: `id`, `label`, `scopes`, `repos`, `branch_prefixes`, `expires_at`, `created_at`, `last_used_at`. |
| 48 | + | ||||
| 49 | + | `--branch-prefix` (repeatable, up to 8, each ending with `/`, for example `agent/claude/`) limits the branches the token may write; the default is all. A limited token pushes, force-pushes and deletes only `refs/heads/<prefix>…`: any other ref (`main`, tags, notes) refuses the whole push, naming the ref. It cannot create or delete a repository, merges only into branches under its prefixes (and deletes only a source branch under them), never has owner rights, and creates only tokens within its own prefixes. A token for an agent: `ermite token create claude --preset agent --scope repo:read --repo <repo> --branch-prefix agent/claude/`. If you hold such a token, work on a branch under your prefix and open a merge request; do not try `main` or tags. | |||
| 48 | 50 | ||||
| 49 | ## Merge requests | 51 | ## Merge requests | ||
| 50 | 52 | ||||