Files
coder/docs/ai-coder/agent-firewall/index.md
T
Nick Vigilante e458692cb8 refactor(docs): convert absolute coder/coder blob/tree/main links to relative (DOCS-351) (#26341)
Closes [DOCS-351](https://linear.app/codercom/issue/DOCS-351).

> [!WARNING]
> **DO NOT MERGE** until
[DOCS-349](https://linear.app/codercom/issue/DOCS-349)
([coder.com#877](https://github.com/coder/coder.com/pull/877)) has
shipped to production and baked for at least one Vercel cycle.
>
> Without DOCS-349, the relative links in this PR resolve to broken
docs-route URLs (`/docs/helm/coder/values.yaml` -> 404) instead of
GitHub URLs tagged with the displayed docs version. DOCS-349 fixes the
rewriter to classify these as GitHub blob/tree URLs with the page's
resolved ref.

## TL;DR

Converts 121 absolute
`https://github.com/coder/coder/(blob|tree)/main/<path>` links across 39
docs markdown files to relative paths. After this lands AND DOCS-349
deploys, every one of these links will follow the displayed docs version
(mainline tag on bare URLs, explicit tag on `/@vX.Y.Z/`, `main` on
`/@main/`) instead of always pointing to `main`.

## Why

Today a reader on `/docs/@v2.30.0/install/docker` follows a
`compose.yaml` link and arrives at `main`'s `compose.yaml`, which
doesn't necessarily match what the docs page describes. Helm values,
Terraform templates, and source-code references in particular drift
across versions. The fix is to let the coder.com rewriter substitute the
page's resolved ref into the URL; that only works on relative links.

## Example payoff (post-DOCS-349)

| URL | Today (absolute, always `main`) | After (relative + rewriter) |
|---|---|---|
| `/docs/install/docker` |
`https://github.com/coder/coder/blob/main/compose.yaml` |
`https://github.com/coder/coder/blob/v2.34.1/compose.yaml` (today's
mainline) |
| `/docs/@v2.30.0/install/docker` | same as above |
`https://github.com/coder/coder/blob/v2.30.0/compose.yaml` |
| `/docs/@main/install/docker` | same as above |
`https://github.com/coder/coder/blob/main/compose.yaml` |

## Scope

- **121 conversions** across **39 files**.
- Verb breakdown: `tree/main` (directories) and `blob/main` (files),
both flipped to relative paths.
- Line anchors (`#L23-L24`) and query strings preserved verbatim.
- Conversion is mechanical: relative path computed from the doc file's
directory to the target via `os.path.relpath`. Any path starting at the
same directory or below gets a `./` prefix; otherwise `../` chains.

## Rebased on main

The branch was rebased onto `main` after the DOCS-350 hotfix
([#26339](https://github.com/coder/coder/pull/26339)) merged. The hotfix
repointed 3 `docs-backend-contrib-guide` refs in `backend.md` to `main`,
which then needed the same `main` -> relative conversion this PR is
doing for the other 121 links. The conflict was resolved by reapplying
the mechanical conversion to `backend.md` after taking the hotfix's
content. Net result: those 3 links land here as relative, same as
everything else. New HEAD `3f501cb622`.

## Inline fix folded in: dead `nix` link

- `docs/about/contributing/CONTRIBUTING.md:7` -> `../../../nix`

The original absolute URL `https://github.com/coder/coder/tree/main/nix`
already returned 404 today. Repointed to `flake.nix` (modern Nix
entrypoint, what the prose "Nix environment" semantically refers to).
Closes [DOCS-357](https://linear.app/codercom/issue/DOCS-357) here since
the `check-docs` Linkspector job surfaced it during rebase; cheaper to
fix inline than in a separate single-line PR.

## Out of scope (filed separately)

- [DOCS-350](https://linear.app/codercom/issue/DOCS-350): 3 dead
`docs-backend-contrib-guide` branch refs in `backend.md`
([#26339](https://github.com/coder/coder/pull/26339), merged).
- [DOCS-352](https://linear.app/codercom/issue/DOCS-352): 10 SHA-pinned
`(blob|tree)/<sha>` links pending intent review.
- [DOCS-355](https://linear.app/codercom/issue/DOCS-355): code-server
analog (4 absolute `(blob|tree)/main` links in `coder/code-server`).
- [DOCS-356](https://linear.app/codercom/issue/DOCS-356): 2 upstream
content bugs in `coder/code-server/docs/CONTRIBUTING.md` (independent of
this PR).


## Not triggering `/coder-agents-review`

Docs-only edit; per `AGENTS.md` the bot review is reserved for
product/CI changes.

## Pre-mortem

| Concern | Mitigation |
|---|---|
| Merging before DOCS-349 deploys regresses ~120 currently-working links
into 404s on coder.com | Clear DO-NOT-MERGE banner; tracked as blocker
in Linear. |
| Relative path computed incorrectly (off-by-one `..`) | Verified all
114 newly-relative non-md/non-image paths resolve to existing files in
the repo (only exception is the pre-existing dead `nix` link above). |
| Line anchors stripped during conversion | Preserved by the
substitution regex; verified `#L<n>-L<m>` cases in `airgap.md` and
`speed-up-templates.md`. |
| Future code reorgs change file locations | Relative links will start
pointing to nothing. Same failure mode as absolute links pointing to
renamed files; can be caught with a future link-checker job. |

## Validation

```
$ grep -rE 'github\.com/coder/coder/(blob|tree)/main' docs --include="*.md" | wc -l
0
$ git diff --stat origin/main | tail -1
39 files changed, 118 insertions(+), 118 deletions(-)
```

114 newly-relative paths verified to resolve to existing repo files
(Python `os.path.exists` check on each computed target).

<details>
<summary>Decision log + planning context</summary>

**Why relative over `(blob|tree)/{{currentDocsVersion}}/...`
templating**: relative paths require zero markdown-system support and
zero upstream churn beyond this one PR. Templating would require a
preprocessor on `coder.com` side AND a convention upstream authors have
to remember; relative paths just work in a plain editor and
`github.com`'s own renderer too.

**Why `./` prefix on same-directory targets**: makes the conversion
grep-able later (`grep -E '\((\.\./|\./)'`).

**Why preserve `#L<n>-L<m>` anchors verbatim**: the anchor is meaningful
to the linked file's content, not to the URL form; keeping it as-is
preserves authorial intent. If the file later changes such that the line
range drifts, that's a different problem the SHA-pin audit
([DOCS-352](https://linear.app/codercom/issue/DOCS-352)) will surface.

</details>

---

*Generated by Coder Agents on @nickvigilante's behalf.*





## Drive-by external link fix folded in

`docs/about/contributing/CONTRIBUTING.md:296` cited
`https://reflectoring.io/meaningful-commit-messages/` which is returning
HTTP 503 (the host appears to be down site-wide right now). `check-docs`
Linkspector flagged it after the rebase. Replaced with
`https://cbea.ms/git-commit/` (Chris Beams' canonical "If applied, this
commit will..." article, confirmed 200), which is the original source of
the rule the prose recites anyway.
2026-06-22 11:39:12 -04:00

12 KiB

Agent Firewall

Agent Firewall is a process-level firewall that restricts and audits what autonomous programs, such as AI agents, can access and use.

Screenshot of Agent Firewall blocking a processExample of Agent Firewall blocking a process.

Note

Agent Firewall requires the AI Governance Add-On. As of Coder v2.32, deployments without the add-on will not be able to access Agent Firewall.

Agent Firewall was previously known as "Agent Boundaries". Some configuration options and internal references still use the old name and will be updated in a future release.

Supported Agents

Agent Firewall supports the securing of any terminal-based agent, including your own custom agents.

Features

Agent Firewall offers network policy enforcement, which blocks domains and HTTP verbs to prevent exfiltration, and writes logs to the workspace.

Agent Firewall also streams audit logs to Coder's control plane for centralized monitoring of HTTP requests.

Getting Started with Agent Firewall

The easiest way to use Agent Firewall is through the agent-firewall module. It can also be ran directly in the terminal by installing the CLI.

Configuration

Note

For information about version requirements and compatibility, see the Version Requirements documentation.

Agent Firewall is configured using a config.yaml file. This allows you to maintain allow lists and share detailed policies with teammates.

In your Terraform module, install Agent Firewall with minimal configuration:

module "agent-firewall" {
  source   = "registry.coder.com/coder/agent-firewall/coder"
  version  = "0.0.1"
  agent_id = coder_agent.main.id
}

To use a custom policy, pass it inline via agent_firewall_config, below is an example of minimal configuration for Claude Code module:

module "agent-firewall" {
  source   = "registry.coder.com/coder/agent-firewall/coder"
  version  = "0.0.1"
  agent_id = coder_agent.main.id

  agent_firewall_config = <<-YAML
    allowlist:
      - "domain=coder.example.com" # Required - use your Coder deployment domain
      - "domain=api.anthropic.com" # Required - API endpoint for Claude
      - "domain=statsig.anthropic.com" # Required - Feature flags and analytics
      - "domain=claude.ai" # Recommended - WebFetch/WebSearch features
      - "domain=*.sentry.io" # Recommended - Error tracking (helps Anthropic fix bugs)
    jail_type: nsjail
    log_dir: /tmp/boundary_logs
    proxy_port: 8087
    log_level: warn
  YAML
}

For examples of wrapping an agent or process such as Claude Code with Agent Firewall, see the agent-firewall module README.

For a basic recommendation of what to allow for agents, see the Anthropic documentation on default allowed domains. For a comprehensive example of a production Agent Firewall configuration, see the Coder dogfood policy example.

To load the policy from a config.yaml file in your template directory instead, pass it via agent_firewall_config. The module writes the config to the workspace and exposes the resolved path via agent_firewall_config_path, so everyone who launches Agent Firewall manually inside the workspace picks up the same configuration without extra flags. This is especially convenient for managing extensive allow lists in version control.

module "agent-firewall" {
  source   = "registry.coder.com/coder/agent-firewall/coder"
  version  = "0.0.1"
  agent_id = coder_agent.main.id

  agent_firewall_config = file("${path.module}/config.yaml")
}

Configuration Parameters

  • allowlist defines the URLs that the agent can access, in addition to the default URLs required for the agent to work. Rules use the format "key=value [key=value ...]":
    • domain=github.com - allows the domain and all its subdomains
    • domain=*.github.com - allows only subdomains (the specific domain is excluded)
    • method=GET,HEAD domain=api.github.com - allows specific HTTP methods for a domain
    • method=POST domain=api.example.com path=/users,/posts - allows specific methods, domain, and paths
    • path=/api/v1/*,/api/v2/* - allows specific URL paths
  • jail_type selects the isolation backend. Valid values: nsjail (default), landjail. See Jail Types for a detailed comparison.
  • log_dir defines where boundary writes log files.
  • log_level defines the verbosity at which requests are logged. Agent Firewall uses the following verbosity levels:
    • WARN: logs only requests that have been blocked by Agent Firewall
    • INFO: logs all requests at a high level
    • DEBUG: logs all requests in detail
  • no_user_namespace disables creation of a user namespace inside the jail. Enable this in restricted environments that disallow user namespaces, such as Bottlerocket nodes in EKS auto-mode. Only applies to the nsjail jail type.
  • proxy_port defines the port used by the HTTP proxy. Default: 8080.
  • use_real_dns uses the host's real DNS resolver inside the jail instead of the built-in dummy DNS server. This allows DNS resolution for non-proxied traffic but permits DNS-based data exfiltration. Default: false.

For detailed information about the rules engine and how to construct allowlist rules, see the rules engine documentation.

You can also run Agent Firewall directly in your workspace and configure it per template. You can do so by installing the binary into the workspace image or at start-up. You can do so with the following command:

curl -fsSL https://raw.githubusercontent.com/coder/boundary/main/install.sh | bash

When running the binary directly, Agent Firewall reads config.yaml from ~/.config/coder_boundary/ automatically.

Jail Types

Agent Firewall supports two different jail types for process isolation, each with different characteristics and requirements:

  1. nsjail - Uses Linux namespaces for isolation. This is the default jail type and provides network namespace isolation. See nsjail documentation for detailed information about runtime requirements and Docker configuration.

  2. landjail - Uses Landlock V4 for network isolation. This provides network isolation through the Landlock Linux Security Module (LSM) without requiring network namespace capabilities. See landjail documentation for implementation details.

The choice of jail type depends on your security requirements, available Linux capabilities, and runtime environment. Both nsjail and landjail provide network isolation, but they use different underlying mechanisms. nsjail uses Linux namespaces, while landjail uses Landlock V4. Landjail may be preferred in environments where namespace capabilities are limited or unavailable.

Implementation Comparison: Namespaces+iptables vs Landlock V4

Aspect Namespace Jail (Namespaces + veth-pair + iptables) Landlock V4 Jail
Privileges Requires CAP_NET_ADMIN No special capabilities required
Docker seccomp Requires seccomp profile modifications or sysbox-runc Works without seccomp changes
Kernel requirements Linux 3.8+ (widely available) Linux 6.7+ (very new, limited adoption)
Bypass resistance Strong - transparent interception prevents bypass Medium - can bypass by connecting to evil.com:<HTTP_PROXY_PORT>
Process isolation PID namespace (processes can't see/kill others); implementation in-progress No PID namespace (agent can kill other processes)
Non-TCP traffic control Can block/control UDP via iptables; implementation in-progress No control over UDP (data can leak via UDP)
Application compatibility Works with ANY application (transparent interception) Tools without HTTP_PROXY support will be blocked

Audit Logs

Agent Firewall streams audit logs to the Coder control plane, providing centralized visibility into HTTP requests made within workspaces—whether from AI agents or ad-hoc commands run with boundary.

Audit logs are independent of application logs:

  • Audit logs record Agent Firewall's policy decisions: whether each HTTP request was allowed or denied based on the allowlist rules. These are always sent to the control plane regardless of Agent Firewall's configured log level.
  • Application logs are Agent Firewall's operational logs written locally to the workspace. These include startup messages, internal errors, and debugging information controlled by the log_level setting.

For example, if a request to api.example.com is allowed by Agent Firewall but the remote server returns a 500 error, the audit log records decision=allow because Agent Firewall permitted the request. The HTTP response status is not tracked in audit logs.

Note

Requires Coder v2.30+ and Agent Firewall v0.5.2+.

Audit Log Contents

Each Agent Firewall audit log entry includes:

Field Description
decision Whether the request was allowed (allow) or blocked (deny)
workspace_id The UUID of the workspace where the request originated
workspace_name The name of the workspace where the request originated
owner The owner of the workspace where the request originated
template_id The UUID of the template that the workspace was created from
template_version_id The UUID of the template version used by the current workspace build
http_method The HTTP method used (GET, POST, PUT, DELETE, etc.)
http_url The fully qualified URL that was requested
event_time Timestamp when boundary processed the request (RFC3339 format)
matched_rule The allowlist rule that permitted the request (only present when decision is allow)

Viewing Audit Logs

Agent Firewall audit logs are emitted as structured log entries from the Coder server. You can collect and analyze these logs using any log aggregation system such as Grafana Loki.

Example of an allowed request (assuming stderr):

2026-01-16 00:11:40.564 [info]  coderd.agentrpc: boundary_request owner=joe  workspace_name=some-task-c88d agent_name=dev  decision=allow  workspace_id=f2bd4e9f-7e27-49fc-961e-be4d1c2aa987  http_method=GET http_url=https://coder.example.com  event_time=2026-01-16T00:11:39.388607657Z  matched_rule=domain=coder.example.com request_id=9f30d667-1fc9-47ba-b9e5-8eac46e0abef trace=478b2b45577307c4fd1bcfc64fad6ffb span=9ece4bc70c311edb