IP allowlist
The IP allowlist limits your org to the networks you name. When the list is set, a request from an address outside it is refused, even if the credential is valid and the user signed in with SSO.
It lives at <your-org>/_admin/security/policies, under IP allowlist. Only org owners can change it.
Where the list applies
There is one list, and it applies to every surface below:
| Surface | What is refused from outside the list |
|---|---|
| Web admin | <your-org>/_admin/* |
| fremforge API | https://frem.sh/_app/api/v1/orgs/<your-org>/* |
| Repository web interface | The org’s pages and its private repositories, files, issues, pull requests, releases and attachments |
| Forgejo REST API | https://frem.sh/api/v1/ calls on the org’s private repositories, and on the org itself |
| Git over HTTPS | Clone, fetch and push |
| Git LFS | Object downloads and uploads, and file locks |
| Package registries | Every registry, including the container registry |
| Git over SSH | Nothing. See Git over SSH is not covered. |
Public repositories stay readable by everyone, because their content is public anyway. A member who is signed in to a public repository is still judged when they push, change settings or open the org’s pages. Anonymous visitors never see a refusal: a private repository looks the same to them as it always did.
The security page shows Where the list applies right now with one line per surface: Enforced, Logged only (with the reason) or Not covered. The API returns the same information in surfaces. When this page and that panel disagree, the panel is right.
Rollout. One list for every surface is being turned on gradually. Until your org is switched over, the panel can show a surface as Logged only — still being rolled out. Requests from outside the list are then recorded in the audit log but not refused. The web admin and Git over HTTPS are enforced throughout (for an older
web-scope list, only the web admin; see below).
Setting the list
- Use CIDR notation:
203.0.113.0/24,2001:db8::/48. A single address is/32, or/128for IPv6. - Put one range per line, or separate them with commas. The limit is 100 ranges.
/0is refused. To allow every address, leave the list empty.- Include your office and VPN ranges, and the egress addresses of any CI that does not run on fremforge.
- The page shows Your address right now, which is the address fremforge sees for you.
You cannot lock yourself out by saving. If an enforcing list does not contain your own address, the save is refused: either add your address, or tick Audit-only preview first.
A change takes effect within about a minute on every surface.
Audit-only preview
With Audit-only preview on, every surface records the requests it would refuse but lets them through. A good way to introduce a list:
- Enter the ranges and tick Audit-only preview. Save.
- Use the org as usual for a day or two, including CI and any integrations.
- Review the
ip_allowlist.would_blockevents in the audit log. Each one names the surface, the address, a sample path and how many requests it stands for. - Add any legitimate ranges you missed.
- Untick Audit-only preview. The list is enforced from now on.
fremforge-hosted runners
CI jobs on fremforge’s own runners reach your org from fremforge’s network, not from yours. The token minted for a job always works. Allow fremforge-hosted runners (on by default) decides whether other credentials used from a runner, such as a PAT or an OAuth token kept as a secret, work too. Turn it off if you want a stolen PAT to be useless even when it is used from inside a CI job.
Self-hosted runners and external CI are judged by their egress address like any other client. Add those addresses to the list.
If an owner is locked out
Your address can change: a new office, a VPN outage, travel. Break-glass switches every surface to logging only for 24 hours. Nothing is bypassed for good, and every step is recorded in the audit log.
- Owner self-service. Open
https://frem.sh/_app/ip-allowlist/break-glass?org=<your-org>from any network, signed in as an owner. We email a signed link to your account’s own email address. The link works for 30 minutes. Open it and confirm. Refusal pages and Git error messages for your org print this address. - fremverk support can turn on the same break-glass for your org on request.
Break-glass ends by itself after 24 hours and is never extended while it is on. Fix the list, then end it sooner with End break-glass now on the security page.
Audit-log actions: ip_allowlist.break_glass.activated, ip_allowlist.break_glass.ended.
Lists created before one list covered every surface
Older lists had a scope: web (web admin only) or web+git (plus Git over HTTPS). Those lists keep enforcing where they enforced before. On the other surfaces they log would-be refusals for 30 days after we notify the org’s owners, and then enforce there too. The security page shows the date.
To skip the wait, use Enforce on every surface now (audit-log action security.ip_allowlist.enforce_all). You cannot move a list back to a legacy scope, and every new list covers every surface.
Git over SSH is not covered
The IP allowlist does not filter Git over SSH. SSH reaches fremforge through a load balancer that replaces the client’s address with one of our own cluster nodes, so the address your developer connects from is not visible to us. We do not judge a connection by an address we know is wrong. A list that silently blocked every SSH user, or silently let them all through while claiming to filter, would be worse than saying so.
If your org needs every Git operation to be filtered by IP:
- Turn off Allow SSH protocol on the Authentication policy page. This is the default for orgs created after 2026-05-22.
- Have members use HTTPS with Git Credential Manager (Secure sign-in). Git over HTTPS is covered by the list.
Turning SSH off blocks SSH pushes. SSH clone and fetch cannot be blocked yet. The security page lists Git over SSH as Not covered, and the API returns ssh_covered: false.
What a refused request sees
| Surface | Response |
|---|---|
| fremforge API | 403 not-in-allowlist with the address we saw |
| Web interface, web admin | A refusal page naming the org and the address, with the break-glass link |
| Forgejo REST API, LFS, packages | 403 with the same message |
| Git over HTTPS | git prints the message and the break-glass link |
Each refusal is recorded as ip_allowlist.blocked, and each logged-only request as ip_allowlist.would_block. Repeats from the same address on the same surface are grouped into one event with a count, so a misconfigured CI loop does not flood the log.
API
The list is also available through the fremforge API, using the policy:read and policy:write scopes.
# Read: the list, the runner toggle, per-surface modes, break-glass state
curl -H "Authorization: Bearer $TOKEN" \
https://frem.sh/_app/api/v1/orgs/<your-org>/security
# Replace the list
curl -X PUT -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"cidrs":["203.0.113.0/24","2001:db8::/48"],"audit_only":true,"allow_platform_runners":true}' \
https://frem.sh/_app/api/v1/orgs/<your-org>/security/ip-policyThe ip_allowlist object contains cidrs, scope, audit_only, allow_platform_runners, surfaces (per surface: mode is enforce or audit, plus a reason), new_surfaces_enforce_at, break_glass_until and ssh_covered.
On the API, the lockout guard applies to the caller’s own address. Errors: invalid-cidr for a malformed range, invalid-allowlist for more than 100 ranges, a /0 range or a list that would block the caller, and invalid-scope for a legacy scope.
Limits
- If fremforge’s Git service cannot read the org policies for more than 24 hours, it stops enforcing the list rather than refuse every org. The fremforge API and the web admin are not affected. Such an outage is a platform incident that we act on; it is not expected in normal operation.
- Addresses are judged as fremforge’s edge sees them. A proxy or NAT in front of your users decides which address that is, so list the proxy’s egress range.
- At most 100 ranges.