Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
92a2f32
docs(plans): finalize Orthrus write-mode spec after two Supervisor re…
Wikid82 Jul 20, 2026
eb173d5
test(orthrus): add write-mode E2E specs as test.fixme
Wikid82 Jul 20, 2026
a4be39e
feat(orthrus): add write-mode plumbing with zero behavior change
Wikid82 Jul 20, 2026
8311999
feat(orthrus): implement opt-in write-endpoint allowlist, body valida…
Wikid82 Jul 20, 2026
9326215
feat(orthrus): extend PATCH/proxy-status API for write mode, wire aud…
Wikid82 Jul 20, 2026
30cf1c0
feat(orthrus): add write-mode UI, audit-log deep-linking, i18n
Wikid82 Jul 20, 2026
d2fa315
docs(orthrus): enable write-mode E2E specs, rewrite absolute read-onl…
Wikid82 Jul 20, 2026
6fe7a80
test(orthrus): add muzzle corpus cases exposing agent/backend normali…
Wikid82 Jul 20, 2026
1e05ed0
refactor(orthrus): extract normalizeDockerPath helper in backend muzz…
Wikid82 Jul 20, 2026
043f9e5
fix(orthrus): align agent muzzle normalization order with backend (#1…
Wikid82 Jul 20, 2026
0ba40d1
feat(orthrus): add structural allowlist parity checker (#1161)
Wikid82 Jul 20, 2026
40d3ac2
feat(orthrus): add CI-enforced lint, staticcheck, and coverage gates …
Wikid82 Jul 20, 2026
9b9ebd0
docs(orthrus): document agent CI parity and muzzle normalization fix
Wikid82 Jul 20, 2026
6562a64
docs(orthrus): commit muzzle parity spec referenced by CI tooling
Wikid82 Jul 20, 2026
260c5ee
docs(qa): record muzzle allowlist parity QA audit (#1160, #1161)
Wikid82 Jul 20, 2026
9e4f83c
Merge branch 'development' into feature/orthrus
Wikid82 Jul 20, 2026
21954ce
fix(orthrus): resolve patch-coverage baseline against real PR base an…
Wikid82 Jul 20, 2026
2cf051d
test(orthrus): close agent/muzzle and agent/leash patch-coverage gap …
Wikid82 Jul 20, 2026
6fde74d
fix(orthrus): allow container rename in write-mode allowlist
Wikid82 Jul 21, 2026
bbcfdcb
feat(orthrus): notify operator when write-mode change requires an age…
Wikid82 Jul 21, 2026
3b8ce64
docs(orthrus): record rename allowlist fix and restart-required toast…
Wikid82 Jul 21, 2026
1859083
Merge branch 'development' into feature/orthrus
Wikid82 Jul 21, 2026
c43087f
fix(muzzle): allow CapDrop and safe UsernsMode values in container cr…
Wikid82 Jul 22, 2026
710898d
fix: reject dangerous ContainerIDFile, expand muzzle safe key allowlist
Wikid82 Jul 22, 2026
ad0c8b1
Merge branch 'development' into feature/orthrus
Wikid82 Jul 22, 2026
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/workflows/codecov-upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ on:
required: false
default: true
type: boolean
run_agent:
description: 'Run agent coverage upload'
required: false
default: true
type: boolean

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand Down Expand Up @@ -189,3 +194,49 @@ jobs:
directory: ./frontend/coverage
flags: frontend
fail_ci_if_error: true

agent-codecov:
name: Agent Codecov Upload
runs-on: ubuntu-latest
timeout-minutes: 15
if: ${{ github.event_name != 'workflow_dispatch' || inputs.run_agent }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
ref: ${{ github.sha }}

- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
with:
go-version-file: agent/go.mod
cache-dependency-path: agent/go.sum

# No "Resolve encryption key" step: agent has no encryption-key
# bootstrapping (consistent with quality-checks.yml's agent-quality
# job) — agent's tests don't touch CHARON_ENCRYPTION_KEY_TEST.
# No CGO_ENABLED: 1 either: agent/'s Docker image builds with
# CGO_ENABLED=0 (FROM scratch), so there is no cgo dependency here.

- name: Run Go tests with coverage
working-directory: ${{ github.workspace }}
run: |
bash scripts/agent-test-coverage.sh 2>&1 | tee agent/test-output.txt
exit "${PIPESTATUS[0]}"

- name: Upload test output artifact
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent-test-output
path: agent/test-output.txt
retention-days: 7

- name: Upload agent coverage to Codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./agent/coverage.txt
flags: agent
fail_ci_if_error: true
19 changes: 19 additions & 0 deletions .github/workflows/nightly-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,25 @@ jobs:
- name: Set lowercase image name
run: echo "ORTHRUS_IMAGE_NAME_LC=${ORTHRUS_IMAGE_NAME,,}" >> "$GITHUB_ENV"

# Gates this nightly image publish on the agent module's own test
# suite, mirroring orthrus-build.yml's PR-facing gate — needed here
# too because sync-development-to-nightly performs a git reset --hard
# / force-push path that does not itself go through a reviewed PR
# against nightly, so gating only the PR-facing workflow would leave
# this production-image-publishing path untested. See Section 3.3.5 of
# docs/plans/current_spec.md and GH #1161.
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: agent/go.mod
cache-dependency-path: agent/go.sum

- name: Run Orthrus agent tests
run: |
set -euo pipefail
cd agent
go test ./...

- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0

Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/orthrus-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@ jobs:
fi
fi

# Gates the Docker build on the agent module's own test suite,
# including the shared anti-drift corpus with backend/internal/orthrus
# (testdata/muzzle_corpus.json) — see Section 3.3.5 of
# docs/plans/current_spec.md and GH #1161. Prior to this step, agent/
# had zero CI-enforced test execution: every workflow touching it went
# straight from checkout to docker/build-push-action, only building the
# module's source as an opaque COPY . . step inside agent/Dockerfile's
# multi-stage build.
- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: agent/go.mod
cache-dependency-path: agent/go.sum

- name: Run Orthrus agent tests
run: |
set -euo pipefail
cd agent
go test ./...

- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0

Expand Down
89 changes: 89 additions & 0 deletions .github/workflows/quality-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,25 @@ jobs:
cd backend
go test ./internal/api/routes -run 'TestRegister_StateChangingRoutesRequireAuthentication|TestRegister_StateChangingRoutesDenyByDefaultWithExplicitAllowlist|TestRegister_AuthenticatedRoutes' -count=1 -v

muzzle-allowlist-parity:
name: Muzzle Allowlist Parity (backend/agent, #1160/#1161)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
ref: ${{ github.sha }}

- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: backend/go.mod

- name: Run structural allowlist parity checker
run: |
set -euo pipefail
go run scripts/ci/check_muzzle_allowlist_parity.go

codecov-trigger-parity-guard:
name: Codecov Trigger/Comment Parity Guard
runs-on: ubuntu-latest
Expand Down Expand Up @@ -250,6 +269,76 @@ jobs:
} >> "$GITHUB_STEP_SUMMARY"
exit "$PERF_STATUS"

agent-quality:
name: Agent (Go)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
fetch-depth: 0
ref: ${{ github.sha }}

- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: agent/go.mod
cache-dependency-path: agent/go.sum

# No "Resolve encryption key" step here: unlike backend-quality, agent
# has no CHARON_ENCRYPTION_KEY_TEST-dependent tests to bootstrap for
# (agent is a standalone module with no models/GORM/encryption
# concerns). No GORM Security Scanner step for the same reason (agent
# has no models). No Perf Asserts step: agent has no equivalent
# benchmark suite.

- name: go vet
working-directory: agent
run: go vet ./...

- name: Run golangci-lint (fast config, root-shared)
uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
# renovate: datasource=github-releases depName=golangci/golangci-lint
version: v2.12.2
working-directory: agent
args: --config ../.golangci-fast.yml --timeout=5m

- name: Run Go tests with coverage gate
id: go-tests
working-directory: ${{ github.workspace }}
run: |
bash "scripts/agent-test-coverage.sh" 2>&1 | tee agent/test-output.txt; exit "${PIPESTATUS[0]}"

- name: Upload test output artifact
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: agent-test-output
path: agent/test-output.txt
retention-days: 7

- name: Go Test Summary
if: always()
working-directory: agent
run: |
{
echo "## 🔧 Agent Test Results"
if [ "${{ steps.go-tests.outcome }}" == "success" ]; then
echo "✅ **All tests passed**"
PASS_COUNT=$(grep -c "^--- PASS" test-output.txt || echo "0")
echo "- Tests passed: ${PASS_COUNT}"
else
echo "❌ **Tests failed**"
echo ""
echo "### Failed Tests:"
echo '```'
grep -E "^--- FAIL|FAIL\s+github" test-output.txt || echo "See logs for details"
echo '```'
fi
} >> "$GITHUB_STEP_SUMMARY"

# Codecov upload handled separately in codecov-upload.yml (agent-codecov job).

frontend-quality:
name: Frontend (React)
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ backend/tr_no_cover.txt
backend/nohup.out
backend/charon
backend/main
backend/localpatchreport
backend/codeql-db/
backend/codeql-db-*/
backend/.venv/
Expand Down
File renamed without changes.
19 changes: 17 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1323,7 +1323,7 @@ go test ./integration/...

### Pre-Commit Checks

**Automated Hooks (via `.pre-commit-config.yaml`):**
**Automated Hooks (via `lefthook.yml`):**

**Fast Stage (< 5 seconds):**

Expand All @@ -1332,10 +1332,21 @@ go test ./integration/...
- YAML syntax check
- JSON syntax check
- Markdown link validation
- `go vet` and golangci-lint (fast config), run separately for `backend/`
and `agent/` (`go-vet` / `go-vet-agent`, both glob-scoped to their
respective module directory) — `agent/` is a standalone Go module (see
"Orthrus Agent" below) and gets the same enforcement `backend/` does, via
a shared root-level `.golangci-fast.yml` config
- `scripts/ci/check_muzzle_allowlist_parity.go`: a structural (AST-based)
guard that fails the commit if the Docker API allowlist declarations in
`backend/internal/orthrus/muzzle.go` and `agent/muzzle/muzzle.go` (the two
independently-maintained copies described in "Orthrus" below) diverge —
glob-scoped to those two files

**Manual Stage (run explicitly):**

- Backend coverage tests (60-90s)
- Agent coverage tests (`scripts/agent-test-coverage.sh`)
- Frontend coverage tests (30-60s)
- TypeScript type checking (10-20s)

Expand All @@ -1358,7 +1369,11 @@ go test ./integration/...
2. **Test:** Go tests, Vitest, Playwright
3. **Security:** Trivy, CodeQL, Grype, Govulncheck
4. **Build:** Docker image build
5. **Coverage:** Upload to Codecov (85% gate)
5. **Coverage:** Upload to Codecov (85% gate) — `backend`, `frontend`, and
`agent` each upload under a distinct Codecov flag
(`.github/workflows/codecov-upload.yml`); `quality-checks.yml` runs a
matching `agent-quality` job (go vet, lint, coverage gate) unconditionally
on every PR, not only PRs that touch `agent/**`
6. **Supply Chain:** SBOM generation, Cosign signing

---
Expand Down
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,51 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### CI/CD

- **Orthrus Agent CI Parity**: `agent/` (the standalone Orthrus agent Go
module) now gets the same enforcement `backend/` already had — `go vet`,
golangci-lint (fast config, now shared from repo-root
`.golangci-fast.yml`), and a test-coverage gate, both in pre-commit
(`go-vet-agent`, `golangci-lint-fast`, `agent-test-coverage`) and in CI
(`quality-checks.yml`'s new `agent-quality` job, unconditional on every
PR). Previously `agent/`'s pre-commit hooks passed trivially because they
were hardcoded to `backend/` — the exact mechanism by which a prior
incident's agent-side fix omission went undetected locally (GH #1161).
- New `scripts/agent-test-coverage.sh` and `scripts/check-module-coverage.sh`
(the latter fixes a pre-existing dangling `Makefile` reference)
- `scripts/local-patch-report.sh` and the local patch-coverage tool now
report `agent/` patch coverage alongside backend/frontend, so
CLAUDE.md's mandatory patch-coverage preflight has visibility into
agent changes
- New `agent-codecov` job uploads `agent/coverage.txt` to Codecov under a
distinct `agent` flag
- New `scripts/ci/check_muzzle_allowlist_parity.go`: a structural
(AST-based) guard that fails the build if the Docker API allowlist
declarations in `backend/internal/orthrus/muzzle.go` and
`agent/muzzle/muzzle.go` diverge — closing the drift-detection gap that
let the underlying normalization bug (see Security, below) go
unnoticed for two release cycles

- **Supply Chain**: Optimized verification workflow to prevent redundant builds
- Change: Removed direct Push/PR triggers; now waits for 'Docker Build' via `workflow_run`

### Security

- **Orthrus Muzzle Normalization Order (GH #1160)**: Fixed a divergence
between the backend and agent-side Docker API allowlist filters where the
agent normalized a request path (version-prefix strip, then
`path.Clean`) in a different order than the backend, and additionally
accepted a non-numeric `v`-prefixed segment as a version prefix via a
loose `path.Match("v*", ...)` wildcard. A crafted path (e.g. a
traversal-disguised or non-numeric version prefix) could be classified
differently by each filter in isolation; today's pipeline always runs
the backend filter first, masking the bug, but the agent-side filter
alone was strictly more permissive on 3 confirmed input classes,
including one reaching a write endpoint. Both filters now apply the
identical strip-then-clean order via a `normalizeDockerPath` helper
present in both files, and agent's allowlists were collapsed to
unversioned-only entries (dropping the redundant, overly-permissive
`/v*/...` duplicates).

- chore(security): verify CVE-2026-39824 (golang.org/x/sys) is not present — golang.org/x/sys already at v0.46.0, exceeding the v0.44.0 fix; no action required

- chore(security): pin gosu-builder's golang.org/x/sys to v0.46.0 (GO-2026-5024 / CVE-2026-39824) — upstream tianon/gosu@1.17 vendors v0.13.0; vulnerable code is Windows-only and never compiled for this Linux-only binary; v0.46.0 matches the existing pin used elsewhere in the Dockerfile for the same advisory
Expand Down
12 changes: 7 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,14 @@ lint-backend:
cd backend && docker run --rm -v $(PWD)/backend:/app -w /app golangci/golangci-lint:latest golangci-lint run -v

lint-fast:
@echo "Running fast linters (staticcheck, govet, errcheck, ineffassign, unused)..."
cd backend && golangci-lint run --config .golangci-fast.yml ./...
@echo "Running fast linters (staticcheck, govet, errcheck, ineffassign, unused) — backend + agent..."
cd backend && golangci-lint run --config ../.golangci-fast.yml ./...
cd agent && golangci-lint run --config ../.golangci-fast.yml ./...

lint-staticcheck-only:
@echo "Running staticcheck only..."
cd backend && golangci-lint run --config .golangci-fast.yml --disable-all --enable staticcheck ./...
@echo "Running staticcheck only — backend + agent..."
cd backend && golangci-lint run --config ../.golangci-fast.yml --enable-only staticcheck ./...
cd agent && golangci-lint run --config ../.golangci-fast.yml --enable-only staticcheck ./...

lint-docker:
@echo "Running Hadolint..."
Expand All @@ -191,7 +193,7 @@ test-race:
cd backend && go test -race -v ./...

check-module-coverage:
@echo "Running module-specific coverage checks (backend + frontend)"
@echo "Running module-specific coverage checks (backend + agent)"
@bash scripts/check-module-coverage.sh

benchmark:
Expand Down
Loading
Loading