| 1 | --- |
| 2 | title: Guardrails |
| 3 | description: What g1t's agents may reach, run, spend and take in a project's sandboxes, and how each rule is enforced. |
| 4 | --- |
| 5 | |
| 6 | Guardrails decide what g1t's agents may do in their sandboxes: which hosts |
| 7 | a sandbox can reach, which commands the agent's harness refuses, and how |
| 8 | much one run may cost and how long it may take. A workspace sets defaults, |
| 9 | and each project can override them. |
| 10 | |
| 11 | They apply to every sandbox g1t starts for a project's agents (implement, |
| 12 | revise, answer, catch up, review, plan, and replies to mentions). The |
| 13 | sandboxes of its merge queue get the network list and the time cap; they |
| 14 | build the queue's states, not an agent's work, so command rules and the |
| 15 | cost cap do not apply to them. GitHub Actions jobs and deploy |
| 16 | builds get the network list too, with what builds need added (see |
| 17 | [builds](#builds)), and their own time limit. Merge checks are not covered; |
| 18 | see [What is not covered](#what-is-not-covered). Every sandbox, whatever |
| 19 | it runs, is watched for [mining](#abuse-and-mining). |
| 20 | |
| 21 | ## Where to set them |
| 22 | |
| 23 | - **Workspace defaults**: the workspace's **Settings**, **Guardrails**. |
| 24 | Owners can change them; members can read them. |
| 25 | - **A project's overrides**: the project's **Settings**, **Guardrails**. |
| 26 | People with the Admin [role](/guides/access-and-roles/) on |
| 27 | its repository can see and change them; the page is not shown to anyone |
| 28 | else. |
| 29 | |
| 30 | Every setting on a project's page starts as "As the workspace", which |
| 31 | follows the workspace's default, whatever it is now. Choose a value to |
| 32 | override it for that project only. Allowed domains, workflow-only domains |
| 33 | and deny patterns add up: a project's are added to the workspace's, never |
| 34 | instead of them. |
| 35 | |
| 36 | Changes apply to runs that start after you save. A run that is under way |
| 37 | keeps the guardrails it started with. |
| 38 | |
| 39 | ## Network |
| 40 | |
| 41 | With **Only allowed hosts** restricted (the default), a sandbox can reach: |
| 42 | |
| 43 | - g1t's own hosts, always: `g1t.sh`, `api.g1t.sh`, `models.g1t.sh` and |
| 44 | `mcp.g1t.sh`, for cloning, pushing, reporting and the model; |
| 45 | - the package registries that are on (all of them by default): |
| 46 | |
| 47 | | Registry | Hosts | |
| 48 | | --- | --- | |
| 49 | | npm and Yarn | `registry.npmjs.org`, `registry.yarnpkg.com`, `repo.yarnpkg.com` | |
| 50 | | PyPI | `pypi.org`, `files.pythonhosted.org` | |
| 51 | | crates.io and Rust toolchains | `crates.io`, `index.crates.io`, `static.crates.io`, `static.rust-lang.org` | |
| 52 | | Go module proxy | `proxy.golang.org`, `sum.golang.org` | |
| 53 | | GitHub downloads | `codeload.github.com`, `raw.githubusercontent.com`, `objects.githubusercontent.com` | |
| 54 | |
| 55 | - the domains you list: `api.stripe.com` allows exactly that host, and |
| 56 | `*.example.com` allows every subdomain of `example.com` (not |
| 57 | `example.com` itself; list both if you need both). |
| 58 | |
| 59 | `github.com` itself is not on the default list. A project with |
| 60 | dependencies fetched with git from GitHub, rather than as archives, needs it |
| 61 | listed. |
| 62 | |
| 63 | ### How it is enforced |
| 64 | |
| 65 | This is enforced outside the sandbox, by Cloudflare Containers' outbound |
| 66 | interception: |
| 67 | |
| 68 | - A restricted sandbox starts with no internet connection at all. Its DNS |
| 69 | resolves nothing on its own, and no protocol or port other than HTTP (80) |
| 70 | and HTTPS (443) has any route out: SSH, raw TCP and UDP connections fail. |
| 71 | - Every HTTP and HTTPS request it makes is handed to g1t's runner Worker |
| 72 | before it leaves. The Worker forwards requests to allowed hosts and |
| 73 | answers every other with `403` and a line saying the host is not allowed |
| 74 | and where to allow it. |
| 75 | - To see the host of an HTTPS request, the connection is re-encrypted with |
| 76 | a certificate the sandbox is given when it starts, which g1t adds to the |
| 77 | sandbox's trusted certificates (and points Node.js, Python, curl, Cargo |
| 78 | and git at). A program that brings its own fixed list of certificates and |
| 79 | ignores the system's cannot connect anywhere, allowed or not. |
| 80 | |
| 81 | Nothing running in the sandbox, root included, can change this: the rule |
| 82 | is applied where the sandbox's traffic leaves it, not inside it. |
| 83 | |
| 84 | Each refused host appears once as a step of the run, such as |
| 85 | `Blocked: example.com (not an allowed domain)`, on the run's page under |
| 86 | **Agents**. If a run needs a host, allow it and start the work again. |
| 87 | |
| 88 | Setting **Only allowed hosts** to Open gives that project's sandboxes the |
| 89 | whole internet, as before guardrails. |
| 90 | |
| 91 | ### Builds |
| 92 | |
| 93 | GitHub Actions jobs and deploy builds reach the project's list, plus what |
| 94 | real builds need, which no setting removes: |
| 95 | |
| 96 | - GitHub, where `uses:` actions, `actions/checkout`'s helpers and the |
| 97 | setup actions' downloads come from: `github.com`, `api.github.com`, |
| 98 | `codeload.github.com`, `objects.githubusercontent.com`, |
| 99 | `raw.githubusercontent.com`, `release-assets.githubusercontent.com`, |
| 100 | `ghcr.io`; |
| 101 | - toolchains: `nodejs.org`, `go.dev`, `dl.google.com`, |
| 102 | `static.rust-lang.org`, `sh.rustup.rs`; |
| 103 | - every package registry above, whatever the project turned on for its |
| 104 | agents, and RubyGems, Packagist, NuGet, Maven Central, Gradle and |
| 105 | Debian's mirrors; |
| 106 | - container registries, for a job's own Docker Engine: Docker Hub |
| 107 | (`registry-1.docker.io`, `auth.docker.io` and the CDNs its layers come |
| 108 | from), `mirror.gcr.io`, Quay (`quay.io` and its CDNs), and Docker's |
| 109 | package repository, `download.docker.com`; |
| 110 | - for deploy builds, Cloudflare's API, which the build uploads its app to. |
| 111 | |
| 112 | The repository itself is cloned from g1t, which is always reachable. A |
| 113 | project whose guardrails set **Only allowed hosts** to Open runs its jobs |
| 114 | and builds with an open network too. |
| 115 | |
| 116 | The containers a job starts with [Docker](/guides/actions/#docker), its |
| 117 | services and its build steps share the job's network, so this list is |
| 118 | theirs too: an image from another registry, or a build step that |
| 119 | downloads from another host, needs that host allowed, as a step would. |
| 120 | |
| 121 | ### Workflow-only domains |
| 122 | |
| 123 | Some hosts only a workflow should reach: the API a deploy uploads to, a |
| 124 | release server, a package registry you publish to. Listing them under |
| 125 | allowed domains would open them to agents as well. List them under |
| 126 | **Workflow-only domains** instead, one per line: |
| 127 | |
| 128 | ``` |
| 129 | api.cloudflare.com | deploy.yml | production |
| 130 | uploads.example.com | release.yml, nightly.yml |
| 131 | *.internal.example.com |
| 132 | ``` |
| 133 | |
| 134 | | Part | | |
| 135 | | --- | --- | |
| 136 | | The domain | As for allowed domains: `example.com`, or `*.example.com` for its subdomains. | |
| 137 | | Workflows | Workflow files by name, comma-separated, as they are in `.g1t/workflows/`. Left out: any workflow. | |
| 138 | | Environments | The environments a job must name with `environment:`, comma-separated. Left out: any job. | |
| 139 | |
| 140 | A domain is reached only by: |
| 141 | |
| 142 | - jobs of the workflows and environments its line names; |
| 143 | - in a run that is not of a pull request from a fork, which runs code |
| 144 | anyone could write. |
| 145 | |
| 146 | Agents, checks, the merge queue and deploy builds never reach these |
| 147 | hosts, whatever the line says. A job that names its environment with an |
| 148 | expression (`environment: ${{ inputs.target }}`) matches only lines with |
| 149 | no environments. |
| 150 | |
| 151 | Workflow-only domains are set by the same people as the rest of the page: |
| 152 | owners for the workspace's, Admin for a project's. Each change |
| 153 | is recorded in the workspace's [audit log](/guides/audit-log/) as |
| 154 | `update_guardrails`, saying which domains were added or removed and what |
| 155 | they were limited to. |
| 156 | |
| 157 | ## Commands |
| 158 | |
| 159 | The agent's harness checks every tool call the agent makes before it runs. |
| 160 | A refused call does not run; the agent is told it was refused and why, and |
| 161 | the run shows a step such as |
| 162 | `Denied: Ran git push --force origin feature (no force-pushing)`. |
| 163 | |
| 164 | Built-in rules, each on by default: |
| 165 | |
| 166 | | Rule | What it refuses | |
| 167 | | --- | --- | |
| 168 | | No force-pushing | `git push` with `--force`, `-f`, `--force-with-lease`, `--mirror`, `--delete` or `--prune`, a `+` refspec, or `:branch` to delete one. | |
| 169 | | No rewriting the default branch | Pushing to the default branch, `git branch -f/-d/-D/-m/-M` on it, `git update-ref` of it, and `git filter-branch`, `git filter-repo` and `git replace`. | |
| 170 | | No reading files outside the project | File tools (read, edit, write, search) outside the checked-out project, `/tmp`, and dependency caches (Cargo's registry and git checkouts, Go's module cache, Rust toolchains). Shell commands that touch g1t's own files in the sandbox, or other processes' environments under `/proc`. | |
| 171 | | No printing the environment | `env` and `printenv`, `export -p`, `set` and `declare -p` on their own, `compgen -e`, reading `/proc/*/environ`, and any command that reads a variable whose name contains `TOKEN`, `KEY`, `SECRET`, `PASSWORD`, `CREDENTIAL` or `AUTH`. | |
| 172 | | No sudo | `sudo`, `su`, `doas` and `pkexec`. With this on, the sandbox also gives up root before the agent starts, so nothing the agent runs can become root. | |
| 173 | |
| 174 | **Also refuse** adds your own rules, one per line, written as permission |
| 175 | rules: |
| 176 | |
| 177 | - `Bash(terraform apply:*)` refuses any shell command that starts with |
| 178 | those words, wherever it appears in a line (`cd infra && terraform |
| 179 | apply` too). `Bash(rm -rf *)` uses `*` as a wildcard; `Bash(make deploy)` |
| 180 | refuses exactly that command. |
| 181 | - `Read(secrets/**)`, `Edit(//etc/**)`: file paths. `//` starts at the |
| 182 | root, `~/` at the home directory, anything else at the project. `**` |
| 183 | crosses directories, `*` does not. `Read` covers reading and searching; |
| 184 | `Edit` covers every tool that writes a file. |
| 185 | - `WebFetch(domain:example.com)` refuses fetching that domain and its |
| 186 | subdomains. |
| 187 | - A tool's name on its own, such as `WebSearch`, refuses the tool. |
| 188 | - Plain text is the start of a shell command: `kubectl delete` is saved as |
| 189 | `Bash(kubectl delete:*)`. |
| 190 | |
| 191 | ### How it is enforced |
| 192 | |
| 193 | The rules are written into the harness's managed settings, which no |
| 194 | settings file in the project or the home directory can override, as a hook |
| 195 | the harness runs before every tool call and as its permission rules. With |
| 196 | **No sudo** on, the sandbox then gives up root, so the agent cannot edit |
| 197 | them or the program the hook runs. |
| 198 | |
| 199 | This is a guard against an agent's mistakes, not a sandbox against a |
| 200 | determined one. Shell commands are matched as text, and a command can |
| 201 | always be written in a way no rule foresees (built up from variables, or |
| 202 | run from a script the agent wrote). The hard boundaries are elsewhere: |
| 203 | |
| 204 | - The network list, enforced outside the sandbox. |
| 205 | - The sandbox's credentials. The agent itself holds none of g1t's: the |
| 206 | runner clones and pushes with credentials passed per command, and pushes |
| 207 | only to the run's own fork or branch, never with force. See |
| 208 | [credentials](/guides/working-with-g1t/#credentials). |
| 209 | - Branch protection on the repository, which g1t enforces when a push |
| 210 | arrives, whatever the sandbox did. |
| 211 | |
| 212 | ### Changes from forks |
| 213 | |
| 214 | When a run checks out a fork's head (revising, reviewing or answering on |
| 215 | any pull request from a fork, including g1t's own, and replying to a |
| 216 | mention on one), the harness loads nothing from that checkout: no |
| 217 | `CLAUDE.md`, no `.claude/settings.json` or `settings.local.json` (so none |
| 218 | of their hooks or permissions), no `.mcp.json` servers, and no commands or |
| 219 | skills. g1t's guardrails, its tools and the repository's instructions from |
| 220 | its own branches still apply. The run's session says so at the start. This |
| 221 | follows the rule g1t uses for |
| 222 | [repository instructions](/guides/working-with-g1t/#repository-instructions): |
| 223 | they are read from the repository's own branches, never from a fork. |
| 224 | |
| 225 | ## Caps |
| 226 | |
| 227 | **Cost per run**: the most one run may spend on its model, in US dollars. |
| 228 | $2.00 by default, the same as the workspace billing's spend cap per run; |
| 229 | 0 means no cap here, though billing's cap still applies; at most $100. The |
| 230 | harness tracks the run's spend as it goes and stops the agent when it |
| 231 | reaches the cap. |
| 232 | |
| 233 | **Time per run**: how long each kind of run may take, in minutes. By |
| 234 | default: |
| 235 | |
| 236 | | Kind of run | Minutes | |
| 237 | | --- | --- | |
| 238 | | Implement | 90 | |
| 239 | | Revise | 60 | |
| 240 | | Catch up | 45 | |
| 241 | | Review | 30 | |
| 242 | | Plan | 30 | |
| 243 | | Answer | 20 | |
| 244 | | Checks | 45 | |
| 245 | | Merge queue | 45 | |
| 246 | | Merge check | 10 | |
| 247 | |
| 248 | At most 240 minutes, but a run's credentials last two hours, so a longer |
| 249 | cap does not give an agent more than that to push. |
| 250 | |
| 251 | **The workspace's billing** sets caps too. Every workspace has a spend cap |
| 252 | per run, $2 by default, which owners can set from $0.10 to $100, so a run |
| 253 | stops at $2 unless an owner raises it (see |
| 254 | [caps](/guides/usage-and-billing/#caps)). A new paid workspace's first |
| 255 | month, and the trial, also cap every run's time at 60 minutes (see |
| 256 | [who can run agents](/guides/working-with-g1t/#who-can-run-agents)). A run gets |
| 257 | the lower of its guardrails' cap and its plan's, for time and for cost, |
| 258 | and its page shows the cap it got. |
| 259 | |
| 260 | A run that reaches a cap is stopped and marked **Stopped**, with "Stopped |
| 261 | at its cost cap" or "Stopped at its time cap" on its page. A pull request it |
| 262 | was working on is left open for you, as when a person stops a run: raise |
| 263 | the cap if it was too low, then ask for a review, a revision or a catch-up |
| 264 | to start again. A run of checks or the merge queue that reaches its time |
| 265 | cap fails, as a sandbox that stops early always has. |
| 266 | |
| 267 | The run's page shows its caps under **Guardrails**: the time so far against |
| 268 | its time cap, live, and its spend against its cost cap. Spend is reported |
| 269 | when the run ends (or stops at its cap), so while it runs the page shows |
| 270 | the cap, not a running total. |
| 271 | |
| 272 | ### How they are enforced |
| 273 | |
| 274 | - The cost cap is enforced by the harness, which counts the run's model |
| 275 | spend the same way it reports it for billing and stops the agent once |
| 276 | the spend reaches the cap. The step in flight when it does can take the |
| 277 | run a little past it. |
| 278 | - g1t's model proxy holds the run to the same cap, whatever happens in the |
| 279 | sandbox. It adds up what each of the run's model answers cost, and once |
| 280 | the run has spent its cap it refuses the run's model requests with |
| 281 | `402` and the error code `run_cap_reached`, which shows in the run's log. |
| 282 | The proxy also takes at most 16 of a run's model requests at a time, |
| 283 | and a run's model token reaches only `/v1/messages` (with |
| 284 | `/v1/messages/count_tokens`) and `/v1/models`. The token stops working |
| 285 | when the run ends. |
| 286 | - The time cap is enforced twice: the harness stops the agent when it |
| 287 | passes, and the sandbox itself is stopped three minutes after, whatever |
| 288 | is running in it. |
| 289 | |
| 290 | ## Abuse and mining |
| 291 | |
| 292 | g1t does not run cryptocurrency miners, on any plan. Mining needs a mining |
| 293 | pool and hours of CPU; g1t's sandboxes withhold the first and watch for the |
| 294 | second: |
| 295 | |
| 296 | - **No pool to reach.** No mining pool is on any allowed list, so a |
| 297 | restricted sandbox's miner has nowhere to send its work. |
| 298 | - **Miners by name.** A shell command an agent runs, a check, a build |
| 299 | command, a workflow step or a container a job starts whose image or |
| 300 | command names a known miner (`xmrig`, `cpuminer`, `t-rex` and others), a |
| 301 | pool address (`stratum+tcp://`) or a miner's flags (`--donate-level`, |
| 302 | `--algo=rx/0`) is refused, whatever the project's rules. A running process whose command line names one stops |
| 303 | the sandbox at once. |
| 304 | - **The CPU signature.** Every sandbox samples itself every 30 seconds: |
| 305 | CPU use, file and disk I/O, network bytes, new processes, and whether the |
| 306 | run did anything (a tool call, an agent step, a new check command or |
| 307 | workflow step). It is stopped when, for 10 minutes straight, CPU stays at |
| 308 | or above 90% (one dip allowed) while file and disk I/O average under |
| 309 | 64 KB a second, the network under 16 KB a second, fewer than five new |
| 310 | processes start, and nothing else happens. |
| 311 | |
| 312 | Compiling and testing are CPU-bound too, but they read sources, write |
| 313 | objects and start processes (a `cargo build` or `npm test` starts |
| 314 | hundreds), so they do not match. A compiler can spend minutes in code |
| 315 | generation with little I/O, so when the busiest process is a known |
| 316 | compiler or runtime (`rustc`, `cc1`, `clang`, `go`, `javac`, `node` and |
| 317 | others) the sandbox is not stopped for CPU alone. |
| 318 | |
| 319 | A sandbox stopped this way ends with "Stopped: unusual CPU use; contact |
| 320 | support if this was a real job." An agent run shows it under **Agents** |
| 321 | and leaves its pull request for you, a check or merge queue run fails with |
| 322 | it, a workflow job fails with it, and a deployment's status says it. g1t's |
| 323 | staff are told, with the measurements, and look at what ran. If it was a |
| 324 | real job, write to hey@flagon.io and say which run. |
| 325 | |
| 326 | ## What is not covered |
| 327 | |
| 328 | - **GitHub Actions jobs and deploy builds** get the network list and their |
| 329 | time limit, but no command rules or cost cap: they run commands from the |
| 330 | repository's workflows and build settings, not an agent. |
| 331 | - **Merge checks** only merge two commits; they are not given guardrails. |
| 332 | - The **commit history** an agent produces is reviewed like any other |
| 333 | change: guardrails limit what an agent can do while it works, not what |
| 334 | its change does once it is merged. |