diff --git a/.claude/skills/setup-gate-skill/SKILL.md b/.claude/skills/setup-gate-skill/SKILL.md index 09642a5..786dd87 100644 --- a/.claude/skills/setup-gate-skill/SKILL.md +++ b/.claude/skills/setup-gate-skill/SKILL.md @@ -17,4 +17,4 @@ npx skills@latest add mattpocock/skills/handoff ## Key context this repo is a software of Runtime enforcement for MCP agents: capability manifests, session taint tracking, and -a non-configurable rule-of-two trifecta invariant, with a hash-chained audit log. \ No newline at end of file +a non-configurable rule-of-two trifecta invariant, with a hash-chained audit log. diff --git a/.github/workflows/code-scan.yml b/.github/workflows/code-scan.yml new file mode 100644 index 0000000..e2f41c2 --- /dev/null +++ b/.github/workflows/code-scan.yml @@ -0,0 +1,28 @@ +name: Code Scan + +on: + pull_request: + branches: ["develop", "main"] + push: + branches: ["develop", "main"] + +permissions: + contents: read + +jobs: + code-scan: + name: Code Scan (CodeQL) + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - uses: actions/checkout@v4 + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: python + - name: Perform CodeQL analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:python" diff --git a/.github/workflows/dependency-scan.yml b/.github/workflows/dependency-scan.yml new file mode 100644 index 0000000..9088b8e --- /dev/null +++ b/.github/workflows/dependency-scan.yml @@ -0,0 +1,38 @@ +name: Dependency Scan + +on: + pull_request: + branches: ["develop", "main"] + push: + branches: ["develop", "main"] + +permissions: + contents: read + +jobs: + dependency-scan: + name: Dependency Scan + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + - name: Dependency Review + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: high + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + - name: Audit installed dependencies for known vulnerabilities + run: | + # Upgrade pip/setuptools first: the venv's bootstrap setuptools is + # old enough to carry its own CVEs, which would fail every run on + # build tooling rather than this project's actual dependencies. + pip install --quiet --upgrade pip setuptools + pip install --quiet pip-audit + pip install --quiet -e ".[dev,ops]" + pip-audit diff --git a/.github/workflows/secret-scan.yml b/.github/workflows/secret-scan.yml new file mode 100644 index 0000000..9411b0f --- /dev/null +++ b/.github/workflows/secret-scan.yml @@ -0,0 +1,32 @@ +name: Secret Scan + +on: + pull_request: + branches: ["develop", "main"] + push: + branches: ["develop", "main"] + +permissions: + contents: read + +jobs: + secret-scan: + name: Secret Leak Scan (Gitleaks) + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Run Gitleaks + # gitleaks/gitleaks-action requires a paid license for org-owned repos + # (bawbel is an org; the free tier is personal-account-only). The + # gitleaks CLI itself is fully open source, so run it directly. + # Version is pinned for reproducible CI runs; bump deliberately. + env: + GITLEAKS_VERSION: "8.30.1" + run: | + curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ + | tar -xz -C /usr/local/bin gitleaks + gitleaks detect --source . --log-opts="--all" --redact --config .gitleaks.toml --exit-code 1 diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..3e4ad35 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,10 @@ +title = "bawbel-gate gitleaks config" + +[extend] +useDefault = true + +[allowlist] +description = "Known-fake credentials used as test fixtures for policy/manifest.py's own secret-scan detector, not real leaked secrets" +paths = [ + '''tests/test_m2_policy\.py''', +] diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..59dc274 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,56 @@ +# Changelog + +All notable changes to this project are documented here. Format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows +[Semantic Versioning](https://semver.org/). + +No version has been tagged or released yet. The M0-M7 build (project scaffold +through policy engine, integrity pinning, operations layer, embedded console, +and bawbel-hub) is documented as milestones in README's "What is built" table +rather than itemized here; this file picks up from that baseline. + +## [Unreleased] + +### Added + +- MCP multiplexer request loop (`mux/proxy.py`): `bawbel-gate serve` and + `serve --console` previously did nothing -- the enforce/learn loop was a + stub. Now spawns upstream servers, merges/namespaces `tools/list`, routes + every `tools/call` through the policy engine, forwards on allow/approve, + denies without touching upstream, and writes the hash-chained audit trail. +- Real console dashboard (sessions/servers tables, pending-approvals panel, + live audit feed via SSE), replacing a bare JSON dump. +- Minimal browser UI for `bawbel-hub` (`/` route, `?t=` query-token auth), + per DESIGN.md 14.4. +- `GOVERNANCE.md` and `CODE_OF_CONDUCT.md`. +- Full PyPI packaging: complete metadata, trusted-publishing workflow + (auto to TestPyPI, manual-approval gate to production PyPI). +- CI security pipeline on every PR and push to `develop`/`main`: CodeQL + (code-scan.yml), Dependency Review + pip-audit (dependency-scan.yml), + Gitleaks (secret-scan.yml). Repo-level secret scanning, push protection, + and Dependabot security updates enabled. +- OpenSSF Scorecard workflow and badge. + +### Fixed + +- `bawbel-hub`'s `/v1/enroll` required the admin bearer token an unenrolled + gate doesn't have yet -- the single-use enrollment token is itself the + credential (DESIGN.md 14.3). The documented enroll flow was unreachable. +- `ConsoleServer` used a plain `http.server.HTTPServer` (one request at a + time), so a single open `/v1/events` SSE connection starved every other + request, including the page itself. Switched to `ThreadingHTTPServer`. +- `_schemas.py` located `schemas/` via a repo-root-relative path that only + resolves inside an editable/source checkout. A real `pip install + bawbel-gate` wheel would have raised `FileNotFoundError` on every + schema-dependent command. Schemas are now bundled into the wheel and + `_schemas.py` prefers the packaged copy. +- sdist `include` only adds to hatchling's default "everything not + gitignored" file set rather than replacing it, so untracked working-tree + files (`.agents/`, ~150K of unrelated content) were leaking into every + build. Switched to `only-include`, a strict allowlist. + +### Security + +- Documented (`SECURITY.md`, `.gitleaks.toml`) and enforced (branch + ruleset: required signed commits, required PR review) hygiene that was + previously either aspirational or entirely absent. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f7e61d8..84c7758 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,4 +116,4 @@ Do not open a public issue or PR that demonstrates a security bypass. See ## License By contributing, you agree your contribution is licensed under this project's -Apache-2.0 license (see [LICENSE](./LICENSE)). \ No newline at end of file +Apache-2.0 license (see [LICENSE](./LICENSE)). diff --git a/README.md b/README.md index 8c3c884..ba4f356 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@ [![Tests](https://img.shields.io/badge/tests-327%20passing-brightgreen)](#install-development) [![Fail-closed](https://img.shields.io/badge/fail--closed-enforced-critical)](./DESIGN.md) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/bawbel/gate/badge)](https://scorecard.dev/viewer/?uri=github.com/bawbel/gate) +[![Code Scan](https://github.com/bawbel/gate/actions/workflows/code-scan.yml/badge.svg?branch=develop)](https://github.com/bawbel/gate/actions/workflows/code-scan.yml) +[![Dependency Scan](https://github.com/bawbel/gate/actions/workflows/dependency-scan.yml/badge.svg?branch=develop)](https://github.com/bawbel/gate/actions/workflows/dependency-scan.yml) +[![Secret Scan](https://github.com/bawbel/gate/actions/workflows/secret-scan.yml/badge.svg?branch=develop)](https://github.com/bawbel/gate/actions/workflows/secret-scan.yml) [![Security Policy](https://img.shields.io/badge/security-policy-blue)](./SECURITY.md) [![Contributor Covenant](https://img.shields.io/badge/code_of_conduct-2.1-blueviolet)](./CODE_OF_CONDUCT.md) [![DCO](https://img.shields.io/badge/DCO-required-lightgrey)](./CONTRIBUTING.md#pull-requests) @@ -176,6 +179,28 @@ Point your MCP client at the gate instead of your servers directly: } ``` +`gate.yaml` holds the upstream server definitions plus one manifest path per server +(see DESIGN.md 3.1 for the full annotated version): + +```yaml +schema: bawbel/gate-config/v1 +audit_log: /var/log/bawbel/gate.audit.jsonl +approval: + channel: terminal # terminal | webhook | slack + webhook_url: null + timeout_seconds: 120 + on_timeout: deny +servers: + - name: github + command: github-mcp + args: [] + manifest: /etc/bawbel/manifests/github-mcp.cap.yaml + - name: filesystem + command: fs-mcp + args: ["--root", "/work"] + manifest: /etc/bawbel/manifests/fs-mcp.cap.yaml +``` + No manifests yet? Run learning mode first: ```bash @@ -291,6 +316,7 @@ Six event classes emitted as structured records regardless of exporter: - [IMPLEMENTATION_PLAN.md](./IMPLEMENTATION_PLAN.md) - milestone sequence. - [BAWBEL_GATE_MITIGATIONS_SPEC.md](./BAWBEL_GATE_MITIGATIONS_SPEC.md) - enforcement policy details. - [docs/LANGUAGE.md](./docs/LANGUAGE.md) - canonical terminology. +- [CHANGELOG.md](./CHANGELOG.md) - notable changes since the M0-M7 baseline. ## Related projects @@ -317,4 +343,4 @@ touches `policy/`, `taint/`, `audit/`, or `integrity/`: behavior that diverges f the spec needs a DESIGN.md change first, not a larger diff. Commits must carry a DCO sign-off (`git commit -s`); see [GOVERNANCE.md](./GOVERNANCE.md) for who decides what, and [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md) for how we treat -each other while doing it. \ No newline at end of file +each other while doing it. diff --git a/docs/TESTING.md b/docs/TESTING.md index 546be2a..c3f896e 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -512,4 +512,4 @@ pytest tests/replay/ # attack replays ``` A change that adds approval prompts on the benign corpus (`tests/corpus/`) is a -regression even if all tests pass. Check `test_m4_corpus.py::TestApprovalBudget`. \ No newline at end of file +regression even if all tests pass. Check `test_m4_corpus.py::TestApprovalBudget`.