Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 39 additions & 39 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -349,15 +349,15 @@ jobs:
--type spdxjson \
ghcr.io/${{ github.repository }}/cli-proxy@${{ steps.build_cli_proxy.outputs.digest }}

# Build the minimal probe sandbox and trusted broker from separate Dockerfile
# Build the minimal query sandbox and trusted broker from separate Dockerfile
# targets. The runtime pulls both before the offline broker starts.
build-sealed-probe:
name: Build Sealed Probe Image
build-bounded-query:
name: Build Bounded Query Image
runs-on: ubuntu-latest
needs: bump-version
outputs:
probe_digest: ${{ steps.build_sealed_probe.outputs.digest }}
broker_digest: ${{ steps.build_sealed_probe_broker.outputs.digest }}
query_digest: ${{ steps.build_bounded_query.outputs.digest }}
broker_digest: ${{ steps.build_bounded_query_broker.outputs.digest }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
Expand All @@ -382,71 +382,71 @@ jobs:
- name: Install cosign
uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0

- name: Build and push Sealed Probe image
id: build_sealed_probe
- name: Build and push Bounded Query image
id: build_bounded_query
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: ./containers/sealed-probe
target: probe
context: ./containers/bounded-query
target: query
push: true
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/${{ github.repository }}/sealed-probe:${{ needs.bump-version.outputs.version_number }}
ghcr.io/${{ github.repository }}/sealed-probe:latest
cache-from: type=gha,scope=sealed-probe
cache-to: type=gha,mode=max,scope=sealed-probe
ghcr.io/${{ github.repository }}/bounded-query:${{ needs.bump-version.outputs.version_number }}
ghcr.io/${{ github.repository }}/bounded-query:latest
cache-from: type=gha,scope=bounded-query
cache-to: type=gha,mode=max,scope=bounded-query

- name: Sign Sealed Probe image with cosign
- name: Sign Bounded Query image with cosign
run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}/sealed-probe@${{ steps.build_sealed_probe.outputs.digest }}
ghcr.io/${{ github.repository }}/bounded-query@${{ steps.build_bounded_query.outputs.digest }}

- name: Generate SBOM for Sealed Probe image
- name: Generate SBOM for Bounded Query image
uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
with:
image: ghcr.io/${{ github.repository }}/sealed-probe@${{ steps.build_sealed_probe.outputs.digest }}
image: ghcr.io/${{ github.repository }}/bounded-query@${{ steps.build_bounded_query.outputs.digest }}
format: spdx-json
output-file: sealed-probe-sbom.spdx.json
output-file: bounded-query-sbom.spdx.json

- name: Attest SBOM for Sealed Probe image
- name: Attest SBOM for Bounded Query image
run: |
cosign attest --yes \
--predicate sealed-probe-sbom.spdx.json \
--predicate bounded-query-sbom.spdx.json \
--type spdxjson \
ghcr.io/${{ github.repository }}/sealed-probe@${{ steps.build_sealed_probe.outputs.digest }}
ghcr.io/${{ github.repository }}/bounded-query@${{ steps.build_bounded_query.outputs.digest }}

- name: Build and push Sealed Probe Broker image
id: build_sealed_probe_broker
- name: Build and push Bounded Query Broker image
id: build_bounded_query_broker
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: ./containers/sealed-probe
context: ./containers/bounded-query
target: broker
push: true
platforms: linux/amd64,linux/arm64
tags: |
ghcr.io/${{ github.repository }}/sealed-probe-broker:${{ needs.bump-version.outputs.version_number }}
ghcr.io/${{ github.repository }}/sealed-probe-broker:latest
cache-from: type=gha,scope=sealed-probe-broker
cache-to: type=gha,mode=max,scope=sealed-probe-broker
ghcr.io/${{ github.repository }}/bounded-query-broker:${{ needs.bump-version.outputs.version_number }}
ghcr.io/${{ github.repository }}/bounded-query-broker:latest
cache-from: type=gha,scope=bounded-query-broker
cache-to: type=gha,mode=max,scope=bounded-query-broker

- name: Sign Sealed Probe Broker image with cosign
- name: Sign Bounded Query Broker image with cosign
run: |
cosign sign --yes \
ghcr.io/${{ github.repository }}/sealed-probe-broker@${{ steps.build_sealed_probe_broker.outputs.digest }}
ghcr.io/${{ github.repository }}/bounded-query-broker@${{ steps.build_bounded_query_broker.outputs.digest }}

- name: Generate SBOM for Sealed Probe Broker image
- name: Generate SBOM for Bounded Query Broker image
uses: anchore/sbom-action@28d71544de8eaf1b958d335707167c5f783590ad # v0.22.2
with:
image: ghcr.io/${{ github.repository }}/sealed-probe-broker@${{ steps.build_sealed_probe_broker.outputs.digest }}
image: ghcr.io/${{ github.repository }}/bounded-query-broker@${{ steps.build_bounded_query_broker.outputs.digest }}
format: spdx-json
output-file: sealed-probe-broker-sbom.spdx.json
output-file: bounded-query-broker-sbom.spdx.json

- name: Attest SBOM for Sealed Probe Broker image
- name: Attest SBOM for Bounded Query Broker image
run: |
cosign attest --yes \
--predicate sealed-probe-broker-sbom.spdx.json \
--predicate bounded-query-broker-sbom.spdx.json \
--type spdxjson \
ghcr.io/${{ github.repository }}/sealed-probe-broker@${{ steps.build_sealed_probe_broker.outputs.digest }}
ghcr.io/${{ github.repository }}/bounded-query-broker@${{ steps.build_bounded_query_broker.outputs.digest }}

# Build agent-act image with catthehacker/ubuntu:act-24.04 base for GitHub Actions parity
# amd64-only: catthehacker/ubuntu:act-24.04 does not publish arm64 manifests
Expand Down Expand Up @@ -684,7 +684,7 @@ jobs:
release:
name: Create Release
runs-on: ubuntu-latest
needs: [bump-version, build-squid, build-agent, build-api-proxy, build-cli-proxy, build-agent-act, build-build-tools, build-sealed-probe, build-gh-aw-node]
needs: [bump-version, build-squid, build-agent, build-api-proxy, build-cli-proxy, build-agent-act, build-build-tools, build-bounded-query, build-gh-aw-node]
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
Expand Down Expand Up @@ -784,8 +784,8 @@ jobs:
"ghcr.io/${{ github.repository }}/agent-act@${{ needs['build-agent-act'].outputs.digest }}" \
"ghcr.io/${{ github.repository }}/api-proxy@${{ needs['build-api-proxy'].outputs.digest }}" \
"ghcr.io/${{ github.repository }}/cli-proxy@${{ needs['build-cli-proxy'].outputs.digest }}" \
"ghcr.io/${{ github.repository }}/sealed-probe@${{ needs['build-sealed-probe'].outputs.probe_digest }}" \
"ghcr.io/${{ github.repository }}/sealed-probe-broker@${{ needs['build-sealed-probe'].outputs.broker_digest }}" \
"ghcr.io/${{ github.repository }}/bounded-query@${{ needs['build-bounded-query'].outputs.query_digest }}" \
"ghcr.io/${{ github.repository }}/bounded-query-broker@${{ needs['build-bounded-query'].outputs.broker_digest }}" \
"ghcr.io/${{ github.repository }}/gh-aw-node@${{ needs['build-gh-aw-node'].outputs.digest }}" \
> release/containers.txt
echo "Generated containers.txt:"
Expand Down
18 changes: 9 additions & 9 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ The system is orchestrated by `src/cli.ts` and managed by `src/docker-manager.ts
- Agent calls the sidecar with no auth (e.g., `http://172.30.0.30:10001` for Anthropic); sidecar injects the real key and forwards via Squid
- Ports: 10000 (OpenAI), 10001 (Anthropic), 10002 (Copilot), 10003 (Gemini) — these are discrete ports, not a contiguous range

**4. Sealed-Probe Broker (optional)** — `containers/sealed-probe/`, no network
- Enabled via `sealedProbes.enabled` in the AWF config file (config-only; there is no CLI flag family)
**4. Bounded-Query Broker (optional)** — `containers/bounded-query/`, no network
- Enabled via `boundedQueries.enabled` in the AWF config file (config-only; there is no CLI flag family)
- The only AWF service with `network_mode: none`: no `awf-net`, no external bridge, no DNS, no Squid, no host gateway
- Reachable only through one Unix socket in `<workDir>/sealed-probes/run/`, bind-mounted into the agent at `/run/awf-sealed-probe/broker.sock`
- Receives the resolved Docker socket so it can launch per-invocation probe containers; that path never enters the agent's env or volumes
- The same image is used for the probe sandbox, which guarantees the probe image is already local (the broker cannot pull — it has no network)
- Probes run `python3` with `--network none`, `--read-only`, non-root, `--cap-drop ALL`, `no-new-privileges`, a seccomp profile, and time/memory/CPU/PID/file-size bounds
- Agent surface: `/usr/local/bin/sealed-probe` (from `containers/agent/sealed-probe-wrapper.sh`) plus a generated read-only `SKILL.md`; the wrapper always prints one canonical JSON line, writes nothing to stderr, and exits `0`
- Trusted host staging (`src/sealed-probe/staging.ts`) materializes an immutable seed per configured repo *before* the agent starts, using `GH_TOKEN`/`GITHUB_TOKEN` only in a child-process env — never in argv, a URL, a log, or the compose file
- See [docs/awf-config-spec.md](docs/awf-config-spec.md) §14 for the full model, including the two-bit disclosure bound and residual channels
- Reachable only through one Unix socket in `<workDir>/bounded-queries/run/`, bind-mounted into the agent at `/run/awf-bounded-query/broker.sock`
- Receives the resolved Docker socket so it can launch per-invocation query containers; that path never enters the agent's env or volumes
- The broker (`bounded-query-broker`) and query sandbox (`bounded-query`) are separate published images; a one-shot networkless Compose service pulls the sandbox image before broker startup so the broker (which has no network) can launch query containers
- Queries run `python3` with `--network none`, `--read-only`, non-root, `--cap-drop ALL`, `no-new-privileges`, a seccomp profile, and time/memory/CPU/PID/file-size bounds
- Agent surface: `bounded-query` command at `/tmp/awf-lib/bounded-query` (inside chroot, added to PATH by `entrypoint.sh`; the source file in the container is `/usr/local/bin/bounded-query-wrapper.sh`) plus a generated read-only `SKILL.md`; the wrapper always prints one canonical JSON line, writes nothing to stderr, and exits `0`
- Trusted host staging (`src/bounded-query/staging.ts`) materializes an immutable seed per configured repo *before* the agent starts, using `GH_TOKEN`/`GITHUB_TOKEN` only in a child-process env — never in argv, a URL, a log, or the compose file
- See [docs/awf-config-spec.md](docs/awf-config-spec.md) §14 for the full model, including per-repository information-budget accounting and residual channels

### Documentation Files

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ See [GitHub Actions](docs/github_actions.md) for advanced setup and `awf logs su
- [Usage guide](docs/usage.md) — CLI flags, domain allowlists, examples
- [AWF config schema](docs/awf-config.schema.json) — machine-readable JSON Schema for JSON/YAML configs (also published as a [versioned release asset](https://github.com/github/gh-aw-firewall/releases/latest/download/awf-config.schema.json) for IDE autocomplete)
- [AWF config spec](docs/awf-config-spec.md) — normative processing and precedence rules for tooling/compiler integration
- [Bounded queries](docs/bounded-queries.md) — run information-budgeted queries against private repositories without exposing their contents
- [Audit log schema](schemas/audit.schema.json) — JSON Schema for L7 traffic audit records (`audit.jsonl`)
- [Token usage schema](schemas/token-usage.schema.json) — JSON Schema for per-call token usage records (`token-usage.jsonl`)
- [Schemas README](schemas/README.md) — versioning policy, record identification, and validation examples
Expand Down
6 changes: 3 additions & 3 deletions containers/agent/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -273,15 +273,15 @@ RUN if ! getent group awfuser >/dev/null 2>&1; then \

# Copy iptables setup script, PID logger, API proxy health check, Claude key helper,
# gh CLI proxy wrapper (used when --enable-cli-proxy is active), and the
# sealed-probe wrapper (installed as `sealed-probe` when sealed probes are enabled)
# bounded-query wrapper (installed as `bounded-query` when bounded queries are enabled)
COPY setup-iptables.sh /usr/local/bin/setup-iptables.sh
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
COPY pid-logger.sh /usr/local/bin/pid-logger.sh
COPY api-proxy-health-check.sh /usr/local/bin/api-proxy-health-check.sh
COPY get-claude-key.sh /usr/local/bin/get-claude-key.sh
COPY gh-cli-proxy-wrapper.sh /usr/local/bin/gh-cli-proxy-wrapper.sh
COPY sealed-probe-wrapper.sh /usr/local/bin/sealed-probe-wrapper.sh
RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh /usr/local/bin/api-proxy-health-check.sh /usr/local/bin/get-claude-key.sh /usr/local/bin/gh-cli-proxy-wrapper.sh /usr/local/bin/sealed-probe-wrapper.sh
COPY bounded-query-wrapper.sh /usr/local/bin/bounded-query-wrapper.sh
RUN chmod +x /usr/local/bin/setup-iptables.sh /usr/local/bin/entrypoint.sh /usr/local/bin/pid-logger.sh /usr/local/bin/api-proxy-health-check.sh /usr/local/bin/get-claude-key.sh /usr/local/bin/gh-cli-proxy-wrapper.sh /usr/local/bin/bounded-query-wrapper.sh

# Copy pre-built one-shot-token library from rust-builder stage
# This prevents tokens from being read multiple times (e.g., by malicious code)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
#!/bin/sh
# /usr/local/bin/sealed-probe
# /usr/local/bin/bounded-query
#
# Agent-facing sealed-probe CLI (protocol v2).
# Agent-facing bounded-query CLI (protocol v2).
#
# Forwards a *narrow* request to the trusted sealed-probe broker over a
# Forwards a *narrow* request to the trusted bounded-query broker over a
# dedicated Unix socket. It is analogous to gh-cli-proxy-wrapper.sh, but the
# API is deliberately far narrower: this wrapper cannot express a command, an
# image, a path, a URL, a ref, a mount, a runtime, a timeout, an environment,
# or a credential. It accepts exactly:
#
# --repo owner/repo (exactly once)
# --schema '<json>' (exactly once; a finite response schema, see
# src/sealed-probe/protocol.ts)
# the probe script on stdin
# src/bounded-query/protocol.ts)
# the query script on stdin
#
# Output contract: exactly one line of canonical JSON on stdout, nothing on
# stderr, and exit status 0 — for every outcome and for every failure.
Expand All @@ -29,10 +29,10 @@
# image).

CANONICAL_ERROR='{"status":"error"}'
SOCKET="${AWF_SEALED_PROBE_SOCKET:-/run/awf-sealed-probe/broker.sock}"
SOCKET="${AWF_BOUNDED_QUERY_SOCKET:-/run/awf-bounded-query/broker.sock}"
PROTOCOL_VERSION=2
# Keep in sync with MAX_SCHEMA_BYTES in src/sealed-probe/protocol.ts and
# containers/sealed-probe/broker/protocol.js.
# Keep in sync with MAX_SCHEMA_BYTES in src/bounded-query/protocol.ts and
# containers/bounded-query/broker/protocol.js.
MAX_SCHEMA_BYTES=4096

emit_error() {
Expand Down Expand Up @@ -101,11 +101,11 @@ RESPONSE=$(
-X POST \
-H "Expect:" \
-H "Content-Type: application/octet-stream" \
-H "X-AWF-Probe-Version: ${PROTOCOL_VERSION}" \
-H "X-AWF-Query-Version: ${PROTOCOL_VERSION}" \
-H "X-AWF-Repo: ${REPO}" \
-H "X-AWF-Schema-B64: ${SCHEMA_B64}" \
--data-binary @- \
"http://localhost/probe" 2>/dev/null
"http://localhost/query" 2>/dev/null
) || emit_error

# Pass the broker's canonical response through unmodified, but only if it has
Expand Down
Loading
Loading