Skip to content
Closed
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
51 changes: 51 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
name: Bug report
about: Something isn't working as expected
title: ""
labels: bug
assignees: ""
---

<!--
Burnwall stores zero telemetry and is local-only, so we can't see your machine.
The single most useful thing you can attach is a redacted diagnostic bundle:

burnwall doctor --export

It is metadata-only (no prompts, no API keys, no raw paths) and self-scans for
secrets before writing — if anything secret-shaped survived, it refuses to write
rather than risk a leak. Review the file, then paste it below.
-->

## What happened

A clear description of the problem.

## What you expected

What you expected to happen instead.

## Steps to reproduce

1.
2.
3.

## Diagnostic bundle

Paste the output of `burnwall doctor --export` (it's redacted + self-scanned):

```
(paste here)
```

## Environment

- Burnwall version: <!-- `burnwall --version` -->
- OS / arch:
- AI tool(s) involved: <!-- Claude Code, Codex CLI, Aider, … -->

## Anything else

Logs, screenshots, or context. Please don't paste API keys or prompt content —
the `doctor --export` bundle already excludes them.
63 changes: 63 additions & 0 deletions .github/actions/burnwall-scan/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: 'Burnwall Scan'
description: >-
Scan agent config files (CLAUDE.md, .cursorrules, .mcp.json, .claude/ and
friends) for committed credentials and hidden-instruction smuggling, and
upload the findings as SARIF to the repository Security tab.
author: 'Burnwall'
branding:
icon: 'shield'
color: 'orange'

inputs:
paths:
description: 'Space-separated files or directories to scan.'
required: false
default: '.'
all-files:
description: 'Scan every text file in directories, not just known agent configs.'
required: false
default: 'false'
fail-on-findings:
description: 'Fail the job when anything is found (in addition to the SARIF upload).'
required: false
default: 'false'
upload-sarif:
description: >-
Upload the SARIF report to GitHub code scanning. Requires the
`security-events: write` permission on the job. Set to false to only
print findings (and optionally gate via fail-on-findings).
required: false
default: 'true'
burnwall-version:
description: 'Burnwall release to install (for example "0.9.15"). Defaults to the latest release.'
required: false
default: 'latest'

runs:
using: 'composite'
steps:
- name: Install Burnwall
shell: bash
run: |
if [ "${{ inputs.burnwall-version }}" != "latest" ]; then
export BURNWALL_VERSION="${{ inputs.burnwall-version }}"
fi
curl -fsSL https://raw.githubusercontent.com/intbot/burnwall/main/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"

- name: Scan agent configs
shell: bash
run: |
ARGS=""
if [ "${{ inputs.all-files }}" = "true" ]; then ARGS="$ARGS --all-files"; fi
if [ "${{ inputs.fail-on-findings }}" = "true" ]; then ARGS="$ARGS --fail-on-findings"; fi
# fail-on-findings exits non-zero AFTER writing the SARIF report, so
# the upload step still runs (`if: always()` below) and the Security
# tab gets the findings either way.
burnwall scan ${{ inputs.paths }} --sarif burnwall-scan.sarif $ARGS

- name: Upload SARIF to code scanning
if: ${{ always() && inputs.upload-sarif == 'true' }}
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: burnwall-scan.sarif
97 changes: 97 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,103 @@

All notable changes to Burnwall.

## [0.10.0] — 2026-06-12

A large release: a wave of security, cost, and compliance features, plus an
availability-hardening pass driven by dogfooding — so the proxy stays safe to run
hands-off even when something outside Burnwall (an antivirus, a crash) takes it down.

### Added

**Security**
- **Scan agent config files for committed secrets + hidden instructions.** `burnwall
scan <paths>` checks `CLAUDE.md` / `.cursorrules` / `.mcp.json` / `.claude/` and
friends for committed credentials and invisible-Unicode instruction smuggling, with
SARIF output. A one-line **GitHub Action** runs it in CI and posts findings to the
repository's Security tab.
- **Teach your agent about Burnwall.** `burnwall skills install` drops a guide where
Claude Code and Codex discover it, so the agent can read your spend, explain a block,
and run the file scanner — but never weaken protection itself.
- **Decode-then-scan + invisible-text scrubbing.** Obfuscated (base64/hex) and
zero-width-Unicode payloads inside tool calls are un-hidden before checking.
- **Canary trap.** Plant a fake credential; if it ever tries to leave the machine, the
request is blocked and a tamper-proof receipt is sealed.
- **Egress checks for file uploads and credential misdirection** (opt-in), a
**silent-billing watchdog** (warns when a session flips from subscription to metered),
and a **slow-drip exfiltration monitor** (warn-only).
- **Per-project MCP allowlist** — restrict which MCP servers an agent may reach, per repo.
- **Paranoid mode** (opt-in) — fail closed: block a request the scanner cannot inspect,
for users who prefer that over the fail-open default.
- **Image/link exfil warning** (opt-in, warn-only) — flags a model reply that embeds a
data-carrying image URL, the zero-click exfiltration pattern.

**Cost**
- **Per-repo / per-client cost export** to CSV, correct even when several projects run
at once.
- **`burnwall wire-check`** — compare your real on-the-wire spend with a log-scrape
estimate.
- **Cache-dead-zone warning**, an **hourly spend brake** (opt-in), and an optional
**cheaper-model fallback** when you hit a budget cap instead of stopping work.
- **Tool-output trim** (opt-in) — middle-truncate oversized tool results before they
re-enter context, with an in-band marker, to cut token cost.

**Compliance**
- **SPDX 3.0 AI-profile bill-of-materials** and framework-labelled evidence packs on top
of the existing CycloneDX AIBOM + SARIF exporters; a control crosswalk rides on blocks.

**Integration**
- **Sit in front of a gateway you already use.** A new `[upstreams]` config (and
`--upstream-*` flags) chains Burnwall ahead of any OpenAI- or Anthropic-compatible
gateway, keeping cross-tool spend tracking and enforcement on top.

**Resilience**
- **`burnwall recover`** — get unstuck if the proxy dies under you: pauses routing so new
shells go direct, and explains how to restore already-open tools.
- **`burnwall guard`** — a watchdog that auto-pauses routing if the proxy dies while
routed, so a crash or quarantine can't strand new shells.

**Diagnostics & data**
- **`burnwall doctor`** — a one-glance health check that names what's wrong and the exact
fix, with `burnwall doctor --export` writing a redacted, metadata-only bundle that
self-scans for secrets before it's written (and refuses to write if anything
secret-shaped survives) — the thing to attach to a bug report.
- **`burnwall explain <id>`** — explain any block in plain language: what rule fired, a
masked preview of what matched, why that class is blocked, and how to proceed.
- **`burnwall export --format csv|json`** — a portable copy of your metadata, on your
machine, any time.
- **Rule reference + troubleshooting docs.** Every block carries a stable rule id that
resolves to a `docs/RULES.md` entry (mirrored by `burnwall explain`), plus a
symptom→fix `docs/TROUBLESHOOTING.md` and a diagnostic-first bug-report template.

### Changed
- **Graceful drain on stop.** `burnwall stop` (and `upgrade`) now let in-flight requests
finish before exiting instead of cutting them mid-stream.
- **A crash, forced kill, or antivirus quarantine is now diagnosed.** `burnwall start`
notices an unclean prior exit and, on a streak, points at the likely cause (an
antivirus quarantining the unsigned binary) with the fix. Panics in background tasks
are now written to the log instead of vanishing silently.
- **Status-line block count** reads `🚫 N blocked` and no longer renders the digit on top
of the shield glyph in some terminals.
- **Status-line context reads true.** The context gauge no longer snaps toward ~100% off
a stale plan window — it shows the tool's own headroom figure (the one `/usage` reports)
and marks it stale rather than implying the conversation is nearly full.
- **Blocks and alerts are reported separately.** A warn-only security alert is no longer
counted as a block: `burnwall status` shows the two side by side, and the nudge line
reads "blocked N request(s)" versus "raised N security alert(s)" honestly.
- **Windows install note.** The README and the installer now explain the
Defender/SmartScreen false positive and how to recover from it.

### Fixed
- **Fewer false security blocks**, each locked with a regression test: a
credential-shaped string in resent conversation history (including a `/compact`
summary), an editor tool writing a key into a local test fixture, a search query that
mentions a sensitive path, and a tool's non-command metadata field no longer 403 —
while a genuine credential or dangerous command inside an actual tool call still blocks.
- **MCP watcher description-drift state is now per-watcher.** The advisory "a tool changed
its description" memory was process-global, so two watchers — or an ephemeral upstream
port reused by a different server — could leak sightings into each other (a flaky test
surfaced it). It's now scoped to each watcher instance; enforcement was never affected.

## [0.9.15] — 2026-06-10

A follow-up from live dogfooding: kill a false-positive class that could wedge a
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "burnwall"
version = "0.9.15"
version = "0.10.0"
edition = "2024"
rust-version = "1.87"
description = "Local proxy for AI coding tools (Claude Code, Codex CLI, Aider): cache-aware cost tracking, path/command security checks, daily budget enforcement. Zero telemetry."
Expand Down Expand Up @@ -47,7 +47,7 @@ tokio = { version = "1", features = ["full"] }

# HTTP proxy
hyper = { version = "1", features = ["full"] }
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "http1", "http2"] }
hyper-util = { version = "0.1", features = ["tokio", "server-auto", "server-graceful", "http1", "http2"] }
http-body-util = "0.1"
tower = { version = "0.5", features = ["util"] }

Expand Down Expand Up @@ -103,6 +103,7 @@ tempfile = "3"
wiremock = "0.6" # Mock HTTP server for integration tests
assert_cmd = "2" # CLI testing
predicates = "3" # Assertion helpers
dashmap = "6" # Construct WatchState literals in tests

[[test]]
name = "proxy_test"
Expand Down Expand Up @@ -206,6 +207,10 @@ path = "tests/unit/waste_test.rs"
name = "observe_test"
path = "tests/unit/observe_test.rs"

[[test]]
name = "cost_export_test"
path = "tests/unit/cost_export_test.rs"

[[test]]
name = "audit_cli_test"
path = "tests/integration/audit_cli_test.rs"
Expand Down
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,31 @@ sha256sum --ignore-missing -c sha256.sum

See [`SECURITY.md`](SECURITY.md) for the full integrity + TLS-handling statement.

### Windows: if Defender or SmartScreen flags it

The release binaries aren't code-signed yet, so Windows SmartScreen may show an
"unknown publisher" prompt on first run, and Defender's machine-learning
heuristic can occasionally flag the binary as a false positive — ironically,
partly *because* a local security proxy looks structurally like the things it
protects against. It's a false positive; the binary is the one built by this
repo's CI (verify it with the attestation command above).

If Defender quarantines the binary while the proxy is running, your AI tools may
start failing with `ConnectionRefused` (they're still pointed at the now-gone
local proxy). To recover:

```
burnwall recover # pauses routing so new terminals go direct, and tells you what to restart
```

To prevent re-quarantine, exclude Burnwall's directory in an elevated PowerShell:

```powershell
Add-MpPreference -ExclusionPath "$env:USERPROFILE\.burnwall"
```

You can report the false positive to Microsoft at <https://www.microsoft.com/wdsi>.

## How It Works

Burnwall runs as a local HTTP proxy. You point your AI tools at it via environment variables:
Expand Down Expand Up @@ -250,6 +275,10 @@ verifiable, not by asking for trust:
- **Zero telemetry.** No analytics, no phone-home, no tracking. Ever.
- **No prompt logging.** Only metadata is stored (model, tokens, cost, timestamp).
- **No API key storage.** Keys pass through in headers and are never written to disk.
- **Your data, portable.** All metadata lives in a single SQLite file under
`~/.burnwall` (`burnwall.db`). Back it up by copying that one file; export it
any time with `burnwall export --format csv|json`. See
[docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md).
- **Read-only on responses.** Burnwall inspects responses to compute cost and
**never modifies them** — your tool gets the provider's bytes unchanged.
- **Single binary, signed releases.** Install from a checksummed, signed release
Expand Down
6 changes: 6 additions & 0 deletions dist-workspace.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,9 @@ allow-dirty = ["ci"]
# `gh attestation verify <file> --repo intbot/burnwall`. No signing key to
# manage — a security tool should be exemplary about its own integrity.
github-attestations = true
# Code signing is NOT wired yet — unsigned releases can trip Windows Defender /
# SmartScreen and macOS Gatekeeper. The procurement + CI plan (Azure Artifact
# Signing for Windows, Apple Developer ID + notarization for macOS) lives in
# internal/SIGNING.md. Signing must integrate through cargo-dist's codesign hook
# so the SIGNED binary is the one hashed + attested — a post-hoc re-sign would
# break the published checksums and `gh attestation verify`.
Loading