Skip to content

ci: repo hardening (2/3) — CI workflows (shellcheck, prose-lint, CodeQL, gitleaks, Scorecard)#11

Open
ArangoGutierrez wants to merge 17 commits into
cvander:mainfrom
ArangoGutierrez:feat/7-ci-workflows
Open

ci: repo hardening (2/3) — CI workflows (shellcheck, prose-lint, CodeQL, gitleaks, Scorecard)#11
ArangoGutierrez wants to merge 17 commits into
cvander:mainfrom
ArangoGutierrez:feat/7-ci-workflows

Conversation

@ArangoGutierrez

Copy link
Copy Markdown
Contributor

What

Second of three PRs for issue #7. Adds five CI workflows plus their supporting config:

  • .github/workflows/shellcheck.yml — shellcheck on PR and push-to-main
  • .github/workflows/lint-prose.yml — yamllint + markdownlint + lychee (annotations only for the first month)
  • .github/workflows/codeql.yml — Python + JS/TS, pre-wired for the first skill PR
  • .github/workflows/gitleaks.yml — generic PII + upstream defaults + optional private overlay
  • .github/workflows/scorecard.yml — OpenSSF Scorecard, weekly
  • .github/workflows/README.md — pinning / permissions / harden-runner conventions
  • .gitleaks.toml, .markdownlint.yaml, .lycheeignore, .yamllint.yaml
  • docs/MAINTAINER-SETUP.md — expanded with GITLEAKS_PRIVATE_CONFIG format

Conventions applied

  • Every uses: pinned to a 40-char commit SHA with trailing version comment
  • Top-level permissions: {} with minimum re-grant per job
  • step-security/harden-runner@… egress-policy: audit is the first step of every job
  • No pull_request_target — fork-PR safe
  • No speculative CI: workflows validated locally before push (see below)

In-PR cleanup (not scope creep)

Adding linters to a repo with existing content requires the existing content to lint clean from day one, otherwise the first run is red and the workflow is effectively off. Three follow-on fixes are bundled here:

  • fix(shell): remove dead pattern __* in case statements — SC2221/SC2222 surfaced by shellcheck; _* already subsumes __*, no behavior change.
  • fix(docs): add blank lines around headings/lists, language tags to code fences — MD022/MD032/MD040/MD049 across existing markdown. Auto-fixed by markdownlint-cli2 --fix where possible; fences and emphasis adjusted manually.
  • ci: tune markdownlint (disable MD034, exclude data/music dirs) — MD034 (bare URLs) is noisy on prose docs; docs/music/ and data/ hold content-as-data, same category as fixtures.

Local verification (no speculative CI)

Per the repo's "no speculative CI" rule, every workflow was exercised locally before push:

Workflow How verified Evidence
shellcheck act -W shellcheck.yml with planted cd /tmp (SC2164) job failed with the planted violation, passed on clean tree
lint-prose (markdownlint) act -W lint-prose.yml -j markdownlint with planted ###Bad heading (MD018) job failed with the planted violation, passed on clean tree
lint-prose (yamllint, lychee) act -W lint-prose.yml on clean tree all three jobs succeeded
gitleaks gitleaks detect --config .gitleaks.toml with planted AKIAABCDEFGHIJKLMNOP¹ rule aws-access-token fired on the planted key, clean tree otherwise
CodeQL Not locally exercisable (autobuild needs real infra) will be observed on first push; Python leg is expected to analyze maya/, skills/*/main.py, evals/, tests/
Scorecard Not locally exercisable (needs repo context, branch protection, sigstore) first-run baseline on main

Plus the static gates: actionlint clean across all 5 workflows, yamllint --strict clean, shellcheck -S warning clean, git diff --check clean, all 17 commits GPG-signed.

¹ The canonical AKIAIOSFODNN7EXAMPLE is pre-allowlisted by upstream gitleaks defaults, so a non-canonical 20-character fake AWS key was used to exercise the rule.

Known limitations (deliberate)

  • lychee runs with fail: false — broken links are reported but do not gate merges. Intended for the first month while the link set stabilizes; a follow-up will flip it to blocking.
  • harden-runner is in audit mode on every job. After the egress baseline is known (~1 week of runs), a follow-up will flip it to block.
  • No workflow_dispatch on any workflow today — can be added in a follow-up if manual re-runs are needed between crons.
  • CodeQL Monday vs Scorecard Tuesday cron split is deliberate (avoids stepping on each other).
  • Gitleaks credit-card-number rule is broad (any 13–19 digit run). Upstream defaults catch real card-issuer tokens; this generic rule may produce some noise during audit phase.

Checklist

  • Tested locally on macOS (actionlint, shellcheck, gitleaks, yamllint, act)
  • No secrets or PII in the diff
  • Skills are CLI-invocable and fail gracefully (N/A — CI only)
  • Read AGENTS.md — this doesn't violate any hard stops
  • The bar runs fine without this (fail-safe — CI only, zero runtime impact)
  • All commits GPG-signed and DCO-signed

Refs: #7

Explains the SHA-pinning rule, least-privilege permissions discipline,
harden-runner audit-mode defaults, where each workflow surfaces its
results, and the fork-PR permission caveat.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
The pattern `_*|__*` is redundant because `_*` already matches any
string starting with an underscore, so `__*` is unreachable (SC2221,
SC2222). No behavior change — scripts continue to skip any skill
directory whose name starts with `_`.

Surfaced by the new shellcheck workflow added in the same PR.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Runs shellcheck at severity=warning on push to main and on PRs that
touch .sh/.bash files or skills/. Pinned, least-privileged, and
harden-runner-wrapped per the workflow conventions in
.github/workflows/README.md.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Disables line-length (prose), inline-HTML (badges), and
first-line-must-be-heading (CHANGELOG) rules. Everything else stays
at the markdownlint defaults.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Suppresses link-checker false positives from localhost, example.*
domains, and the issue-template new-issue URLs (which 404 until the
maintainer enables them in repo settings).

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Three parallel jobs triggered on PR and push-to-main for changes under
*.md / *.yml / *.yaml. Lychee runs with fail=false (annotations only)
for the first month to establish a clean baseline; a follow-up flips it
to blocking.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
- Disable MD034 (bare URLs): prose and research files legitimately
  contain bare URLs; mechanical <...> wrapping is not worth the churn.
- Exclude docs/music/ and data/ from markdownlint globs: these hold
  content-as-data (artist contact lists, close-out records) that share
  the fixture pattern — data, not docs.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
…de fences

Addresses markdownlint findings (MD022, MD032, MD040, MD049) surfaced
by the new lint-prose workflow:

- MD022/MD032: add required blank lines before/after headings and lists
  across skills/, docs/, evals/, research/, and top-level docs.
- MD040: tag untagged code fences as `text` (README, evals/README,
  docs/operations/closing — ASCII-art tree / form templates).
- MD049: normalize emphasis markers to asterisks in AGENTS.md.

Auto-fixed by `markdownlint-cli2 --fix`; code fences and emphasis were
adjusted manually. Verified locally via `act` against the pinned
markdownlint-cli2-action.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Matrix covers the two languages skill code is most likely to arrive in.
Today both legs find zero source files and exit quickly. When the first
.py or .ts file lands, analysis activates automatically without a CI
change. Scheduled weekly on Monday 06:00 UTC.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Extends the upstream gitleaks default rule set (cloud tokens,
generic-api-key, slack-webhook, etc.) with generic PII patterns:
email addresses, US and MX phone numbers, and credit-card-shaped
numerics. An allowlist suppresses obvious false positives in example
files, docs, issue templates, and colocated Python unit tests
(maya/**/test_*.py) that hold synthetic PII as test data. Bar-specific
PII patterns can be supplied via the GITLEAKS_PRIVATE_CONFIG
repository secret; see the gitleaks workflow for the overlay
mechanism.

Note on regex syntax: gitleaks uses Go's RE2-based stdlib engine,
which does not support lookarounds or backreferences — rules use
word boundaries and explicit separators instead.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Runs gitleaks on every PR and push-to-main using the .gitleaks.toml
rule set. If the GITLEAKS_PRIVATE_CONFIG repository secret is set,
its contents are appended to the config at CI time (runner-local, never
committed) so bar-specific PII rules can be maintained outside the
public repo. GITLEAKS_LICENSE is read if present (required only under
GitHub Organization accounts).

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Expands the optional-secrets section with the expected TOML shape of
the private overlay, so the maintainer can add bar-specific PII rules
without touching the public repo.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Runs weekly on Tuesday 06:00 UTC plus push-to-main and
branch_protection_rule changes. Publishes the public SARIF-backed
report to api.securityscorecards.dev and uploads findings to the
GitHub Security tab. id-token: write is the minimum required to sign
results for public attestation.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
The earlier resolution used `gh api .../git/ref/tags/<tag> --jq '.object.sha'`,
which for annotated tags returns the tag-object SHA rather than the
underlying commit SHA. github/codeql-action and ossf/scorecard-action
both ship annotated tags, so both were mis-pinned.

Replaces:
- github/codeql-action@51313611... (tag object) → afb54ba3... (commit),
  across 3 uses in codeql.yml and 1 use in scorecard.yml
- ossf/scorecard-action@ff5dd892... (tag object) → 62b2cac7... (commit)
  in scorecard.yml

Resolved via `gh api repos/<owner>/<repo>/commits/<tag> --jq '.sha'`,
which always returns the commit SHA. The other 7 pinned actions use
lightweight tags and were unaffected.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
The previous patterns `(^|/)test_[^/]+\.py$` and `_test\.py$`
allowlisted ANY Python test-named file anywhere in the repo, meaning
a real secret committed under a file named test_foo.py at the repo
root or in an unknown path would be silently suppressed.

Restricts the allowlist to recognized source subtrees only — maya/,
skills/, evals/, tests/ — so a misnamed file in a new subtree is
still scanned by default.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
Without these additions, a PR that only modifies a linter config
(.lycheeignore, .markdownlint.yaml, .yamllint.yaml) or the workflow
file itself would not re-run the corresponding linter. Adds the
config paths and each workflow's own path to its trigger list.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
The previous implementation left .gitleaks-private.toml in the runner
workspace after the merge step. Any subsequent artifact upload — in
this workflow or a later one — could have leaked the overlay contents.

Writes the overlay to mktemp (which GitHub-hosted runners place under
$RUNNER_TEMP, auto-wiped on job teardown) and adds a trap to clean
up on any exit path, including early failures. The public
.gitleaks.toml still gets the appended rules so gitleaks can read a
single merged config.

Refs: cvander#7
Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
@ArangoGutierrez ArangoGutierrez marked this pull request as ready for review April 23, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant