decoperations/owasp.wtf is a reusable composite action — drop it into any
workflow and it'll install the OWASP.WTF CLI, install the requested OSS
scanners (Semgrep, Gitleaks, Trivy, etc.), run a scan, and upload the report
(plus SARIF to GitHub code scanning).
# .github/workflows/security.yml
name: Security
on:
pull_request:
push:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
permissions:
contents: read
packages: read
security-events: write # for SARIF upload to code scanning
steps:
- uses: actions/checkout@v4
- uses: decoperations/owasp.wtf@v1That's it. The defaults run a standard scan with Semgrep + Gitleaks + Trivy, write a SARIF report, upload it to GitHub code scanning, and archive it as a workflow artifact.
| Input | Default | Description |
|---|---|---|
directory |
. |
Directory to scan |
mode |
scan |
Scan mode: quick, scan, deep |
format |
sarif |
Output format: terminal, json, sarif, markdown, html, fix-plan |
output |
owasp-wtf.sarif |
Report path |
ignore |
— | Comma-separated ignore globs |
fail-on |
high |
Fail the workflow when any finding ≥ this severity (critical, high, medium, low). Empty = never fail. |
install-tools |
semgrep,gitleaks,trivy |
OSS scanners to auto-install. Supported: semgrep, gitleaks, trivy, syft, grype, hadolint. |
agent |
generic |
For format: fix-plan: claude, cursor, codex, copilot, generic |
version |
latest |
CLI version to install (latest, 1.0.0, …) |
upload-artifact |
true |
Upload the report as a workflow artifact |
upload-sarif |
auto |
Upload SARIF to code scanning. auto = on when format: sarif. |
artifact-name |
owasp-wtf-report |
Name for the uploaded artifact |
node-version |
22 |
Node.js version used to run the CLI |
github-token |
${{ github.token }} |
Token for GitHub Packages install (needs read:packages) |
workspace |
— | Scope the scan to one or more workspace subdirs (comma- or newline-separated). Empty = scan the full directory. |
baseline |
— | Path to a baseline file. Findings already in the baseline are excluded from the report and from fail-on grading. |
update-baseline |
false |
Write a fresh baseline file from the current findings and exit success. Use to (re)generate the snapshot. |
include-build-output |
false |
Scan build artifact directories (.next, dist, build, .turbo, coverage, out). Off by default. |
cache-cli |
true |
Cache the CLI tarball at ~/.cache/owasp-wtf/. Skips re-download when the same version is already cached (handy on self-hosted runners). |
| Output | Description |
|---|---|
report-path |
Path of the generated report |
findings |
Total finding count (when format: json or format: sarif) |
score |
Risk score 0–100 (when format: json) |
The action calls one of the v2 CLI subcommands depending on mode:
| Mode | Adapters | Use |
|---|---|---|
quick |
native + gitleaks | Pre-commit / fast PR check |
scan (default) |
+ semgrep + trivy | Normal PR / push gate |
deep |
+ syft + grype + hadolint | Nightly / pre-release |
- uses: actions/checkout@v4
- uses: decoperations/owasp.wtf@v1
with:
fail-on: highpermissions:
security-events: write
steps:
- uses: actions/checkout@v4
- uses: decoperations/owasp.wtf@v1
# format defaults to sarif; SARIF is auto-uploaded to code scanning- uses: actions/checkout@v4
- id: scan
uses: decoperations/owasp.wtf@v1
with:
format: markdown
output: owasp-report.md
fail-on: '' # don't fail the job — we just want the comment
- name: Comment report on PR
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('${{ steps.scan.outputs.report-path }}', 'utf8');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});- uses: actions/checkout@v4
- uses: decoperations/owasp.wtf@v1
with:
format: fix-plan
output: SECURITY_FIX_PLAN.md
agent: claude
fail-on: ''
- uses: actions/upload-artifact@v4
with:
name: security-fix-plan
path: SECURITY_FIX_PLAN.md- uses: decoperations/owasp.wtf@v1
with:
directory: apps/api
ignore: "**/*.test.ts,**/__generated__/**"Pass workspace to scope a scan to one or more sub-packages. Combine with
baseline so the first rollout suppresses pre-existing findings and the gate
fails only on net-new ones.
permissions:
security-events: write
pull-requests: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: decoperations/owasp.wtf@v1
with:
mode: scan
workspace: |
apps/web
packages/shared
baseline: owasp-baseline.json
fail-on: highTo (re)generate the baseline, run the action with update-baseline: 'true'
on a one-off branch and commit the resulting file.
- uses: decoperations/owasp.wtf@v1
with:
baseline: owasp-baseline.json
update-baseline: 'true'
- run: git add owasp-baseline.json && git commit -m "chore: refresh OWASP baseline" && git pushcache-cli: 'true' is the default. On a persistent self-hosted runner it
keeps repeated installs to a cp rather than a network download. Use
'false' to force a fresh download every run.
- uses: decoperations/owasp.wtf@v1
with:
cache-cli: 'true'on:
schedule:
- cron: '0 6 * * *'
jobs:
deep-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: decoperations/owasp.wtf@v1
with:
mode: deep
install-tools: semgrep,gitleaks,trivy,syft,grype,hadolint
format: sarif
fail-on: critical- uses: actions/checkout@v4
- uses: decoperations/owasp.wtf@v1
with:
format: json
output: owasp-wtf.json
artifact-name: owasp-json-report- uses: decoperations/owasp.wtf@v1
with:
version: 1.0.0If you'd rather install scanners yourself (e.g. via cache), set
install-tools: ''. The CLI will gracefully skip any missing tool and still
produce a report from whatever is installed (including the bundled native
rules).
- uses: actions/checkout@v4
- name: Cache and install Semgrep
run: pipx install semgrep
- uses: decoperations/owasp.wtf@v1
with:
install-tools: ''For reproducible CI, pin by tag or SHA:
- uses: decoperations/owasp.wtf@v1 # latest v1.x
- uses: decoperations/owasp.wtf@<full-commit-sha> # exact commit| Permission | Why |
|---|---|
contents: read |
Check out the repo |
packages: read |
Install the CLI from GitHub Packages |
security-events: write |
Upload SARIF to GitHub code scanning |
pull-requests: write |
Only if you're posting comments via github-script |
jobs:
scan:
permissions:
contents: read
packages: read
security-events: writeThe action definition lives in action.yml at the repo root.
PRs welcome — see CONTRIBUTING.md.
This repo runs OWASP.WTF against itself on every PR via
.github/workflows/dogfood.yml. It scans
with the CLI built from PR source and with the published composite action,
uploads SARIF to GitHub code scanning, and posts a sticky summary comment on
the PR. It's a working real-world example of wiring the action into CI.