diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 51cd055..27ea748 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -257,6 +257,42 @@ jobs: -m unittest lib.python.base_demo_cli.tests.test_cli + security: + name: Security scanners + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.13" + + - name: Install uv + run: python -m pip install --disable-pip-version-check uv==0.12.5 + + - name: Install ShellCheck + run: | + sudo apt-get update + sudo apt-get install -y shellcheck + + - name: Run Bandit + run: | + uv run --no-project --with bandit==1.9.4 \ + bandit -q -r bin lib -x '*/tests/*' --severity-level medium + + - name: Run pip-audit + run: | + uv export --locked --no-dev --no-emit-project --format requirements-txt \ + | uv run --no-project --with pip-audit==2.10.1 \ + pip-audit --cache-dir "$RUNNER_TEMP/pip-audit" -r /dev/stdin + + - name: Run ShellCheck + run: | + git ls-files -z '*.sh' 'bin/base-demo-python-info' \ + | xargs -0 shellcheck --severity=error + validate-ubuntu: runs-on: ubuntu-latest timeout-minutes: 35 diff --git a/docs/contracts.md b/docs/contracts.md index 91ff1c9..00546ad 100644 --- a/docs/contracts.md +++ b/docs/contracts.md @@ -36,6 +36,7 @@ depend on. | `compose-local-isolation` | Every published Compose port binds to loopback, Compose owns container names, and every Compose operation uses a stable checkout-and-environment project identity unless automation supplies a validated override. | `infra/compose.yaml`, `bin/base-demo-services` | `tests/infra_test.bats`, `tests/validate.sh` | Disposable credentials can become remotely reachable, or one worktree's lifecycle and log commands can target another worktree's containers. | Security | | `frontend-setup-path` | `basectl setup base-demo` installs Node 22.22.0 with bundled npm 10.9.4 through mise, and `basectl test base-demo` delegates to a mise task that runs locked `npm ci` when frontend inputs change or dependencies are absent. | `.mise.toml`, `base_manifest.yaml`, `services/demo-console/package.json`, `services/demo-console/package-lock.json` | `tests/demo_console_test.bats`, `tests/validate.sh`, Base's external base-demo E2E | A clean checkout can pass setup but fail the supported test path because Node, npm, Vite, or `node_modules` was never provisioned. | Setup | | `demo-console-build-gate` | macOS and Ubuntu CI install the locked frontend dependencies with Node 22.22.0/npm 10.9.4, compile the production console, assert entry artifacts, and fail the audit at moderate severity or higher. | `services/demo-console/package.json`, `services/demo-console/package-lock.json`, `.github/workflows/tests.yml` | `services/demo-console/build.sh`, `tests/demo_console_test.bats`, `tests/validate.sh` | Green validation can skip Vite, omit production artifacts, or retain a known dependency advisory. | CI | +| `security-scanners` | Required CI runs pinned Bandit 1.9.4 over project Python sources, pip-audit 2.10.1 against the locked uv export, and ShellCheck over tracked shell entrypoints. | `.github/workflows/tests.yml`, `pyproject.toml`, `uv.lock` | `tests/validate.sh`, GitHub Actions `security` | Shell or Python security regressions and vulnerable locked dependencies can merge without a dedicated required signal. | Security | | `ci-trigger-deduplication` | Feature-branch commits run the validation workflow through `pull_request` only, `push` validation remains enabled for `main`, superseded runs cancel only within the same PR number or ref, and the `validate`, `validate-base-cli-source`, and `validate-ubuntu` job IDs remain stable. | `.github/workflows/tests.yml` | `tests/validate.sh`, GitHub Actions | A PR commit can consume two full validation runs, unrelated branches can cancel one another, default-branch validation can disappear, or check names can drift. | CI | | `release-identity` | `VERSION` is the authoritative stable SemVer identity; Python/uv metadata, frontend package metadata, the Base-style top README badge strip and release links, and the changelog heading agree, and only an explicit matching `vX.Y.Z` tag can publish a GitHub Release. | `VERSION`, `pyproject.toml`, `uv.lock`, `services/demo-console/package.json`, `README.md`, `CHANGELOG.md`, `.github/workflows/release.yml` | `bin/base-demo-release-check`, `tests/validate.sh`, GitHub Actions | Users, automation, or release provenance can disagree about the base-demo version, the current release can be hard to discover, the project-status strip can drift, or an unreviewed branch can publish a release. | Release | | `ci-pinned-dependencies` | CI uses immutable full commits for the published Base v1.8.0 and base-bash-libs v2.0.0 contracts, plus full SHA-pinned GitHub Actions. | `.github/workflows/tests.yml` | `tests/validate.sh` | CI can drift with Base `main`, an older base-bash-libs release, or mutable action tags instead of validating the intended release capability contract. | CI | diff --git a/tests/validate.sh b/tests/validate.sh index 9143c77..d3d7e33 100755 --- a/tests/validate.sh +++ b/tests/validate.sh @@ -262,6 +262,25 @@ for job_id in ("validate", "validate-base-cli-source", "validate-ubuntu"): ) PY +security_workflow_contracts=( + ' security:' + 'name: Security scanners' + 'timeout-minutes: 20' + 'actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065' + 'python -m pip install --disable-pip-version-check uv==0.12.5' + 'uv run --no-project --with bandit==1.9.4' + 'uv run --no-project --with pip-audit==2.10.1' + 'uv export --locked --no-dev --no-emit-project --format requirements-txt' + "git ls-files -z '*.sh' 'bin/base-demo-python-info'" + 'shellcheck --severity=error' +) +for security_contract in "${security_workflow_contracts[@]}"; do + grep -Fq "$security_contract" .github/workflows/tests.yml || { + printf '.github/workflows/tests.yml is missing security contract: %s\n' "$security_contract" >&2 + exit 1 + } +done + grep -Fq 'pull_request_target:' .github/workflows/issue-branch-policy.yml || { printf '.github/workflows/issue-branch-policy.yml does not validate pull_request_target events.\n' >&2 exit 1