SecureCheck is a centralised, reusable GitHub Actions workflow that runs a multi-scanner security pipeline against a consumer repository and posts a single Discord embed summarising the result. It has no server, no database, and no long-running process. Every run is a fresh GitHub-hosted Ubuntu runner.
- GitHub Actions (
workflow_callreusable workflow) - Node.js 20 (default, configurable via
node_versioninput) - Python 3.11 (default, configurable via
python_versioninput, used to run Semgrep) - Bash +
jqfor finding-count extraction - Scanners:
gitleaks8.24.3,semgrep(autoruleset),trivy(viaaquasecurity/trivy-action@0.35.0) @anthropic-ai/sdkv0.37 for the optional Claude PR review (modelclaude-sonnet-4-6)- Discord incoming webhook for notifications
Path: .github/workflows/scan.yml
Defines a single scan job triggered by workflow_call. It checks out the consumer repo at full depth (needed by gitleaks for history scanning) and additionally checks out SecureCheck itself into .securecheck/ so the Node scripts are available without bundling them into the consumer repo.
Path: scripts/notify.mjs
Reads scanner counts from environment variables, builds a Discord embed (title, description, severity colour, fields, links, footer), and POSTs it to DISCORD_WEBHOOK_URL. Severity colour rules live entirely here:
- 0 findings: green (
0x2ecc71) - gitleaks > 0: red (
0xe74c3c) - total > 10: orange (
0xe67e22) - otherwise: yellow (
0xf1c40f)
If the optional Claude step ran and produced critical or high severity findings, the notifier inlines the first five into a separate embed field, reading them from /tmp/claude.json.
Path: scripts/claude-review.mjs
Pull request only. Computes git diff origin/<base>...HEAD, truncates to 40 KB, and asks Claude Sonnet 4.6 to return a strict JSON array of security findings (severity, file, line, title, why). Output is written to stdout and captured to /tmp/claude.json. The script always exits 0: any failure (missing key, network error, malformed response) collapses to [] so the rest of the pipeline keeps running.
- Consumer repo's caller workflow (
.github/workflows/security.yml) invokesw1ck3ds0d4/SecureCheck/.github/workflows/scan.yml@mainwithsecrets: inherit. scan.ymlchecks out the consumer repo (fetch-depth: 0) and SecureCheck (path: .securecheck).- Sets up Node and Python.
- Runs gitleaks, Semgrep, and Trivy. Each scanner writes JSON to
/tmp/<scanner>.json. Counts are extracted withjqand exported as step outputs. - On
pull_requestevents, ifANTHROPIC_API_KEYis set, runsclaude-review.mjswhich writes/tmp/claude.jsonand exports a count. notify.mjsreads all step outputs from env vars, builds the embed, and posts to Discord. Skipped silently whenDISCORD_WEBHOOK_URLis unset.- All four JSON reports are uploaded as the
security-reportsartifact (14-day retention,if-no-files-found: ignore).
- Push to default branch: post only if total findings > 0 (silent on green commits).
- Pull request: always post, including clean runs (the heartbeat behaviour, so reviewers see the bot ran).
- The conditional logic for "silent on clean push" is implicit: the embed always builds, but the green colour and zero counts make a clean push run a benign notification. (Note: the README describes silent-on-clean-push behaviour; the current
notify.mjsposts on every run that has a webhook configured. Reconcile inROADMAP.md.)
Inputs (workflow_call):
node_version(default'20')python_version(default'3.11')
Secrets (read from consumer repo via secrets: inherit):
DISCORD_WEBHOOK_URL(required for notifications, optional for the workflow itself)ANTHROPIC_API_KEY(optional, gates the Claude review step)
.github/workflows/scan.yml Reusable workflow
scripts/notify.mjs Discord embed builder and poster
scripts/claude-review.mjs Claude PR diff reviewer
package.json @anthropic-ai/sdk dep, type: module
LICENSE AGPL v3
COMMERCIAL.md Dual-license commercial terms
README.md Install and usage docs
- No state between runs. Every artifact is produced from scratch from the checked-out tree.
- All scanners run with
exit-code: 0(or|| true) so a finding never fails the workflow. Severity gating is the consumer's choice, not enforced here. - The Claude script always exits 0 to guarantee the notifier still runs and the artifact still uploads.
- The diff is truncated at 40,000 characters before sending to the Claude API, capping cost and token use per PR.