diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 9864e80..519aa58 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -26,7 +26,8 @@ env: jobs: lint: name: lint - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 15 steps: - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -61,7 +62,8 @@ jobs: zizmor: name: zizmor - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -72,7 +74,8 @@ jobs: secrets: name: secrets - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 steps: - name: checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 @@ -90,7 +93,8 @@ jobs: images: name: list images - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 10 outputs: matrix: ${{ steps.list.outputs.images }} steps: @@ -105,7 +109,8 @@ jobs: image-scan: name: scan ${{ matrix.image }} needs: images - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 + timeout-minutes: 20 strategy: fail-fast: false matrix: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..8916e39 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security + +This is a practice repo: a local Ethereum devnet and its monitoring, not a +production service. No keys are committed; the validators use the public +interop keys. + +To report a problem (a leaked secret, an unsafe default someone could copy, +a vulnerable pin), open a private report under the Security tab ("Report a +vulnerability"), not a public issue. Expect a reply within a week. diff --git a/docs/ci-standard.md b/docs/ci-standard.md new file mode 100644 index 0000000..58a09ca --- /dev/null +++ b/docs/ci-standard.md @@ -0,0 +1,58 @@ +# CI standard + +The CI rules this repo follows. The same standard covers my other practice +repos; this copy holds the parts that apply here: Baseline and Containers. +Sources: [OpenSSF Scorecard](https://github.com/ossf/scorecard/blob/main/docs/checks.md), +GitHub's *Security hardening for GitHub Actions*, and the +[zizmor](https://docs.zizmor.sh/audits/) audits. + +A rule that is not met is listed under "Gaps" with the reason. + +## Baseline + +| ID | Rule | Why | +|---|---|---| +| B1 | Top-level `permissions: contents: read`; writes granted per job | least token for a compromised step | +| B2 | Actions pinned to a commit sha; tool images by digest | tags move, shas do not | +| B3 | Dependabot keeps the pins current | a pin that never moves collects CVEs | +| B4 | `actions/checkout` with `persist-credentials: false` | no token left in `.git/config` | +| B5 | actionlint and zizmor on every change | broken expressions, injection, dangerous triggers | +| B6 | Untrusted values reach `run:` only through `env:` | `${{ }}` in `run:` is shell injection | +| B7 | yamllint `--strict`, shellcheck | cheap, catches real mistakes | +| B8 | Secret scan of the tree | a leaked key is found before it spreads | +| B9 | Hygiene script: English only, no AI attribution in this repo | one script enforces the repo's rule | +| B10 | Superseded runs are cancelled | stale checks waste minutes | +| B11 | Weekly scheduled run | pins do not change, advisories do | +| B12 | Runner image pinned (`ubuntu-24.04`); `timeout-minutes` on every job | no silent image change, no 6 h hung job | +| B13 | `main` protected: no force-push, no deletion | history cannot be rewritten or lost | +| B14 | `LICENSE` and `SECURITY.md` | how to report a problem | + +## Containers + +| ID | Rule | Why | +|---|---|---| +| C1 | Every image reference pinned, checked by a script | no floating tag reaches the devnet | +| C2 | Image CVE scan: CRITICAL, ignore unfixed; accepted findings have a reason and an expiry | the weekly run fails once an expiry passes | +| C3 | Compose files render; Prometheus rules and rule tests pass | broken config fails before it is started | + +## Where each rule is met + +| ID | Where | +|---|---| +| B1, B2, B4, B6, B10, B11, B12 | `.github/workflows/security.yml` | +| B3 | `.github/dependabot.yml` (github-actions) | +| B5, B7, B9 | `security.yml`, `lint` and `zizmor` jobs; B9 is `.github/scripts/hygiene.sh` | +| B8 | `security.yml`, `secrets` job (trivy, `trivy-secret.yaml`) | +| B13 | branch protection on `main`: force-push and deletion blocked | +| B14 | `LICENSE`, `SECURITY.md` | +| C1 | `.github/scripts/list-images.sh --check` | +| C2 | `security.yml`, `image-scan` job, `.trivyignore.yaml` | +| C3 | `.github/scripts/compose-config.sh`, promtool steps in `lint` | + +## Gaps + +| ID | Gap | Why | +|---|---|---| +| B3 | dependabot does not update the client images | they are set through `${VAR}` and the devnet pin file, which dependabot cannot read; upgrades are done by hand with a soak, and C2 flags the ones that matter | +| B13 | no required checks before a push to `main` | changes go straight to `main`; a failed check turns the commit red but does not block it | +| C1 | client images are pinned to exact tags, not digests | `list-images.sh --check` rejects floating tags; the CI tool images are pinned by digest |