Blackout Secure Code Scanning Kit
ActionsAbout
Copyright © 2025-2026 Blackout Secure | Apache License 2.0
A drop-in composite GitHub Action that detects what's in your repo → runs the right scanners → audits repo posture → uploads one unified SARIF to GitHub Advanced Security.
Everything in one Marketplace install: secret scanning, workflow linting, shell linting, and a posture auditor that checks the Advanced Security toggles, workflow permissions, branch protection, required reviews, and CODEOWNERS for every branch you care about.
- Posture audit — 30+ rules (
PS001–PS033) covering GHAS toggles, workflowpermissions:blocks, branch protection, required reviews, signed commits, status checks, conversation resolution, force-push restrictions, and CODEOWNERS ownership coverage. Per-rule severity is configurable. - Bundled scanners (v1.0) — actionlint for workflow YAML,
gitleaks for secrets across the working tree, and shellcheck
for
*.sh/*.bash. Each runs conditionally based on what ecosystems are detected. - Ecosystem detection — walks the working tree and surfaces what's present: Python / JavaScript / TypeScript / Go / Java / C# / Ruby / Rust / shell, plus Dockerfiles, Compose files, GitHub workflows, Terraform, Kubernetes manifests, and package-manager lockfiles.
- Unified SARIF upload — every scanner's findings and every posture
finding land in a single SARIF that is uploaded to GitHub Advanced
Security under one category (
bos-code-scanning-kit), so they all appear on the repo Security tab. .bos-scan.ymlconfig — per-repo policy lives in one human-readable YAML file at the repo root. Defaults are safe; you only declare what you want to change.- Pure-stdlib Python core — no third-party Python deps beyond
PyYAML. The composite Action installs the kit on the runner with a singlepip install.
- GitHub-hosted Linux runner (
ubuntu-latestor newer) — the kit installspythonviaactions/setup-python@v5automatically. - For the posture audit: a token with
reposcope. The default${{ secrets.GITHUB_TOKEN }}is enough for the code-scanning probe (PS001). Secret-scanning (PS002), Dependabot (PS003), and branch-protection probes (PS020-PS025) require a PAT — see SCANNING_PAT — advanced posture credentials for the full tick / don't-tick checklist (classic and fine-grained). - For the SARIF upload:
security-events: writein your workflowpermissions:block.
name: Code scanning
on:
push:
branches: [main, dev]
pull_request:
branches: [main, dev]
schedule:
- cron: '17 4 * * 1' # weekly Monday 04:17 UTC
permissions:
contents: read
security-events: write # upload SARIF
actions: read # workflow context
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: blackoutsecure/bos-code-scanning-kit@v1
with:
# Prefer SCANNING_PAT when the org/repo has set it (unlocks
# PS002 / PS003 / PS020-PS025); otherwise fall back to the
# workflow's built-in GITHUB_TOKEN (PS001 only — the other
# posture rules emit `skip` rows). See § 'SCANNING_PAT —
# advanced posture credentials' below for the PAT recipe.
github_token: ${{ secrets.SCANNING_PAT || secrets.GITHUB_TOKEN }}That's it. The kit auto-discovers your ecosystem, runs every applicable scanner, audits posture, and uploads a single SARIF.
The
secrets.SCANNING_PAT || secrets.GITHUB_TOKENform is safe to ship before you've created the PAT — when the secret is unset, the expression evaluates tosecrets.GITHUB_TOKENand the kit runs in baseline mode (PS001 only). AddingSCANNING_PATat the org/repo level later upgrades every consuming workflow automatically with no code changes.
Pick a uses: ref shape based on how strict your supply-chain posture
needs to be. All three forms are supported equally.
| Form | Example | When to use |
|---|---|---|
| Floating major (default) | blackoutsecure/bos-code-scanning-kit@v1 |
Friendly default. Auto-tracks every v1.x.y patch + minor release as we ship bug fixes and new rules. Recommended for most callers. |
| Immutable tag | blackoutsecure/bos-code-scanning-kit@v1.0.0 |
Pin to a specific release. Predictable scan results across runs; requires manual bumps for new fixes. Recommended when failed scans break critical pipelines. |
| SHA-pinned | blackoutsecure/bos-code-scanning-kit@<40-char-sha> # v1.0.0 |
Strictest. Survives even a malicious tag-move on the kit repo (the tj-actions/changed-files class of supply-chain attack). Recommended for regulated / high-security callers. Use Dependabot's package-ecosystem: github-actions to keep the pin current. |
The SHA for any tag is git rev-list -n 1 v1.0.0 against this repo, or
the commit field of the GitHub Release JSON.
| Input | Default | Description |
|---|---|---|
owner |
(none) | GitHub owner of the repo being scanned. Defaults to the workflow context. |
repo |
(none) | GitHub repo name being scanned. Defaults to the workflow context. |
config |
(none) | Path to .bos-scan.yml. Defaults to auto-discovery at the repo root. |
github_token |
(none) | Token used by the posture audit (PS001 code scanning, PS002 secret scanning, PS003 Dependabot alerts, PS020-PS025 branch protection). Leave empty to fall back to the workflow's built-in GITHUB_TOKEN, which is enough for PS001 only. PS002/PS003/PS020-PS025 require a PAT with admin reach — by org convention stored as a secret named SCANNING_PAT. See the kit README § 'SCANNING_PAT — advanced posture credentials' for the classic / fine-grained tick checklist and the recommended caller pattern. |
enable_posture |
true |
true to run the posture audit step. |
enable_scanners |
true |
true to run the bundled scanners (actionlint / gitleaks / shellcheck). |
enable_upload |
true |
true to upload the merged SARIF to GitHub Advanced Security. |
fail_on |
fail |
fail (default) — exit non-zero if posture has any FAIL findings or any scanner reports a result. never — collect findings but always exit 0 (useful for first-time rollouts). |
http_timeout |
20 |
Per-request HTTP timeout (seconds) for the posture audit's GitHub REST calls. Default 20. Each probe is independent, so the practical upper bound on a posture run is roughly http_timeout * number-of-probes (~10 on a baseline scan). Bump on self-hosted runners with slow egress, or to ride out brief GitHub API latency spikes that otherwise surface as PS*** error: HTTP 502 rows. Bare integer string; no unit. |
sarif_output |
bos-scan.sarif |
Path for the merged SARIF artefact. |
The table above is auto-generated from
action.ymlbyscripts/render_readme_inputs.py. Editaction.ymland runpython3 scripts/render_readme_inputs.py --write.
| Output | Description |
|---|---|
sarif_path |
Path to the merged SARIF file produced by the run. |
posture_failures |
Number of FAIL findings from the posture audit. |
outcome |
Severity-tier verdict for the run: success (no findings at any level), warn (only warning/note-level findings — nothing the enforcement policy would block on), or failure (at least one error-level finding from the posture audit or any scanner). Reflects severity only — it does NOT change based on fail_on, so callers can gate pipelines on the verdict independently of whether the kit step itself exited non-zero. |
The posture audit can run with either:
secrets.GITHUB_TOKEN(baseline visibility)secrets.SCANNING_PAT(recommended for full posture coverage)
Recommended caller pattern:
with:
github_token: ${{ secrets.SCANNING_PAT || secrets.GITHUB_TOKEN }}With only GITHUB_TOKEN, rules that require admin-level visibility
(for example branch protection and some security settings) may emit
skip findings. Using SCANNING_PAT upgrades those to real
pass/warn/fail evaluations.
Minimum PAT guidance:
- Fine-grained PAT (preferred): grant read access only to the selected repositories and security/admin metadata needed by posture checks.
- Classic PAT (fallback):
reposcope. - Store as an Actions secret named
SCANNING_PAT.
Severities can be overridden per rule in .bos-scan.yml.
| Rule | Default | What it checks |
|---|---|---|
| PS001 | warn | GitHub code scanning is enabled via either Default setup or an Advanced workflow uploading CodeQL analyses. |
| PS002 | warn | GitHub secret scanning is enabled (probed via the secret-scanning alerts API). |
| PS003 | warn | Dependabot vulnerability alerts are enabled. |
| PS004 | warn | Secret-scanning push protection is enabled (refuses pushes that contain detected secrets; toggles independently of PS002). |
| PS010 | warn | Every workflow file declares an explicit top-level permissions: block. |
| PS011 | warn | No workflow uses permissions: write-all at the workflow or job level. |
| PS012 | warn | Every third-party uses: reference (in .github/workflows/ and .github/actions/) is pinned to a 40-char commit SHA. Local (./) and docker:// refs are exempt; trusted owner/repo entries can be added to allow_tag_pin. |
| PS020 | warn | The branch has some branch-protection rule configured. |
| PS021 | warn | The branch requires at least N approving reviews (per-branch override). |
| PS022 | warn | The branch restricts force pushes (allow_force_pushes.enabled = false). |
| PS023 | warn | The branch requires status checks to pass before merge. |
| PS024 | warn | The branch requires signed commits. |
| PS025 | warn | The branch requires conversation resolution before merge. |
| PS030 | warn | A CODEOWNERS file is present (root, .github/, or docs/). |
| PS031 | warn | Every non-comment CODEOWNERS line references at least one owner (@user or @org/team). |
| PS032 | warn | (opt-in) Every @org/team referenced in CODEOWNERS exists. Requires validate_users_exist: true. |
| PS033 | warn | (opt-in) Every @user referenced in CODEOWNERS exists. Requires validate_users_exist: true. |
PS000 is reserved for tooling errors (e.g. missing token) and is
always emitted at error severity.
Every scanner output is normalised to SARIF 2.1.0 and merged with the
posture findings into a single upload artefact (bos-scan.sarif by
default). All third-party binaries are version-pinned and downloaded
fresh per run; no scanner is sourced from latest.
| Scanner | Status | Version | Triggered when… | What it scans | Rule prefix |
|---|---|---|---|---|---|
| actionlint | ✅ v1.0 | v1.7.1 |
.github/workflows/*.{yml,yaml} exists |
GitHub Actions workflow YAML (syntax, expressions, embedded run: shell) |
actionlint-native |
| gitleaks | ✅ v1.0 | v8.21.2 |
Always (when enable_scanners: true) |
Secrets across the working tree (API keys, tokens, private keys, etc.) | gitleaks-native |
| shellcheck | ✅ v1.0 | v0.10.0 |
**/*.sh or **/*.bash exists |
Shell-script issues (POSIX compliance, quoting, race conditions) | SCNNNN |
This table covers every external tool currently used by the shipped kit.
| Tool | Purpose | License | Site |
|---|---|---|---|
| actionlint | Lint GitHub Actions workflow syntax, expressions, and embedded shell usage. | MIT | https://github.com/rhysd/actionlint |
| gitleaks | Detect secrets in the repository working tree. | MIT | https://github.com/gitleaks/gitleaks |
| ShellCheck | Analyze shell scripts for correctness, quoting, and portability issues. | GPL-3.0 | https://www.shellcheck.net |
| PyYAML | Parse .bos-scan.yml and related YAML configuration data. |
MIT | https://pyyaml.org |
Only the tools listed above are executed by the current action.yml.
The scanner roster above is driven by the ecosystem detector
(src/scan_kit/detect.py), which classifies
the working tree along three axes. Anything not in this list will be
silently ignored.
| Axis | Recognised values |
|---|---|
| Languages | python · javascript · typescript · go · java · csharp · ruby · rust · shell |
| Build artefacts | Dockerfile · docker-compose · GitHub workflows · Terraform · Kubernetes manifests · Helm charts · shell scripts |
| Package managers | pip · pyproject · poetry · npm · yarn · pnpm · go modules · maven · gradle · cargo · bundler · nuget |
| CodeQL targets | python · javascript-typescript · go · java-kotlin · csharp · ruby · rust (mapped from detected languages) |
Every field is optional — the kit ships safe defaults. A representative full file:
# .bos-scan.yml — Blackout Secure Code Scanning Kit config
owner: blackoutsecure
project_name: my-action
email: security@example.com
scan:
tools: auto # auto | explicit | none
exclude: [] # scanners to skip even if their fingerprint matches
fail_on: high # critical | high | medium | low | never
codeql:
languages: [] # explicit CodeQL languages; empty => auto-detect
exclude_languages: []
posture:
ghas:
require_code_scanning: warn # fail | warn | skip
require_secret_scanning: warn
require_dependabot_alerts: warn
workflows:
require_permissions_block: warn
forbid_write_all: warn
require_pinned_actions: warn # PS012 — fail | warn | skip
allow_tag_pin: [] # owner/repo entries exempted from PS012 (e.g. ['actions/checkout'])
branches:
main:
required_reviews: 2
restrict_force_push: true
require_status_checks: true
require_signed_commits: true
require_conversation_resolution: true
severity: fail
dev:
required_reviews: 1
severity: warn
codeowners:
require_file: warn
validate_users_exist: false # set true to probe each @user/@org-team via APIUnknown top-level keys are ignored so that future kit versions can extend the schema without breaking older callers.
The kit also ships a standalone bos-scan CLI for local triage or
non-GitHub CI:
pip install bos-code-scanning-kit
# Detect ecosystems
bos-scan detect --root .
# Validate config
bos-scan validate --root .
# Posture audit (requires GITHUB_TOKEN)
export GITHUB_TOKEN=<your-token>
bos-scan posture \
--owner your-org \
--repo bos-code-scanning-kit \
--root . \
--sarif posture.sarif
# Merge multiple SARIFs
bos-scan sarif \
--input gitleaks.sarif \
--input actionlint.sarif \
--posture posture.sarif \
--output bos-scan.sarifIssues and PRs are welcome on dev. Run the tests with:
pip install -e .
pip install -r requirements-dev.txt
pytest test/ -v
ruff check src testBlackout Secure Code Scanning Kit is not certified by GitHub. It is provided by a third-party and is governed by separate terms of service, privacy policy, and support documentation.