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
85 changes: 74 additions & 11 deletions .github/branch-protection-guide.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,69 @@
# Branch Protection And CI Roles

This repository uses three CI layers with different responsibilities:
This repository uses three primary CI/CD layers with different responsibilities:

- `PR Gate`: the pre-merge blocking gate for `main`
- `Main Post-Merge`: post-merge validation for merge-result health and advisory supply-chain checks
- `Release`: tag-based release preparation and draft release creation

There is also one source-security workflow managed alongside the CI layers:

- `CodeQL`: source code security scanning for PRs to `main` and pushes to `main`

There is also one optional developer workflow:

- `Dev Fast CI`: lightweight self-checks for non-`main` development branches

Related workflows:

- `.github/workflows/pr-gate.yml`
- `.github/workflows/_quality-gate.yml`
- `.github/workflows/main-post-merge.yml`
- `.github/workflows/codeql.yml`
- `.github/workflows/dev-fast-ci.yml`
- `.github/workflows/release.yml`
- `.github/workflows/publish-images.yml`

## What Should Block Merge

Only the PR gate should be configured as the required branch-protection check for `main`.
Only the PR gate should be configured as the required branch-protection check set for `main`.

Recommended required check targets:

- `PR Gate / Quality Gate / Lint (pull_request)`
- `PR Gate / Quality Gate / Test Backend (pull_request)`
- `PR Gate / Quality Gate / Test Frontend (pull_request)`
- `PR Gate / Quality Gate / Security Scan (pull_request)`
- `PR Gate / Quality Gate / E2E Smoke (pull_request)`

Recommended required check target:
Optional later additions after the first successful CodeQL PR runs:

- workflow: `PR Gate`
- job: `Quality Gate`
- the real reported CodeQL PR job checks, such as the Go and JavaScript TypeScript analysis jobs

Why:

- It is the main admission gate for code entering `main`.
- It runs lint, tests, security fast checks, and e2e smoke.
- It avoids using post-merge or release-only checks as pre-merge blockers.
- `PR Gate` is the main admission gate for code entering `main`
- it runs lint, tests, security fast checks, and e2e smoke
- it avoids using post-merge or release-only checks as pre-merge blockers
- GitHub branch protection must reference the real job-level checks reported by workflows, not abstract wrapper names like `PR Gate` or `Quality Gate`

Do not set these as required checks unless matching workflows are actually reporting them:

- `PR Gate`
- `Quality Gate`
- `Code scanning results`

## Developer Branch Fast CI

`Dev Fast CI` is intended for personal development branches and excludes `main`.

It is useful for early feedback, but it is not a merge-governance layer and should not replace the PR gate.

Recommended use:

- run lightweight lint, backend/frontend tests, and fast security checks on `push -> non-main branches`
- do not treat `Dev Fast CI` as a required check for `main`
- do not use it as a substitute for PR review or branch protection

## What Should Not Block Merge

Expand Down Expand Up @@ -55,8 +92,9 @@ Enable:

Recommended manual settings outside the repository:

- set `PR Gate / Quality Gate` as a required status check
- require at least one human approval
- set the five PR job checks listed above as required status checks
- after the first successful CodeQL runs, optionally add the real reported CodeQL PR check names if you want source security scanning to block merges
- require at least one human approval when the repository has more than one active maintainer
- add CODEOWNERS later for sensitive areas such as workflows, release logic, and runtime bootstrapping

## How To Treat AI In Branch Protection
Expand All @@ -76,4 +114,29 @@ Release workflows are not part of the merge gate.
- `Release` validates tags, generates release artifacts, and creates a draft release
- `Publish Images` runs only after a release is published

This separation avoids using release-specific work as a daily development bottleneck.
This separation avoids using release-specific work as a daily development bottleneck.

## Current Code Scanning State

There is a dedicated repository-managed CodeQL workflow in `.github/workflows/codeql.yml`.

What exists right now:

- `CodeQL` runs for `pull_request -> main`
- `CodeQL` runs for `push -> main`
- `Main Post-Merge` runs an advisory Trivy image scan
- the Trivy SARIF report is uploaded to GitHub Security using `github/codeql-action/upload-sarif@v3`
- the Trivy SARIF upload happens after pushes to `main`, not as a PR gate

What this means operationally:

- PRs to `main` now produce dedicated CodeQL checks and code scanning results
- pushes to `main` continue to refresh the default-branch code scanning baseline
- branch protection should use the real CodeQL job check names after they appear, not the generic label `Code scanning results`
- Trivy image scanning remains a post-merge image-security signal, not a source-code scanning gate

Recommended interpretation:

- CodeQL is the source-code security layer
- Trivy image scanning is the post-merge image-security layer
- these are complementary and should not be treated as the same check type
2 changes: 1 addition & 1 deletion .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Explain the problem, requirement, or risk this PR addresses.
- [ ] `make lint`
- [ ] `make test backend`
- [ ] `make test web`
- [ ] `make e2e`
- [ ] `make test e2e fast`
- [ ] Other validation noted below

Additional validation notes:
Expand Down
19 changes: 17 additions & 2 deletions .github/workflows/_quality-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,22 @@
- name: Run backend tests
run: make test backend

openapi:
name: OpenAPI Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum

- name: Run OpenAPI sync check
run: make openapi-sync

test-frontend:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
name: Test Frontend
runs-on: ubuntu-latest
steps:
Expand Down Expand Up @@ -164,7 +179,7 @@
e2e:
name: E2E Smoke
runs-on: ubuntu-latest
needs: [lint, test-backend, test-frontend, sec]
needs: [lint, test-backend, test-frontend, openapi, sec]
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
Expand All @@ -173,7 +188,7 @@
run: mkdir -p build/reports/e2e

- name: Run container smoke test
run: make e2e
run: make test e2e fast
env:
APPOS_E2E_ARTIFACT_DIR: build/reports/e2e
APPOS_E2E_KEEP_CONTAINER_ON_FAILURE: "1"
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
name: CodeQL

on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:

permissions:
actions: read
contents: read
security-events: write

concurrency:
group: codeql-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze-go:
name: Analyze Go
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: go

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum

- name: Build Go sources
run: cd backend && go build ./...

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:go

analyze-javascript-typescript:
name: Analyze JavaScript TypeScript
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: javascript-typescript

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: web/package-lock.json

- name: Install frontend deps
run: cd web && npm ci

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v3
with:
category: /language:javascript-typescript
85 changes: 85 additions & 0 deletions .github/workflows/dev-fast-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
name: Dev Fast CI

on:
push:
branches-ignore:
- main
workflow_dispatch:

concurrency:
group: dev-fast-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
fast-checks:
name: Fast Checks
runs-on: ubuntu-latest
env:
GITLEAKS_REPORT_PATH: build/reports/gitleaks-report.json
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: backend/go.mod
cache-dependency-path: backend/go.sum

- name: Prepare Go tool bin
run: |
mkdir -p "$RUNNER_TEMP/bin"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: npm
cache-dependency-path: web/package-lock.json

- name: Install frontend deps
run: cd web && npm ci

- name: Install fast-check tools
run: |
set -euo pipefail
GOBIN="$RUNNER_TEMP/bin" go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
GOBIN="$RUNNER_TEMP/bin" go install golang.org/x/vuln/cmd/govulncheck@latest

GL_VERSION="8.24.2"
ARCH="$(uname -m)"
case "$ARCH" in
x86_64) GL_ARCH="x64" ;;
aarch64|arm64) GL_ARCH="arm64" ;;
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;;
esac
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GL_VERSION}/gitleaks_${GL_VERSION}_linux_${GL_ARCH}.tar.gz" \
| tar -xz -C "$RUNNER_TEMP/bin" gitleaks

- name: Run fast lint
run: make lint fast
env:
GOLANGCI_LINT_BIN: ${{ runner.temp }}/bin/golangci-lint

- name: Run fast backend tests
run: make test backend fast

- name: Run frontend tests
run: make test web

- name: Run fast security checks
run: make sec fast
env:
GOVULNCHECK_BIN: ${{ runner.temp }}/bin/govulncheck
GITLEAKS_BIN: ${{ runner.temp }}/bin/gitleaks
GITLEAKS_REPORT_PATH: ${{ env.GITLEAKS_REPORT_PATH }}

- name: Upload fast CI gitleaks report
if: always()
uses: actions/upload-artifact@v4
with:
name: dev-fast-gitleaks-${{ github.sha }}
path: ${{ env.GITLEAKS_REPORT_PATH }}
if-no-files-found: ignore

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +15 to +85
46 changes: 46 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
title = "AppOS gitleaks config"

[extend]
useDefault = true

[[allowlists]]
description = "Generated frontend build output"
paths = [
'''^web/dist/'''
]

[[allowlists]]
description = "BMAD file manifest stores content hashes, not credentials"
paths = [
'''^_bmad/_config/files-manifest\.csv$'''
]

[[allowlists]]
description = "Bundled skill docs use redacted or illustrative token examples"
paths = [
'''^\.agents/skills/.*/resources/knowledge/api-testing-patterns\.md$''',
'''^\.agents/skills/wds-6-asset-generation/steps-p/step-01-load-context\.md$'''
]

[[allowlists]]
description = "Test fixtures use deterministic non-production placeholders"
paths = [
'''^backend/domain/certs/resolve_test\.go$''',
'''^backend/domain/monitor/signals/agent/agent_test\.go$''',
'''^backend/domain/monitor/signals/checks/credential_sweep_test\.go$''',
'''^backend/domain/worker/monitoring_checks_test\.go$'''
]
regexes = [
'''MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=''',
'''-----BEGIN PRIVATE KEY-----''',
]

[[allowlists]]
description = "Deterministic development-only crypto fallback keys"
paths = [
'''^backend/infra/crypto/crypto\.go$''',
'''^backend/domain/secrets/legacy_encryption\.go$'''
]
regexes = [
'''0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'''
]
Loading
Loading