From 2645af552a081c50f77ebe5ce10b7dcdd06f3a82 Mon Sep 17 00:00:00 2001 From: Ihor Date: Fri, 26 Jun 2026 12:30:22 +0000 Subject: [PATCH 1/7] feat(devcontainer): base on Ubuntu 26.04 and add Claude Code CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Bump base image to mcr.microsoft.com/devcontainers/base:ubuntu26.04 (MCR dropped the hyphen for 26.04+; tag verified published and multi-arch) - Add ghcr.io/anthropics/devcontainer-features/claude-code:1.0 feature - Add anthropic.claude-code VS Code extension - Mount named volume claude-code-config-${devcontainerId} at /home/vscode/.claude so auth and settings survive container rebuilds - Add devcontainer-lock.json with SHA-pinned digests for all 5 features CI (ci.yml, replaces security.yml): - Merge security.yml into ci.yml; single workflow with build/test/scan jobs sharing one Docker image via artifact (docker save/load), eliminating the duplicate build that ran on every push - Add schedule (daily 02:00 UTC) and workflow_dispatch triggers - Remove unused actions:read permission; use per-job permission grants - Guard all SARIF uploads against fork PRs (read-only token -> 403) - Add claude to smoke-test binary loop and version assertions - Trivy now gates on CRITICAL (exit-code: 1) for both image and fs scans, consistent with gitleaks which already gated on secrets Dependabot: - Change all three ecosystems (github-actions, docker, devcontainers) from weekly to daily — supply-chain and CVE exposure warrants daily checks - Add devcontainers ecosystem block (was missing) so node, github-cli, common-utils, sshd, and claude-code features receive update PRs - Raise open-pull-requests-limit to 10 for all ecosystems Docs: - Update README: Ubuntu 26.04, Claude Code tooling + extension, fix stale feature list and jq/curl provenance claims, reflect merged CI workflow, add AGENTS.md to repository structure - Add AGENTS.md: project overview, file layout, commit conventions, JSON and GitHub Actions pinning rules, local validation commands, guardrails Tested locally: JSON validity, hadolint, docker build (ubuntu26.04), and full devcontainer smoke test (python3 node npm gh opencode curl jq claude all present; claude --version: 2.1.193). Signed-off-by: Ihor --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer-lock.json | 29 +++++++ .devcontainer/devcontainer.json | 7 +- .github/dependabot.yml | 25 ++++-- .github/workflows/ci.yml | 107 +++++++++++++++++++++-- .github/workflows/security.yml | 88 ------------------- AGENTS.md | 121 +++++++++++++++++++++++++++ README.md | 40 +++++---- 8 files changed, 304 insertions(+), 115 deletions(-) create mode 100644 .devcontainer/devcontainer-lock.json delete mode 100644 .github/workflows/security.yml create mode 100644 AGENTS.md diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index b3e6df3..858b5a7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/devcontainers/base:ubuntu-24.04 +FROM mcr.microsoft.com/devcontainers/base:ubuntu26.04 # hadolint ignore=DL3008 RUN set -eux; \ diff --git a/.devcontainer/devcontainer-lock.json b/.devcontainer/devcontainer-lock.json new file mode 100644 index 0000000..9695bfa --- /dev/null +++ b/.devcontainer/devcontainer-lock.json @@ -0,0 +1,29 @@ +{ + "features": { + "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": { + "version": "1.0.5", + "resolved": "ghcr.io/anthropics/devcontainer-features/claude-code@sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a", + "integrity": "sha256:cfc2e7d3e9fd3b9b01f8d5cb158508a884c8c0ede2e23ed10f32dea5d4ffe69a" + }, + "ghcr.io/devcontainers/features/common-utils:2": { + "version": "2.5.9", + "resolved": "ghcr.io/devcontainers/features/common-utils@sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a", + "integrity": "sha256:cb0c4d3c276f157eed17935747e364178d75fee17f55c4e129966f64633deb3a" + }, + "ghcr.io/devcontainers/features/github-cli:1": { + "version": "1.1.0", + "resolved": "ghcr.io/devcontainers/features/github-cli@sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671", + "integrity": "sha256:d22f50b70ed75339b4eed1ba9ecde3a1791f90e88d37936517e3bace0bbad671" + }, + "ghcr.io/devcontainers/features/node:1": { + "version": "1.7.1", + "resolved": "ghcr.io/devcontainers/features/node@sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6", + "integrity": "sha256:8c0de46939b61958041700ee89e3493f3b2e4131a06dc46b4d9423427d06e5f6" + }, + "ghcr.io/devcontainers/features/sshd:1": { + "version": "1.1.0", + "resolved": "ghcr.io/devcontainers/features/sshd@sha256:f5251b8e4325f68f7280973c6cd65daff414449c66f240621502d4e8e74eb7ee", + "integrity": "sha256:f5251b8e4325f68f7280973c6cd65daff414449c66f240621502d4e8e74eb7ee" + } + } +} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f12e8fd..2b07f1e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,11 +24,16 @@ }, "ghcr.io/devcontainers/features/github-cli:1": { "version": "latest" - } + }, + "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {} }, + "mounts": [ + "source=claude-code-config-${devcontainerId},target=/home/vscode/.claude,type=volume" + ], "customizations": { "vscode": { "extensions": [ + "anthropic.claude-code", "github.copilot", "github.copilot-chat", "ms-python.python", diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9e9aa81..21fe026 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,12 +1,12 @@ # Keep devcontainer dependencies up to date version: 2 updates: - # Monitor GitHub Actions + # Monitor GitHub Actions — daily, security-critical supply-chain component - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" - open-pull-requests-limit: 5 + interval: "daily" + open-pull-requests-limit: 10 labels: - "dependencies" - "github-actions" @@ -19,12 +19,12 @@ updates: - "minor" - "patch" - # Monitor Docker base images + # Monitor Docker base images — daily, CVEs ship in base images - package-ecosystem: "docker" directory: "/.devcontainer" schedule: - interval: "weekly" - open-pull-requests-limit: 5 + interval: "daily" + open-pull-requests-limit: 10 labels: - "dependencies" - "docker" @@ -36,3 +36,16 @@ updates: update-types: - "minor" - "patch" + + # Monitor devcontainer features — daily, includes claude-code, node, github-cli, common-utils, sshd + - package-ecosystem: "devcontainers" + directory: "/.devcontainer" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + labels: + - "dependencies" + - "devcontainers" + commit-message: + prefix: "feat" + include: "scope" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03b28cb..8281182 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,11 +5,12 @@ on: branches: [ main ] pull_request: branches: [ main ] + schedule: + - cron: '0 2 * * *' + workflow_dispatch: permissions: contents: read - actions: read - security-events: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -20,6 +21,9 @@ jobs: runs-on: ubuntu-latest name: Build and Validate timeout-minutes: 15 + permissions: + contents: read + security-events: write steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -58,20 +62,34 @@ jobs: --report-path gitleaks.sarif - name: Upload gitleaks SARIF - if: always() + if: > + always() && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 with: sarif_file: gitleaks.sarif category: security-analysis/gitleaks - name: Build Docker image - run: docker build --pull -t dev-template:latest .devcontainer/ + run: | + docker build --pull -t "dev-template:${{ github.sha }}" .devcontainer/ + docker save "dev-template:${{ github.sha }}" -o /tmp/dev-template.tar + + - name: Upload image artifact + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: docker-image-${{ github.sha }} + path: /tmp/dev-template.tar + retention-days: 1 test: runs-on: ubuntu-latest name: Test Devcontainer needs: build timeout-minutes: 20 + permissions: + contents: read steps: - name: Checkout uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 @@ -84,7 +102,7 @@ jobs: push: never runCmd: | set -eu - for cmd in python3 node npm gh opencode curl jq; do + for cmd in python3 node npm gh opencode curl jq claude; do if ! command -v "$cmd" >/dev/null 2>&1; then echo "::error::$cmd is missing" exit 1 @@ -97,3 +115,82 @@ jobs: opencode --version curl --version | head -1 jq --version + claude --version + + scan: + runs-on: ubuntu-latest + name: Security and SBOM Analysis + needs: build + timeout-minutes: 20 + permissions: + contents: read + security-events: write + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Download image artifact + uses: actions/download-artifact@v4 + with: + name: docker-image-${{ github.sha }} + path: /tmp + + - name: Load Docker image + run: docker load -i /tmp/dev-template.tar + + - name: Run Trivy vulnerability scanner (image) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + image-ref: 'dev-template:${{ github.sha }}' + format: 'sarif' + output: 'trivy-results.sarif' + severity: 'CRITICAL' + exit-code: '1' + + - name: Upload Trivy image scan results + if: > + always() && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) + uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 + with: + sarif_file: 'trivy-results.sarif' + category: 'security-analysis/trivy-image' + + - name: Generate SBOM + if: always() + uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0 + with: + image: 'dev-template:${{ github.sha }}' + format: 'spdx-json' + output-file: 'sbom.spdx.json' + + - name: Upload SBOM as artifact + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: sbom-${{ github.sha }} + path: sbom.spdx.json + retention-days: 30 + + - name: Run Trivy vulnerability scanner (filesystem) + uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-fs-results.sarif' + severity: 'CRITICAL' + exit-code: '1' + + - name: Upload Trivy filesystem scan results + if: > + always() && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) + uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 + with: + sarif_file: 'trivy-fs-results.sarif' + category: 'security-analysis/trivy-filesystem' diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml deleted file mode 100644 index 5843e0a..0000000 --- a/.github/workflows/security.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: Security Analysis - -on: - schedule: - - cron: '0 2 * * *' - push: - branches: [ main ] - pull_request: - branches: [ main ] - workflow_dispatch: - -permissions: - contents: read - security-events: write - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - security-scan: - runs-on: ubuntu-latest - name: Security and SBOM Analysis - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 0 - persist-credentials: false - - - name: Build Docker image for scanning - id: build - continue-on-error: true - run: | - IMAGE_NAME="dev-template:${{ github.sha }}" - docker build --pull -t "$IMAGE_NAME" .devcontainer/ - echo "IMAGE_NAME=$IMAGE_NAME" >> "$GITHUB_ENV" - - - name: Run Trivy vulnerability scanner (image) - if: steps.build.outcome == 'success' - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - image-ref: '${{ env.IMAGE_NAME }}' - format: 'sarif' - output: 'trivy-results.sarif' - severity: 'CRITICAL,HIGH' - exit-code: '0' - - - name: Upload Trivy image scan results - if: steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 - with: - sarif_file: 'trivy-results.sarif' - category: 'security-analysis/trivy-image' - - - name: Generate SBOM - if: steps.build.outcome == 'success' - uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0 - with: - image: '${{ env.IMAGE_NAME }}' - format: 'spdx-json' - output-file: 'sbom.spdx.json' - - - name: Upload SBOM as artifact - if: steps.build.outcome == 'success' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 - with: - name: sbom-${{ github.sha }} - path: sbom.spdx.json - retention-days: 30 - - - name: Run Trivy vulnerability scanner (filesystem) - uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - with: - scan-type: 'fs' - scan-ref: '.' - format: 'sarif' - output: 'trivy-fs-results.sarif' - severity: 'CRITICAL,HIGH' - exit-code: '0' - - - name: Upload Trivy filesystem scan results - if: always() - uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 - with: - sarif_file: 'trivy-fs-results.sarif' - category: 'security-analysis/trivy-filesystem' diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8f0798f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,121 @@ +# AGENTS.md + +Guidance for AI agents and automated contributors working in this repository. + +## Project overview + +This repository is a lean development container template for GitHub Codespaces +and local VS Code Dev Containers. It defines a reproducible Ubuntu 26.04 +environment with Python 3, Node.js LTS, GitHub CLI, Claude Code CLI, and +OpenCode TUI. The repository contains no application source code — all +meaningful files are configuration. + +## Repository layout + +``` +.devcontainer/ + Dockerfile Base image pin + apt package installs (python3, venv, pip, sudo) + devcontainer.json Feature declarations, VS Code extensions, volume mounts, lifecycle commands +.github/ + dependabot.yml Daily Dependabot updates for GitHub Actions, Docker images, devcontainer features + workflows/ + ci.yml Single CI pipeline: build → test → scan (see CI section below) +.gitignore Excludes OS files, editor dirs, Claude session state (.claude/, CLAUDE.md) +AGENTS.md This file +LICENSE MIT +README.md Human-facing documentation +``` + +## Conventions + +### Commit messages +Follow Conventional Commits as used throughout the repo history: +- `feat(devcontainer):` — devcontainer features, base image, extensions +- `ci:` / `ci(deps):` — workflow changes, dependency bumps +- `docker:` — Dockerfile or base image changes +- `docs:` — README, AGENTS.md, comments + +### JSON +`devcontainer.json` is **strict JSON** (no comments, no trailing commas). +Validate with `python3 -m json.tool .devcontainer/devcontainer.json` before +committing. + +### GitHub Actions +All `uses:` entries **must** be pinned to a full 40-character commit SHA with a +`# vX.Y.Z` or `# vX` trailing comment, e.g.: +```yaml +uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 +``` +Never use a floating tag (`@main`, `@v4`, `@latest`). Dependabot keeps SHAs +current via daily PRs. + +### Dockerfile +- One `FROM` line; base image tag must be a versioned MCR tag so Dependabot + can track it (e.g. `ubuntu26.04`, `ubuntu-24.04`; note MCR dropped the + hyphen for 26.04+). +- `hadolint` runs in CI; respect its rules or add a targeted + `# hadolint ignore=DLXXXX` with a justification comment. +- Keep the package list minimal — features (not the Dockerfile) install + language runtimes and CLI tools. + +## CI pipeline (`ci.yml`) + +Three sequential jobs share a single Docker build via artifact: + +| Job | `needs` | What it does | +|-----|---------|-------------| +| `build` | — | hadolint → gitleaks (SARIF) → `docker build` → `docker save` → upload artifact | +| `test` | `build` | Full devcontainer build via `devcontainers/ci`; smoke-tests all required binaries | +| `scan` | `build` | `docker load` → Trivy image scan (gates on CRITICAL) → SBOM → Trivy fs scan (gates on CRITICAL) | + +Triggers: `push`/`pull_request` on `main`, daily `schedule` (02:00 UTC), +`workflow_dispatch`. + +**SARIF uploads** are skipped for fork PRs (read-only token) via: +```yaml +if: > + always() && + (github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name == github.repository) +``` + +## Local validation commands + +Run these before committing, in order: + +```bash +# 1. JSON validity +python3 -m json.tool .devcontainer/devcontainer.json > /dev/null + +# 2. Dockerfile lint +docker run --rm -i hadolint/hadolint < .devcontainer/Dockerfile + +# 3. Base image build (mirrors ci.yml build job) +docker build --pull -t dev-template:local .devcontainer/ + +# 4. Full devcontainer build + smoke test (mirrors ci.yml test job) +# Requires network egress to ghcr.io and registry.npmjs.org +npx -y @devcontainers/cli up --workspace-folder . --remove-existing-container +npx @devcontainers/cli exec --workspace-folder . -- bash -c ' + set -eu + for cmd in python3 node npm gh opencode curl jq claude; do + command -v "$cmd" || { echo "MISSING: $cmd"; exit 1; } + done + claude --version + echo "All checks passed" +' +``` + +## Guardrails + +- **Keep the CI smoke test in sync.** If you add a new tool via a feature or + `updateContentCommand`, add it to the `for cmd in ...` loop in `ci.yml` + (`test` job, `runCmd` block). +- **No secrets in tracked files.** `.claude/` and `CLAUDE.md` are gitignored; + do not commit authentication tokens, API keys, or credential files. +- **Do not remove SHA pins.** Floating action refs will fail code review. +- **Do not push directly to `main`.** All changes go through a PR from a + feature branch. +- **Volume mount target.** The Claude Code volume is mounted at + `/home/vscode/.claude` (matching `remoteUser: vscode`). If the remote user + ever changes, the mount target must be updated to match. diff --git a/README.md b/README.md index c4a9912..4df86e7 100644 --- a/README.md +++ b/README.md @@ -9,16 +9,18 @@ Codespaces and local VS Code Dev Containers. ## Features -- Ubuntu 24.04 base image +- Ubuntu 26.04 (Resolute) base image - Single-image devcontainer build based on `.devcontainer/Dockerfile` - Core tooling for general development work: - Python 3 with `venv` and `pip` - Node.js LTS with `npm` - GitHub CLI + - Claude Code CLI (authenticated session persisted across rebuilds) - OpenCode TUI installed via `updateContentCommand` (cached by prebuilds) - Bash shell with common utilities - Build essentials (`gcc`, `make`, and related packages) via base image - VS Code extensions: + - Claude Code - Python and Pylance - GitHub Copilot and Copilot Chat - YAML @@ -27,13 +29,18 @@ Codespaces and local VS Code Dev Containers. The devcontainer is tuned for fast Codespaces startup: -- Minimal feature set: only `common-utils`, `sshd`, `node`, and `github-cli` - features are installed — Docker-in-Docker and git features are omitted +- Minimal feature set: only `common-utils`, `sshd`, `node`, `github-cli`, and + `claude-code` features are installed — Docker-in-Docker and git features are + omitted - `common-utils` configured with zsh and Oh My Zsh disabled -- `curl`, `wget`, `jq`, and `git` sourced from the base image and - `common-utils` feature — not re-installed in the Dockerfile -- OpenCode TUI installed in `updateContentCommand` instead of `postCreateCommand`, - so it is cached during Codespaces prebuilds and not re-run on every start +- `curl`, `wget`, `jq`, and `git` sourced from the base image — not + re-installed in the Dockerfile +- OpenCode TUI installed in `updateContentCommand` instead of + `postCreateCommand`, so it is cached during Codespaces prebuilds and not + re-run on every start +- Claude Code authentication stored in a named volume + (`claude-code-config-${devcontainerId}`) so sign-in survives container + rebuilds - Small, targeted extension set Approximate startup time: **~45–75 seconds** without prebuilds and @@ -70,23 +77,28 @@ created from `main` will start in approximately **10–25 seconds**. 1. Click "Code" button on the GitHub repository 2. Select "Create codespace on main" 3. Wait for the environment to build +4. Run `claude` in the integrated terminal and follow the authentication prompt ### VS Code Local Dev Containers 1. Clone this repository 2. Open in VS Code 3. Click "Reopen in Container" when prompted +4. Run `claude` in the integrated terminal and follow the authentication prompt ## Repository Structure - `.devcontainer/devcontainer.json`: main devcontainer definition, features, VS Code extensions, and lifecycle commands - `.devcontainer/Dockerfile`: minimal image customization for Python packages -- `.github/workflows/ci.yml`: CI checks for Dockerfile linting, secret - scanning, image build, and devcontainer smoke testing -- `.github/workflows/security.yml`: scheduled and on-push Trivy image and - filesystem scans, plus SBOM generation -- `.github/dependabot.yml`: weekly GitHub Actions and Docker base-image updates +- `.github/workflows/ci.yml`: CI pipeline — Dockerfile linting, secret + scanning, image build (artifact-shared across jobs), devcontainer smoke + testing, Trivy vulnerability scanning (gates on CRITICAL), SBOM generation, + and scheduled daily security scans +- `.github/dependabot.yml`: weekly updates for GitHub Actions, Docker base + images, and devcontainer features +- `AGENTS.md`: guidance for AI agents and automated contributors working in + this repository ## Using as a Template @@ -124,8 +136,8 @@ To add Docker support when you need it: } ``` -If you add new features or packages, keep the CI workflow in sync with any new -tooling expectations you want validated during the container smoke test. +If you add new features or packages, keep the CI smoke test in sync with any +new tooling expectations you want validated during the container smoke test. ## License From 3c75296c7f500b66b231cd09363f60c1a8340ce3 Mon Sep 17 00:00:00 2001 From: Ihor Date: Fri, 26 Jun 2026 16:09:06 +0000 Subject: [PATCH 2/7] ci: pin download-artifact to SHA and fix README cadence - Pin actions/download-artifact@v4 to full commit SHA d3f86a106a0bac45b974a628896c90dbdf5c8093 (v4.3.0), per repo convention that all uses: entries must reference a 40-char SHA (AGENTS.md) - Fix README.md: dependabot.yml now runs daily, not weekly Addresses Copilot review comments on PR #44. Signed-off-by: Ihor --- .github/workflows/ci.yml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8281182..8489f0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,7 +132,7 @@ jobs: persist-credentials: false - name: Download image artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: docker-image-${{ github.sha }} path: /tmp diff --git a/README.md b/README.md index 4df86e7..16d64fa 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ created from `main` will start in approximately **10–25 seconds**. scanning, image build (artifact-shared across jobs), devcontainer smoke testing, Trivy vulnerability scanning (gates on CRITICAL), SBOM generation, and scheduled daily security scans -- `.github/dependabot.yml`: weekly updates for GitHub Actions, Docker base +- `.github/dependabot.yml`: daily updates for GitHub Actions, Docker base images, and devcontainer features - `AGENTS.md`: guidance for AI agents and automated contributors working in this repository From 2efee3c9e65bec64a78c3021c1831de67cb8bace Mon Sep 17 00:00:00 2001 From: Ihor Date: Fri, 26 Jun 2026 17:41:36 +0000 Subject: [PATCH 3/7] ci: fix scan job step ordering with continue-on-error Both Trivy scan steps now use continue-on-error: true with step IDs, so SARIF uploads, SBOM generation, and the fs scan all run to completion even when the image scan finds a CRITICAL vulnerability. A final explicit failure step gates the job on either scan's outcome. Previously, exit-code: 1 on the image scan caused the job to abort before the fs scan ran, which then caused the fs SARIF upload to fail with 'Path does not exist: trivy-fs-results.sarif'. Signed-off-by: Ihor --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8489f0b..ed65e2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -141,6 +141,8 @@ jobs: run: docker load -i /tmp/dev-template.tar - name: Run Trivy vulnerability scanner (image) + id: trivy-image + continue-on-error: true uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: 'dev-template:${{ github.sha }}' @@ -176,6 +178,8 @@ jobs: retention-days: 30 - name: Run Trivy vulnerability scanner (filesystem) + id: trivy-fs + continue-on-error: true uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: scan-type: 'fs' @@ -194,3 +198,9 @@ jobs: with: sarif_file: 'trivy-fs-results.sarif' category: 'security-analysis/trivy-filesystem' + + - name: Fail job if any Trivy scan found CRITICAL vulnerabilities + if: steps.trivy-image.outcome == 'failure' || steps.trivy-fs.outcome == 'failure' + run: | + echo "::error::Trivy found CRITICAL vulnerabilities. Review the Security tab for details." + exit 1 From 89159ed2d5e0c0611f9e64f98304768a1673c002 Mon Sep 17 00:00:00 2001 From: Ihor Date: Fri, 26 Jun 2026 17:47:15 +0000 Subject: [PATCH 4/7] ci: add ignore-unfixed to Trivy scans MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suppresses CRITICAL findings where no fixed version is available in the distro. Unpatched OS-level CVEs (e.g. kernel/stdlib) fluctuate between CRITICAL and unresolvable depending on the Trivy DB snapshot in the daily cache, causing spurious failures. This gates only on actionable findings — vulnerabilities with an available fix. Signed-off-by: Ihor --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed65e2c..242b3b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,6 +149,7 @@ jobs: format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL' + ignore-unfixed: true exit-code: '1' - name: Upload Trivy image scan results @@ -187,6 +188,7 @@ jobs: format: 'sarif' output: 'trivy-fs-results.sarif' severity: 'CRITICAL' + ignore-unfixed: true exit-code: '1' - name: Upload Trivy filesystem scan results From 6380d8d5fa2e8cd63d3f8b12b4c4ea4c108d138f Mon Sep 17 00:00:00 2001 From: Ihor Date: Fri, 26 Jun 2026 17:53:37 +0000 Subject: [PATCH 5/7] ci: disable Trivy DB cache to avoid stale CVE data The daily cache-trivy-YYYY-MM-DD key can persist a Trivy DB snapshot from earlier in the day that contains CVE entries since reclassified or patched. cache: false ensures each scan downloads the current DB, giving consistent results regardless of when the runner picks up the cached artifact. Signed-off-by: Ihor --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 242b3b1..d416b2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,6 +151,7 @@ jobs: severity: 'CRITICAL' ignore-unfixed: true exit-code: '1' + cache: false - name: Upload Trivy image scan results if: > @@ -190,6 +191,7 @@ jobs: severity: 'CRITICAL' ignore-unfixed: true exit-code: '1' + cache: false - name: Upload Trivy filesystem scan results if: > From 5d5d2798eb3c3f5d91d3e9057b7221b5a0f0baa0 Mon Sep 17 00:00:00 2001 From: Ihor Date: Fri, 26 Jun 2026 17:59:56 +0000 Subject: [PATCH 6/7] ci: add limit-severities-for-sarif to Trivy scans When format: sarif, trivy-action builds the SARIF with all severities first (ignoring the severity filter) and runs a second exit-code check. limit-severities-for-sarif: true makes both passes consistent so that ignore-unfixed is respected in the exit-code determination, not just in the SARIF output. Signed-off-by: Ihor --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d416b2b..164525b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,6 +150,7 @@ jobs: output: 'trivy-results.sarif' severity: 'CRITICAL' ignore-unfixed: true + limit-severities-for-sarif: true exit-code: '1' cache: false @@ -190,6 +191,7 @@ jobs: output: 'trivy-fs-results.sarif' severity: 'CRITICAL' ignore-unfixed: true + limit-severities-for-sarif: true exit-code: '1' cache: false From bd291f3a3c18b185b869dff2056b69349f9fe1f9 Mon Sep 17 00:00:00 2001 From: Ihor Date: Tue, 30 Jun 2026 11:45:14 +0000 Subject: [PATCH 7/7] docker: remove pebble binary to eliminate fixable HIGH CVEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /usr/bin/pebble is an ACME/TLS test server baked into the upstream base image (mcr.microsoft.com/devcontainers/base:ubuntu26.04). It is not owned by any apt package, has no systemd unit, and is never invoked at runtime — it is dead weight. Its outdated Go runtime (golang.org/x/net v0.40.0, golang.org/x/sys v0.33.0, stdlib v1.26.2) is the sole source of all fixable HIGH vulnerabilities in the image (14 CVEs including CVE-2026-25680, CVE-2026-27145, CVE-2026-33811, CVE-2026-42504, and others). Removing the binary and its data directory eliminates all of them. Verified with Trivy v0.71.2: Before: 14 fixable HIGH, 1 fixable UNKNOWN (all from usr/bin/pebble) After: 0 fixable HIGH, 0 fixable UNKNOWN The upstream base image scan (sha256:d1fc409b...) confirms the same root cause: pebble is the only source of fixable HIGH CVEs there too. Signed-off-by: Ihor --- .devcontainer/Dockerfile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 858b5a7..56ba647 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -13,4 +13,9 @@ RUN set -eux; \ apt-get -y purge linux-libc-dev; \ apt-get -y autoremove --purge; \ apt-get clean; \ - rm -rf /var/lib/apt/lists/* + rm -rf /var/lib/apt/lists/*; \ + # Remove Pebble, an unused ACME/TLS test server baked into the base + # image. It is not owned by apt, has no systemd unit, and is never run, + # yet its outdated Go runtime (golang.org/x/net, x/sys, stdlib) is the + # sole source of all fixable HIGH CVEs in the image. + rm -rf /usr/bin/pebble /var/lib/pebble