Skip to content

ci: tighten lint coverage, make security scans blocking, enforce coverage threshold #36

Description

@SckyzO

ci: tighten lint coverage, make security scans blocking, enforce coverage threshold

Symptom

The CI pipeline (.github/workflows/ci.yml, security.yml) has three discipline gaps that allow regressions to merge silently:

  1. 5 of the 9 linters listed in CLAUDE.md are not executed in CI — they only run locally via make lint, which requires the Docker stack. YAML, Markdown, shell, Dockerfile, and GitHub Actions files can land with errors that local contributors do not see and CI does not catch.
  2. bandit and pip-audit in security.yml use continue-on-error: true — they run, they produce findings, but they do not fail the workflow. Weekly security scans become noise instead of signal.
  3. No coverage threshold enforced on the pytest jobCLAUDE.md targets ≥70% coverage but nothing blocks a PR that lowers it from 75% to 40%.

All three are CI-level issues that ship in one PR because they share the same review surface (.github/workflows/) and the same risk profile (low-risk YAML edits, dry-run before merge).

Technical Analysis

CICD-02 — Missing linters in CI

CLAUDE.md enumerates 9 linters in make lint. The current CI runs 4:

Linter Local (make lint) CI (ci.yml)
ruff (check + format)
mypy
ESLint
Prettier
Stylelint
yamllint
markdownlint-cli2
shellcheck
hadolint
actionlint

The five missing linters target: YAML config, Markdown documentation pages, shell scripts in scripts/, Dockerfiles, and the GitHub Actions workflows themselves (a meta-lint). All have well-maintained Docker images and run in seconds.

CICD-03 — Bandit and pip-audit not blocking

# .github/workflows/security.yml (approximate, verify exact lines at implementation)
- name: Run Bandit
  run: bandit -r src/
  continue-on-error: true   # ← findings ignored

- name: Run pip-audit
  run: pip-audit
  continue-on-error: true   # ← findings ignored

continue-on-error: true means the step is never red on the workflow badge regardless of findings. A weekly scan that finds nothing and a weekly scan that finds 10 HIGH-severity findings look identical to the operator. This is the same anti-pattern as CICD-01 / Issue #33, applied to filesystem-level scans rather than image scans.

CICD-04 — No coverage threshold

# .github/workflows/ci.yml — current pytest job
- name: Run tests
  run: pytest tests/

No --cov-fail-under=70. CLAUDE.md aspires to 70% coverage; nothing enforces it. A refactor that lowers coverage from 75% to 40% would pass CI green.

Proposed Fix

Fix CICD-02 — Add the 5 missing linters as parallel CI jobs

In .github/workflows/ci.yml, add five jobs (or one matrix job — choose based on local style; matrix is cleaner). Each uses the official Docker image, no setup steps required.

yamllint:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@<pinned>
    - run: docker run --rm -v $(pwd):/work -w /work cytopia/yamllint:1.35 -c .yamllint.yml .

markdownlint:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@<pinned>
    - run: docker run --rm -v $(pwd):/work -w /work davidanson/markdownlint-cli2:v0.13 "**/*.md"

shellcheck:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@<pinned>
    - run: docker run --rm -v $(pwd):/work -w /work koalaman/shellcheck-alpine:v0.10 sh -c 'shellcheck scripts/*.sh'

hadolint:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@<pinned>
    - run: docker run --rm -v $(pwd):/work -w /work hadolint/hadolint:v2.12-alpine hadolint --config .hadolint.yaml frontend/Dockerfile src/Dockerfile

actionlint:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@<pinned>
    - run: docker run --rm -v $(pwd):/work -w /work rhysd/actionlint:1.7 -color

Each job is independent; failures of one do not stop the others. Pin every image version (no :latest).

Image versions cited above are illustrative — at implementation time, confirm the latest stable tag and reflect it in the workflow file.

Fix CICD-03 — Remove continue-on-error on bandit and pip-audit

 - name: Run Bandit
   run: bandit -r src/
-  continue-on-error: true

 - name: Run pip-audit
   run: pip-audit
-  continue-on-error: true

Pre-merge dry-run is required: run security.yml on the candidate branch and verify it passes. If it fails:

  • Patch the finding (upgrade dependency, suppress with comment + rationale)
  • Or add an exception with explicit comment in the appropriate ignore file (.bandit, pip-audit allowlist)

The PR description must include the dry-run result. This is the same workflow as Issue #33 for Trivy.

Fix CICD-04 — Enforce coverage threshold

 - name: Run tests
-  run: pytest tests/
+  run: pytest tests/ --cov=src/rackscope --cov-report=xml --cov-report=term --cov-fail-under=70

Choose 70% (matches CLAUDE.md). Verify the current coverage is ≥70% before merging — if it's currently lower, this PR cannot land without first either raising coverage or temporarily lowering the threshold (and filing an issue to raise it back).

Pre-merge dry-run is again required. Document the current coverage figure in the PR description so reviewers can see the baseline.

Optional: upload coverage.xml to Codecov or GitHub artifact for downstream consumption. Not required for this PR.

Test Checklist

  • CI dry-run on the candidate branch: all 5 new linter jobs pass green (after fixing any pre-existing lint issues, which must be in scope of this PR)
  • CI dry-run: security.yml runs green with bandit and pip-audit blocking, or surfaced findings are addressed before merge
  • CI dry-run: pytest job runs green with --cov-fail-under=70, current coverage reported in PR description
  • Each new linter pinned to a specific image version (no latest tags)
  • All 9 linters from CLAUDE.md now run in CI (count match)
  • Existing CI jobs (ruff, mypy, ESLint, Prettier, Stylelint, pytest) unchanged

Impact and Severity

  • Audience affected: contributors and the project maintenance posture. Today: silent regressions on YAML / Markdown / shell / Dockerfile / GH Actions; silent CVE accumulation; silent coverage drift.
  • Severity: medium. Not user-facing, but each finding represents a real "guard not loaded" situation.
  • Priority: Sprint 3. Independent of all other open issues, ships when convenient. Good batch fix.

Breaking Changes

None for users. Internal CI policy change:

  • PRs that introduce new lint issues now block at CI instead of slipping through.
  • The first PR to land after this one may need extra cleanup if pre-existing lint debt is uncovered — track in the PR description and fix in scope.

Related

Out of Scope

  • Adding any new linter not already in make lint — only enforce parity with the local pipeline
  • Adding mutation testing or fuzzing — possible future addition
  • Raising the coverage threshold above 70% — separate work, file a follow-up if coverage grows organically past 80%+
  • Migrating from Docker-image-based lint runners to native GitHub Actions — current Docker approach is intentional (matches local make lint exactly)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsecuritySecurity-related issues and fixes

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions