← Bubbles Aro projects

Security Testing Toolkit

Automated security testing for any app (Node, Python, Java, Go, PHP, React, Flutter, and more). Four free industry-standard scanners run together and produce one combined report. Start a scan with a few clicks on GitHub or with one command on your computer.

Contains: .github/workflows/security-scan.yml · security/scan.sh · security/summarize.py · security/scan-org.sh · README.md

Video walkthrough (about 1.5 minutes)

How it works, installing on Mac and Windows, running a scan, reading the report, and scanning every repo in your company.

0:00

What it checks

Each scanner looks for a different kind of problem. Together they cover most of the common risks in the OWASP Top 10.

Secrets

Gitleaks

API keys, passwords, tokens and private keys committed to your code or anywhere in git history.

Code (SAST)

Semgrep

Insecure code patterns: SQL injection, XSS, command injection, weak crypto, unsafe deserialization.

Dependencies + config

Trivy

Libraries with known CVEs (npm, pip, Maven, Go, Composer…) and Dockerfile, Terraform, Kubernetes misconfigurations.

Live app (DAST)

OWASP ZAP

Scans your running app from outside: missing security headers, insecure cookies, information leaks. Runs only when you give it a URL.

How it works

Your code→Gitleaks→Semgrep→Trivy→ZAP (optional)→report.html + summary

Every scanner runs inside Docker, so you don't install any of them. Each writes a JSON result. summarize.py merges them into one report sorted by severity, with a "How to fix" column. The run fails if anything at or above your chosen severity (default high) is found, which lets you block risky code from merging.

When it runs on GitHub

TriggerWhen
ManualActions → Security Scan → Run workflow (the "few clicks" option)
PushEvery push to main / master
Pull requestEvery PR, so issues are caught before merging
ScheduleEvery Monday 09:00 IST, which catches newly published CVEs in libraries you already use

Setup (one time, about 2 minutes)

  1. Download and unzipUse the download button at the top of this page.
  2. Copy the files into your project's root folder
    # run from the folder where you unzipped
    cp -r security-testing-toolkit-main/.github security-testing-toolkit-main/security /path/to/your-project/

    On Windows, copy the .github and security folders with File Explorer. Turn on "Show hidden items" to see .github.

  3. Ignore the report folder
    cd /path/to/your-project
    echo "security-reports/" >> .gitignore
    chmod +x security/scan.sh
  4. Commit and push
    git add .github security .gitignore
    git commit -m "Add automated security scanning"
    git push

    The first scan starts automatically after the push.

Run a scan

  1. Open your repo on GitHub → Actions tab
  2. Click "Security Scan" in the left sidebar → "Run workflow"
  3. Optional: paste your staging URLThis also scans the live app. Leave it blank to scan code only.
  4. Pick "fail on" severity and click the green Run workflow button
  5. View resultsThe summary table appears on the run page. Scroll down to Artifacts, download security-reports, and open report.html.

Scan your whole company GitHub

Check every repository in your organization in one go. You get a company report with the counts for each repo, and each repo links to its full report.

  1. Install the GitHub CLI and sign in once
    # Mac
    brew install gh
    # Windows (PowerShell)
    winget install GitHub.cli
    
    gh auth login
  2. Run the org scanReplace your-company with your GitHub organization name. It clones each repo, scans it, then deletes the copy.
    ./security/scan-org.sh your-company
    
    # only the 20 most recently updated repos
    ./security/scan-org.sh your-company 20
  3. Open the company reportorg-reports/index.html lists every repo with its critical, high, medium and low counts.
  4. Keep every repo covered automaticallyAdd .github/workflows/security-scan.yml and the security/ folder to each repo, so every push and pull request is scanned too.

Private repos are included when your GitHub account can access them. Nothing is uploaded anywhere: the scans run on your computer.

Settings (optional)

On GitHub: Repo → Settings → Secrets and variables → Actions → Variables tab → New repository variable.

VariableExampleWhat it does
SECURITY_TARGET_URLhttps://staging.myapp.comRuns the live ZAP scan on every automatic run
SECURITY_FAIL_ONhigh · critical · medium · noneLowest severity that fails the run (default high)

Block risky pull requests

Settings → Branches → Add rule for main → turn on "Require status checks to pass" → select security-scan. A PR with high or critical findings then can't be merged.

The same variables work locally as environment variables: SECURITY_TARGET_URL and SECURITY_FAIL_ON.

Reading the results

report.html shows a count per severity, then every finding with the tool, the issue, the file or URL, and how to fix it. You can filter by severity or tool and search.

SeverityMeaningWhat to do
CRITICALEasy to exploit, serious impactFix immediately, before anything ships
HIGHReal risk; leaked secrets are always highFix before the next release
MEDIUMNeeds specific conditions to exploitFix this sprint
LOW / INFOHardening suggestionsReview; fix if cheap

Files in security-reports/

FileContents
report.htmlCombined, filterable report. Start here.
summary.mdShort summary (also shown on the GitHub run page)
gitleaks.json · semgrep.json · trivy.json · zap.jsonRaw output from each tool
zap.htmlZAP's own detailed report (only if a URL was scanned)

Fixing common issues

Secret found (Gitleaks)

Rotate the key first. Once it's in git history, assume it's compromised. Deleting the line isn't enough. Then move it to an environment variable or a secrets manager:

// before
const apiKey = "sk_live_abc123...";
// after
const apiKey = process.env.API_KEY;
Vulnerable dependency (Trivy)

Upgrade to the version shown in "How to fix", then run the scan again.

npm install lodash@4.17.21        # Node
npm audit fix                     # Node, fix all safe upgrades
pip install -U "django>=4.2.16"   # Python
SQL injection (Semgrep)

Never build queries by joining strings. Use parameters:

// bad
db.query("SELECT * FROM users WHERE id = " + req.params.id);
// good
db.query("SELECT * FROM users WHERE id = $1", [req.params.id]);
Missing security headers (ZAP)
// Express
npm install helmet
app.use(require("helmet")());

# Django settings.py
SECURE_HSTS_SECONDS = 31536000
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = "DENY"
False positive: how to ignore a finding
ToolHow to ignore
SemgrepAdd // nosemgrep (or # nosemgrep) on that line
GitleaksAdd the finding's fingerprint to .gitleaksignore in the repo root
TrivyAdd the CVE ID, e.g. CVE-2024-1234, to .trivyignore

Only ignore after checking it really doesn't apply, and add a comment explaining why.

What the tools can't catch: test these yourself

Automated scanners miss business-logic bugs. These are among the most common real-world vulnerabilities, so add tests for them to your normal test suite:

TestExpected result
Call a protected API without logging in401 Unauthorized
Log in as User A, request User B's record (change the ID in the URL or body)403 / 404, never B's data
Normal user calls an admin endpoint403 Forbidden
Send ' OR 1=1-- or <script>alert(1)</script> in inputsRejected or safely escaped
Wrong password 20 times in a rowRate-limited or locked out
Upload a .php / .exe file disguised as an imageRejected
// Example (Jest + supertest)
test("user cannot read another user's order", async () => {
  const res = await request(app)
    .get("/api/orders/" + orderOfUserB.id)
    .set("Authorization", "Bearer " + tokenOfUserA);
  expect([403, 404]).toContain(res.status);
});

To practice finding these, use PortSwigger Web Security Academy (free labs) and OWASP Juice Shop (a deliberately vulnerable app you run locally).

FAQ

Is the live (ZAP) scan safe?

The baseline scan is passive: it crawls pages and inspects responses without sending attacks. Only scan apps you own or have permission to test, and use staging or local rather than production.

How long does a scan take?

Usually 2–6 minutes on GitHub. The first local run takes longer while Docker downloads the scanner images.

The run failed. Is that broken?

Usually not. A failed run means findings at or above your fail_on level were found. Open the summary to see them. To see results without failing, set SECURITY_FAIL_ON=none.

Does it work with GitLab / Bitbucket?

The local script security/scan.sh works anywhere Docker runs, so you can call it from any CI. The ready-made pipeline file is for GitHub Actions.

My app on localhost isn't reachable by ZAP

The script automatically rewrites localhost to host.docker.internal. Make sure your app listens on 0.0.0.0 rather than only 127.0.0.1, and that Docker Desktop is running.

Does it cost anything?

No. All four tools are free and open source. GitHub Actions is free for public repos and includes free minutes for private repos.