diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..ccb5d6e --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,38 @@ +name: Deploy Pages + +on: + push: + branches: + - main + workflow_dispatch: + +concurrency: + group: pages-${{ github.repository }} + cancel-in-progress: true + +jobs: + deploy: + if: github.repository == 'AxeForging/aigate' + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: web + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/web/axeforge-icon.png b/web/axeforge-icon.png new file mode 100644 index 0000000..da16248 Binary files /dev/null and b/web/axeforge-icon.png differ diff --git a/web/axeforge.css b/web/axeforge.css new file mode 100644 index 0000000..2c8b958 --- /dev/null +++ b/web/axeforge.css @@ -0,0 +1,242 @@ +/* ============================================================ + AxeForge Brand Kit v1 — https://tools.axeforge.io + Copy this file into your project. See BRAND.md for the spec. + + Works two ways from this one file: + - Tailwind v4: `@import "tailwindcss"; @import "./axeforge.css";` + (the @theme inline block maps tokens to axe-* utilities) + - Plain CSS / no Tailwind: just link it; browsers ignore @theme. + + Flavors (set on ): forge (default) | circuit | flux | relay | signal + Theme (set on ): dark (default, omit) | light — data-theme="light" + ============================================================ */ + +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=optional'); +/* display=optional (not swap): the browser decides once at layout time and + never swaps mid-session — kills the web-font-swap layout shift. Trade-off: + on a slow first load a visitor may get the fallback font for that session + instead of Inter/JetBrains Mono, since it never swaps in later. */ + +/* ---- Shell tokens: identical in every flavor except relay. Never override + otherwise -- relay's neutral-gray shell is a deliberate, one-time exception + (see the "new flavor discussion" note below), not a precedent for others. ---- */ +:root { + color-scheme: dark; + + --af-bg: #05070a; + --af-surface: #0a0f1a; + --af-surface-2: #0d1117; /* terminal body */ + --af-surface-3: #161b22; /* terminal header */ + --af-line: #1a2233; + --af-text: #ffffff; + --af-text-muted: #94a3b8; + + --af-font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; + --af-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace; + + --af-radius: 0.5rem; + --af-radius-lg: 1rem; + + /* ---- Flavor tokens: the ONLY vars a flavor changes. Default = forge. ---- */ + --af-accent: #ff4e00; + --af-accent-hover: #e64600; + --af-accent-contrast: #ffffff; /* text placed ON the accent; white passes only on forge, bold/large */ +} + +[data-flavor="circuit"] { + --af-accent: #22d3ee; + --af-accent-hover: #06b6d4; + --af-accent-contrast: #05070a; +} + +[data-flavor="flux"] { + --af-accent: #a78bfa; + --af-accent-hover: #8b5cf6; + --af-accent-contrast: #05070a; +} + +/* relay: the one flavor that also overrides shell tokens (bg/surface/-2/-3/line), + not just the 3 accent vars -- "external tools/hardware/products" (Heyra, rsvp-m5, + ReplayRaccoon), inheriting ReplayRaccoon's neutral-gray shell instead of the + default blue-black. This is the "new flavor discussion" the Do/Don't section + below refers to, made real -- a deliberate one-time exception, not a precedent. */ +[data-flavor="relay"] { + --af-bg: #0a0a0a; + --af-surface: #1a1a1a; + --af-surface-2: #161616; /* terminal body */ + --af-surface-3: #202020; /* terminal header */ + --af-line: #2a2a2a; + + --af-accent: #ff6b00; + --af-accent-hover: #e56000; + --af-accent-contrast: #05070a; +} + +[data-flavor="signal"] { + --af-accent: #ffb000; + --af-accent-hover: #e69e00; + --af-accent-contrast: #05070a; +} + +/* ---- Light theme: opt-in via data-theme="light" on , alongside + data-flavor. Omit data-theme for dark (the default) — existing tools + that never set it are unaffected. Every accent below is the SAME hue as + its dark-mode counterpart, darkened only as far as needed to clear + 4.5:1 against the light shell bg (computed, not eyeballed) — verified + >=4.5:1 as button-fill-with-white-text at any size too, so light mode + drops dark mode's forge-only "bold/large" caveat entirely. + + --af-surface-2/-3 (terminal body/header) are deliberately NOT overridden + here: the terminal stays a dark console on a light page — a common + convention (docs sites, code blocks) that also sidesteps re-deriving + accent-as-text contrast for a component that's a fixed dark box in + every flavor already. ---- */ +[data-theme="light"] { + color-scheme: light; + --af-bg: #f7f8fa; + --af-surface: #ffffff; + --af-line: #dde1e6; + --af-text: #0a0f1a; + --af-text-muted: #475569; /* 7.13:1 on --af-bg */ +} + +[data-theme="light"][data-flavor="forge"] { + --af-accent: #cf3f00; /* 4.54:1 on --af-bg, 4.81:1 vs white button text */ + --af-accent-hover: #b63700; + --af-accent-contrast: #ffffff; +} + +[data-theme="light"][data-flavor="circuit"] { + --af-accent: #0b7c8d; /* 4.60:1 on --af-bg, 4.90:1 vs white button text */ + --af-accent-hover: #0a6d7c; + --af-accent-contrast: #ffffff; +} + +[data-theme="light"][data-flavor="flux"] { + --af-accent: #7a4ff7; /* 4.60:1 on --af-bg, 4.90:1 vs white button text */ + --af-accent-hover: #5e2af5; + --af-accent-contrast: #ffffff; +} + +/* relay-light: the neutral-gray shell exception carried into light mode too + — same reasoning as dark relay (matches ReplayRaccoon's own neutral + palette rather than the kit's blue-tinted default), so relay reads as + one consistent identity across both themes instead of only diverging + from the pack in dark mode. */ +[data-theme="light"][data-flavor="relay"] { + --af-bg: #f5f5f5; + --af-line: #e2e2e2; + --af-text: #0a0a0a; + --af-text-muted: #525252; /* 7.17:1 on --af-bg */ + --af-accent: #ba4e00; /* 4.61:1 on --af-bg, 6.12:1 hover vs white button text */ + --af-accent-hover: #a44500; + --af-accent-contrast: #ffffff; +} + +[data-theme="light"][data-flavor="signal"] { + --af-accent: #966800; /* 4.62:1 on --af-bg, 4.91:1 vs white button text */ + --af-accent-hover: #845c00; + --af-accent-contrast: #ffffff; +} + +/* ---- Tailwind v4 adapter. Inert (ignored at-rule) without Tailwind. + Must be `inline`: utilities then emit var(--af-*) directly, so a + [data-flavor] override below :root still wins. Plain @theme would + resolve the var() once at :root and pin every flavor to forge. ---- */ +@theme inline { + --font-sans: var(--af-font-sans); + --font-mono: var(--af-font-mono); + + --color-axe-black: var(--af-bg); + --color-axe-navy: var(--af-surface); + --color-axe-line: var(--af-line); + --color-axe-muted: var(--af-text-muted); + --color-axe-accent: var(--af-accent); + --color-axe-accent-hover: var(--af-accent-hover); + --color-axe-accent-contrast: var(--af-accent-contrast); + + /* deprecated — old hub names; use axe-accent / axe-accent-hover */ + --color-axe-orange: var(--af-accent); + --color-axe-orange-hover: var(--af-accent-hover); +} + +/* ---- Base ---- */ +body { + background: var(--af-bg); + color: var(--af-text); + font-family: var(--af-font-sans); + -webkit-font-smoothing: antialiased; +} + +:focus-visible { + outline: 2px solid var(--af-accent); + outline-offset: 2px; +} + +/* ---- Components (vanilla CSS — no Tailwind required) ---- */ + +.technical-grid { + background-size: 80px 80px; + background-image: radial-gradient(circle, var(--af-line) 1px, transparent 1px); +} + +.glass-card { + background: color-mix(in srgb, var(--af-surface) 60%, transparent); + -webkit-backdrop-filter: blur(24px); + backdrop-filter: blur(24px); + border: 1px solid var(--af-line); + border-radius: var(--af-radius-lg); + transition: border-color 0.5s ease, box-shadow 0.5s ease; +} +.glass-card:hover { + border-color: color-mix(in srgb, var(--af-accent) 50%, transparent); +} + +.terminal-window { + background: var(--af-surface-2); + border: 1px solid var(--af-line); + border-radius: var(--af-radius); + overflow: hidden; + font-family: var(--af-font-mono); + font-size: 0.75rem; + line-height: 1.5; + box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.5); +} +.terminal-header { + background: var(--af-surface-3); + padding: 0.5rem 1rem; + display: flex; + align-items: center; + gap: 0.5rem; + border-bottom: 1px solid var(--af-line); +} + +/* macOS traffic lights — semantic, not brand; fixed hexes on purpose */ +.dot { width: 10px; height: 10px; border-radius: 9999px; } +.dot-red { background: #ff5f56; } +.dot-yellow { background: #ffbd2e; } +.dot-green { background: #27c93f; } + +.glow-accent, +.glow-orange /* deprecated alias */ { + box-shadow: 0 0 20px color-mix(in srgb, var(--af-accent) 15%, transparent); +} + +.axe-button { + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: var(--af-accent); + color: var(--af-accent-contrast); /* never hardcode white — fails on circuit/flux/signal */ + padding: 0.75rem 1.5rem; + border: 0; + border-radius: var(--af-radius); + font-family: var(--af-font-sans); + font-size: 1.25rem; /* WCAG "large text" floor (18.66px+ bold) — forge's white-on-accent (3.31:1) only clears AA at large-text's 3:1, not body text's 4.5:1 */ + font-weight: 700; + cursor: pointer; + text-decoration: none; + transition: background 0.15s ease, transform 0.15s ease; +} +.axe-button:hover { background: var(--af-accent-hover); } +.axe-button:active { transform: scale(0.95); } diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..51e1c34 Binary files /dev/null and b/web/favicon.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..651ea4d --- /dev/null +++ b/web/index.html @@ -0,0 +1,462 @@ + + + + + +aigate — OS-level sandbox for AI coding agents + + + + + + + + + + + + + + + + + + +
+
+ + + AxeForge/aigate + + +
+
+ +
+
+
+
OS-level sandbox for AI coding agents
+

A trust boundary the AI can't argue its way out of.

+

+ Claude Code, Cursor, Copilot, and Aider all rely on application-level + permission systems (ignore files, allow/deny lists) that can be + bypassed. aigate moves that boundary into the OS kernel: POSIX + ACLs, Linux namespaces, and egress allowlists physically block what the + agent can read, execute, and reach on the network, regardless of what + the model decides to try. +

+ +

The most-starred tool in the AxeForge portfolio. See it on GitHub.

+
+ +
+
Live demo (simulated output, not a real sandbox)
+
+
+ + aigate run -- claude +
+
+
[aigate] sandbox active
+
[aigate] deny_read: .env, secrets/, ~/.ssh/, *.pem
+
[aigate] deny_exec: curl, wget, ssh, scp
+
[aigate] allow_net: api.anthropic.com, api.github.com
+
+
+
+
+
+
+ +
+
+
+
How it works
+

The kernel is the trust boundary, not the app

+

Four enforcement layers, each backed by an OS primitive the sandboxed process cannot negotiate with.

+
+
+
+

File isolation

+

Persistent POSIX ACLs (setfacl) on Linux, extended ACLs (chmod +a) on macOS, deny read access at rest. At run time, mount namespaces overmount denied directories with empty tmpfs and files with /dev/null: two layers of defense-in-depth.

+
+
+

Filesystem scope

+

allowed_paths hides everything in $HOME except the working directory and paths you allow. Non-scoped files are invisible: reads fail, listings don't leak names, writes land in a discarded overlay.

+
+
+

Network isolation

+

Bubblewrap's --unshare-net plus slirp4netns and iptables/ip6tables restrict egress to your allow_net allowlist on Linux; Seatbelt network-outbound rules do the same on macOS.

+
+
+

Process isolation

+

User and PID namespaces (--unshare-user --unshare-pid) mean the sandboxed process can't see or signal anything on the host, and needs no real root to set up mounts and networking.

+
+
+
+
+ +
+
+
+
Cross-platform
+

Real isolation on every supported OS

+

aigate prefers bwrap when it's installed and falls back cleanly when it isn't; run aigate doctor to see which mode is active.

+
+
+ + + + + + +
LayerLinux (bwrap)Linux (unshare fallback)macOS
FileDeclarative bind-mount overridesShell-script mount overridesSeatbelt file-read* deny rules
Network--unshare-net + slirp4netns + iptablesNested unshare + slirp4netns + iptablessandbox-exec network-outbound rules
Process--unshare-user --unshare-pid, sandboxed PID 1unshare --user --map-root-userEnforced by Sandbox.kext
Command execmount --bind deny stubs + arg-checking wrappersSame bind-mount stubs(deny process-exec) rules
+
+
+
+ +
+
+
+
Commands
+

From zero to sandboxed in five commands

+
+
+
+
01
+

setup

+

One-time, needs sudo. Creates the ai-agents OS group and ai-runner user that ACL rules attach to.

+
+
+
02
+

init

+

Writes ~/.aigate/config.yaml with sensible defaults: .env, secrets/, ~/.ssh/, and friends, denied out of the box.

+
+
+
03
+

deny / allow

+

Add or remove file, exec, and network rules, globally or per-project, via .aigate.yaml, which extends the global config.

+
+
+
04
+

run

+

Launches any AI tool inside the sandbox and prints exactly which restrictions are active to stderr before it starts.

+
+
+
05
+

doctor

+

Checks bwrap/slirp4netns/namespaces and prints a READY/DEGRADED/BLOCKED verdict, and exits non-zero so CI can gate on it.

+
+
+
+
+ +
+
+
+
Use cases
+

Why people reach for aigate

+
+
+
+

Regulated environments

+

GDPR/ISO/security teams need a trust boundary that isn't "the model chose not to read the file." ACLs and namespaces don't have that failure mode.

+ sudo aigate setup && aigate init +
+
+

Stop credential exfiltration

+

Deny read on .env, secrets/, and SSH keys, then mask API keys and tokens that still make it into stdout.

+ aigate deny read .env secrets/ *.pem +
+
+

Lock down egress

+

An agent that can only resolve the hosts you named can't phone home to anywhere else, curl an attacker's server, or exfiltrate over DNS.

+ aigate deny net --except api.anthropic.com +
+
+

Gate it in CI

+

doctor exits non-zero when full isolation isn't available, so a pipeline can refuse to run an agent unsandboxed.

+ aigate doctor || exit 1 +
+
+
+
+ +
+
+
+
Install
+

Three commands to a sandboxed agent

+
+
+
+ +
+

Install the binary

+
curl -L https://github.com/AxeForging/aigate/releases/latest/download/aigate-linux-amd64.tar.gz | tar xz
+sudo mv aigate-linux-amd64 /usr/local/bin/aigate
+
+
+
+ +

One-time system setup: sudo aigate setup && aigate init

+
+
+ +

Run any AI tool sandboxed: aigate run -- claude

+
+
+ Full instructions on GitHub +
+
+ + + + + + diff --git a/web/og-image.png b/web/og-image.png new file mode 100644 index 0000000..dcbf841 Binary files /dev/null and b/web/og-image.png differ diff --git a/web/robots.txt b/web/robots.txt new file mode 100644 index 0000000..6ecf3b0 --- /dev/null +++ b/web/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://axeforging.github.io/aigate/sitemap.xml diff --git a/web/sitemap.xml b/web/sitemap.xml new file mode 100644 index 0000000..7d77a38 --- /dev/null +++ b/web/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://axeforging.github.io/aigate/ + monthly + 1.0 + +