| 1 | --- |
| 2 | title: Guardrails |
| 3 | description: Which hosts a project's jobs, checks, merge queue and deploy builds may reach on g1t's machines, how long a run may take, and how each rule is enforced. |
| 4 | --- |
| 5 | |
| 6 | Guardrails decide what a project's work may do in g1t's sandboxes: which |
| 7 | hosts a sandbox can reach, and how long a run may take. A workspace sets |
| 8 | defaults, and each project can override them. |
| 9 | |
| 10 | They apply to every sandbox g1t starts for a project: its Actions jobs, |
| 11 | its checks, its merge queue and its deploy builds. Jobs and deploy builds |
| 12 | get the network list with what builds need added (see [builds](#builds)), |
| 13 | and their own time limit. Merge checks are not covered; see |
| 14 | [what is not covered](#what-is-not-covered). Every sandbox, whatever it |
| 15 | runs, is watched for [mining](#abuse-and-mining). |
| 16 | |
| 17 | ## Where to set them |
| 18 | |
| 19 | - **Workspace defaults**: the workspace's **Settings**, **Guardrails**. |
| 20 | Owners can change them; members can read them. |
| 21 | - **A project's overrides**: the project's **Settings**, **Guardrails**. |
| 22 | People with the Admin [role](/guides/access-and-roles/) on |
| 23 | its repository can see and change them; the page is not shown to anyone |
| 24 | else. |
| 25 | |
| 26 | Every setting on a project's page starts as "As the workspace", which |
| 27 | follows the workspace's default, whatever it is now. Choose a value to |
| 28 | override it for that project only. Allowed domains and workflow-only |
| 29 | domains add up: a project's are added to the workspace's, never |
| 30 | instead of them. |
| 31 | |
| 32 | Changes apply to runs that start after you save. A run that is under way |
| 33 | keeps the guardrails it started with. |
| 34 | |
| 35 | ## Network |
| 36 | |
| 37 | With **Only allowed hosts** restricted (the default), a sandbox can reach: |
| 38 | |
| 39 | - g1t's own hosts, always: `g1t.sh`, `api.g1t.sh` and `mcp.g1t.sh`, for |
| 40 | cloning, pushing and reporting; |
| 41 | - the package registries that are on (all of them by default): |
| 42 | |
| 43 | | Registry | Hosts | |
| 44 | | --- | --- | |
| 45 | | npm and Yarn | `registry.npmjs.org`, `registry.yarnpkg.com`, `repo.yarnpkg.com` | |
| 46 | | PyPI | `pypi.org`, `files.pythonhosted.org` | |
| 47 | | crates.io and Rust toolchains | `crates.io`, `index.crates.io`, `static.crates.io`, `static.rust-lang.org` | |
| 48 | | Go module proxy | `proxy.golang.org`, `sum.golang.org` | |
| 49 | | GitHub downloads | `codeload.github.com`, `raw.githubusercontent.com`, `objects.githubusercontent.com` | |
| 50 | |
| 51 | - the domains you list: `api.stripe.com` allows exactly that host, and |
| 52 | `*.example.com` allows every subdomain of `example.com` (not |
| 53 | `example.com` itself; list both if you need both). |
| 54 | |
| 55 | `github.com` itself is not on the default list. A project with |
| 56 | dependencies fetched with git from GitHub, rather than as archives, needs it |
| 57 | listed. |
| 58 | |
| 59 | ### How it is enforced |
| 60 | |
| 61 | This is enforced outside the sandbox, by Cloudflare Containers' outbound |
| 62 | interception: |
| 63 | |
| 64 | - A restricted sandbox starts with no internet connection at all. Its DNS |
| 65 | resolves nothing on its own, and no protocol or port other than HTTP (80) |
| 66 | and HTTPS (443) has any route out: SSH, raw TCP and UDP connections fail. |
| 67 | - Every HTTP and HTTPS request it makes is handed to g1t's runner Worker |
| 68 | before it leaves. The Worker forwards requests to allowed hosts and |
| 69 | answers every other with `403`. The refusal names the host three times |
| 70 | over, since tools differ in what they show of a failed request: in its |
| 71 | status line (`403 Forbidden by g1t guardrails: example.com is not an |
| 72 | allowed domain`, which is what a tool that prints only the status shows), |
| 73 | in an `x-g1t-blocked-host` header beside `x-g1t-guardrails: blocked`, and |
| 74 | in a plain-text body saying where to allow it: **Allowed domains** for |
| 75 | every sandbox, or **Workflow-only domains** for the jobs of one workflow |
| 76 | and environment. |
| 77 | - To see the host of an HTTPS request, the connection is re-encrypted with |
| 78 | a certificate the sandbox is given when it starts, which g1t adds to the |
| 79 | sandbox's trusted certificates (and points Node.js, Python, curl, Cargo |
| 80 | and git at). A program that brings its own fixed list of certificates and |
| 81 | ignores the system's cannot connect anywhere, allowed or not. |
| 82 | |
| 83 | Nothing running in the sandbox, root included, can change this: the rule |
| 84 | is applied where the sandbox's traffic leaves it, not inside it. |
| 85 | |
| 86 | Each refused host appears once as a step of the run, such as |
| 87 | `Blocked: example.com (not an allowed domain)`. It is a note, not the |
| 88 | run's state: the run carries on. If a run needs a host, allow it and run |
| 89 | it again. |
| 90 | |
| 91 | Tools that send usage reports home are told not to in a sandbox with only |
| 92 | allowed hosts (`DO_NOT_TRACK=1`, and each common tool's own switch, such |
| 93 | as `WRANGLER_SEND_METRICS=false`), so those reports are not refused and |
| 94 | noted on every run. |
| 95 | |
| 96 | Setting **Only allowed hosts** to Open gives that project's sandboxes the |
| 97 | whole internet, as before guardrails. |
| 98 | |
| 99 | ### Builds |
| 100 | |
| 101 | GitHub Actions jobs and deploy builds reach the project's list, plus what |
| 102 | real builds need, which no setting removes: |
| 103 | |
| 104 | - GitHub, where `uses:` actions, `actions/checkout`'s helpers and the |
| 105 | setup actions' downloads come from: `github.com`, `api.github.com`, |
| 106 | `codeload.github.com`, `objects.githubusercontent.com`, |
| 107 | `raw.githubusercontent.com`, `release-assets.githubusercontent.com`, |
| 108 | `ghcr.io`; |
| 109 | - toolchains: `nodejs.org`, `go.dev`, `dl.google.com`, |
| 110 | `static.rust-lang.org`, `sh.rustup.rs`; |
| 111 | - every package registry above, whatever the project turned on, and |
| 112 | RubyGems, Packagist, NuGet, Maven Central, Gradle and |
| 113 | Debian's mirrors; |
| 114 | - container registries, for a job's own Docker Engine: Docker Hub |
| 115 | (`registry-1.docker.io`, `auth.docker.io` and the CDNs its layers come |
| 116 | from), `mirror.gcr.io`, Quay (`quay.io` and its CDNs), and Docker's |
| 117 | package repository, `download.docker.com`; |
| 118 | - for deploy builds, Cloudflare's API, which the build uploads its app to. |
| 119 | |
| 120 | The repository itself is cloned from g1t, which is always reachable. A |
| 121 | project whose guardrails set **Only allowed hosts** to Open runs its jobs |
| 122 | and builds with an open network too. |
| 123 | |
| 124 | The containers a job starts with [Docker](/guides/actions/#docker), its |
| 125 | services and its build steps share the job's network, so this list is |
| 126 | theirs too: an image from another registry, or a build step that |
| 127 | downloads from another host, needs that host allowed, as a step would. |
| 128 | |
| 129 | ### Workflow-only domains |
| 130 | |
| 131 | Some hosts only a workflow should reach: the API a deploy uploads to, a |
| 132 | release server, a package registry you publish to. Listing them under |
| 133 | allowed domains would open them to every sandbox as well. List them under |
| 134 | **Workflow-only domains** instead, one per line: |
| 135 | |
| 136 | ``` |
| 137 | api.cloudflare.com | deploy.yml | production |
| 138 | uploads.example.com | release.yml, nightly.yml |
| 139 | *.internal.example.com |
| 140 | ``` |
| 141 | |
| 142 | | Part | | |
| 143 | | --- | --- | |
| 144 | | The domain | As for allowed domains: `example.com`, or `*.example.com` for its subdomains. | |
| 145 | | Workflows | Workflow files by name, comma-separated, as they are in `.g1t/workflows/`. Left out: any workflow. | |
| 146 | | Environments | The environments a job must name with `environment:`, comma-separated. Left out: any job. | |
| 147 | |
| 148 | A domain is reached only by: |
| 149 | |
| 150 | - jobs of the workflows and environments its line names; |
| 151 | - in a run that is not of a pull request from a fork, which runs code |
| 152 | anyone could write. |
| 153 | |
| 154 | Checks, the merge queue and deploy builds never reach these hosts, whatever the line says. A job that names its environment with an |
| 155 | expression (`environment: ${{ inputs.target }}`) matches only lines with |
| 156 | no environments. |
| 157 | |
| 158 | Workflow-only domains are set by the same people as the rest of the page: |
| 159 | owners for the workspace's, Admin for a project's. Each change |
| 160 | is recorded in the workspace's [audit log](/guides/audit-log/) as |
| 161 | `update_guardrails`, saying which domains were added or removed and what |
| 162 | they were limited to. |
| 163 | |
| 164 | A deploy that signs in to AWS, Google Cloud or Azure with the job's |
| 165 | [OIDC token](/guides/actions/#oidc-tokens), rather than a key kept in a |
| 166 | secret, needs its cloud's token service listed here, such as |
| 167 | `sts.us-east-1.amazonaws.com | deploy.yml | production`. See |
| 168 | [reaching your cloud](/guides/actions/#reaching-your-cloud) for each |
| 169 | cloud's hosts. |
| 170 | |
| 171 | ### Brokered secrets |
| 172 | |
| 173 | The same interception keeps a workflow's API keys out of its jobs. A |
| 174 | [brokered secret](/guides/secrets-and-variables/#brokered-secrets) names |
| 175 | the hosts it is for, and a job is given a stand-in in place of its value. |
| 176 | As a request to one of those hosts passes the runner Worker, the Worker |
| 177 | puts the real value in place of the stand-in, in the request's headers |
| 178 | (inside `Authorization: Basic` too) and its query string, and swaps it |
| 179 | back should the answer echo it. A request to any other host goes with the |
| 180 | stand-in, and the job's log notes it once. The Worker asks for a job's |
| 181 | stand-ins with that job's own token and keeps them only while the job |
| 182 | runs. |
| 183 | |
| 184 | Brokering does not widen the network: a secret's hosts must still be ones |
| 185 | the job may reach. A job whose project's network is open, but which may be |
| 186 | given a brokered secret, still sends its HTTP and HTTPS through the |
| 187 | Worker, which reaches every host for it, and trusts the certificate as a |
| 188 | restricted sandbox does; its other protocols go straight out, as before. |
| 189 | Self-hosted runners' traffic does not pass through g1t, so a runner whose |
| 190 | jobs run on Linux or macOS sends the requests that carry a stand-in to g1t |
| 191 | itself, through a proxy it runs for the job, and g1t fills them in the same |
| 192 | way; see |
| 193 | [on a self-hosted runner](/guides/secrets-and-variables/#on-a-self-hosted-runner). |
| 194 | On Windows, brokered secrets stay stand-ins. |
| 195 | |
| 196 | ## Time per run |
| 197 | |
| 198 | How long each kind of run may take, in minutes. By default: |
| 199 | |
| 200 | | Kind of run | Minutes | |
| 201 | | --- | --- | |
| 202 | | Checks | 45 | |
| 203 | | Merge queue | 45 | |
| 204 | | Merge check | 10 | |
| 205 | |
| 206 | At most 240 minutes. A run that reaches its time cap fails, with |
| 207 | "Stopped: it reached its time cap of 45 minutes." The sandbox is stopped |
| 208 | whatever is running in it. Actions jobs keep their own limit: at most 60 |
| 209 | minutes on g1t's machines, or less with `timeout-minutes` (see |
| 210 | [Actions](/guides/actions/)). |
| 211 | |
| 212 | ## Abuse and mining |
| 213 | |
| 214 | g1t does not run cryptocurrency miners, on any plan. Mining needs a mining |
| 215 | pool and hours of CPU; g1t's sandboxes withhold the first and watch for the |
| 216 | second: |
| 217 | |
| 218 | - **No pool to reach.** No mining pool is on any allowed list, so a |
| 219 | restricted sandbox's miner has nowhere to send its work. |
| 220 | - **Miners by name.** A check, a build command, a workflow step or a container a job starts whose image or |
| 221 | command names a known miner (`xmrig`, `cpuminer`, `t-rex` and others), a |
| 222 | pool address (`stratum+tcp://`) or a miner's flags (`--donate-level`, |
| 223 | `--algo=rx/0`) is refused, whatever the project's rules. A running process whose command line names one stops |
| 224 | the sandbox at once. |
| 225 | - **The CPU signature.** Every sandbox samples itself every 30 seconds: |
| 226 | CPU use, file and disk I/O, network bytes, new processes, and whether the |
| 227 | run did anything (a new check command or workflow step). It is stopped when, for 10 minutes straight, CPU stays at |
| 228 | or above 90% (one dip allowed) while file and disk I/O average under |
| 229 | 64 KB a second, the network under 16 KB a second, fewer than five new |
| 230 | processes start, and nothing else happens. |
| 231 | |
| 232 | Compiling and testing are CPU-bound too, but they read sources, write |
| 233 | objects and start processes (a `cargo build` or `npm test` starts |
| 234 | hundreds), so they do not match. A compiler can spend minutes in code |
| 235 | generation with little I/O, so when the busiest process is a known |
| 236 | compiler or runtime (`rustc`, `cc1`, `clang`, `go`, `javac`, `node` and |
| 237 | others) the sandbox is not stopped for CPU alone. |
| 238 | |
| 239 | A sandbox stopped this way ends with "Stopped: unusual CPU use; contact |
| 240 | support if this was a real job." A check or merge queue run fails with |
| 241 | it, a workflow job fails with it, and a deployment's status says it. g1t's |
| 242 | staff are told, with the measurements, and look at what ran. If it was a |
| 243 | real job, write to hey@flagon.io and say which run. |
| 244 | |
| 245 | ## What is not covered |
| 246 | |
| 247 | - **Merge checks** only merge two commits; they get no network list. |
| 248 | - **Self-hosted runners** run on your machines, whose network g1t does not |
| 249 | see: the network list is not enforced there. See |
| 250 | [self-hosted runners](/guides/self-hosted-runners/#security). |