What It Does
| Scanner |
Engine |
AI Layer |
| Secrets |
Titus · 487 rules · Hyperscan |
--ai-filter-secrets eliminates false positives |
| SCA |
osv-scalibr + osv.dev · 20 ecosystems |
--ai-sca-reachability checks if the vuln is actually called |
| SAST |
Together AI · Qwen/Qwen3-Coder-Next-FP8 + regex pre-filter |
Source-to-sink data flow · 17 deterministic patterns · priority scoring |
| Dockerfile |
AI-powered · Dockerfile, Containerfile, Compose |
Privilege escalation, secret exposure, dangerous mounts |
| Container |
go-containerregistry + osv.dev · Alpine, Debian, Ubuntu, RHEL |
OS package CVEs with layer attribution |
| License |
File-based detection · 13 license types |
Policy engine: allowed_licenses / denied_licenses in .broly.yaml |
| SBOM |
osv-scalibr · 20 ecosystems |
broly sbom generates CycloneDX 1.5 or SPDX 2.3 with PURLs |
Install
go install github.com/Shasheen8/Broly/cmd/broly@latest
Pre-built Linux binaries on Releases. macOS: build from source with brew install vectorscan && make build for Hyperscan support.
SAST and AI features are powered by Together AI and require an API key:
export TOGETHER_API_KEY=your_key_here
Usage
broly scan # all scanners, current directory
broly scan /path/to/project # specific path
# Individual Scanners
broly scan --secrets # secrets only
broly scan --sca # SCA only
broly scan --sast # SAST only (requires TOGETHER_API_KEY)
# AI Enhancements
broly scan --ai-filter-secrets # filter secrets false positives with AI
broly scan --ai-sca-reachability # check if vulnerable deps are actually called
broly scan --ai-triage # verdict (TP/FP) + fix suggestion per finding
broly scan --ai-triage --explain # + concise attack-scenario sentence per finding
# Container Scanning
broly scan --container alpine:3.19 # scan a container image for vulnerabilities
broly scan --container ./image.tar # scan from tarball
# Output
broly scan -f json # JSON output
broly scan -f sarif -o results.sarif # SARIF 2.1.0 for GitHub Code Scanning
broly scan --min-severity high # only high and critical
# SBOM Generation
broly sbom # CycloneDX 1.5 to stdout
broly sbom -f spdx -o sbom.json # SPDX 2.3 to file
# License Policy
broly scan --config .broly.yaml # license findings emitted when allowed_licenses / denied_licenses set
# Config
broly scan --config .broly.yaml # load project config file
broly scan --baseline .broly-baseline.yaml # suppress known FPs / require specific findings
broly scan --incremental # skip unchanged files (uses .broly-cache.json)
Scanner Output
SAST
A fast regex pre-filter catches 18 known vulnerability patterns instantly (SQL injection, hardcoded secrets, XSS sinks, weak crypto, etc.). Then the LLM traces data flow from source to sink and finds what static rules miss.
▸ SAST (4 findings)
SEVERITY ISSUE FILE DESCRIPTION
──────────────────────────────────────────────────────────────────────────────────────────────────
CRITICAL SQL injection via unsanitize.. api/handlers.py:10 User input flows directly ..
CRITICAL OS command injection via uns.. api/handlers.py:15 OS command injection via u..
HIGH Path traversal in read_file api/handlers.py:20 Path traversal in read_fil..
HIGH Insecure deserialization via.. api/handlers.py:25 Insecure deserialization v..
Dockerfile and Compose
Auto-detected during normal scans. Specialized prompts cover privilege escalation, hardcoded secrets, dangerous mounts, unpinned base images, and more.
▸ DOCKERFILE (4 findings)
SEVERITY ISSUE FILE DESCRIPTION
──────────────────────────────────────────────────────────────────────────────────────────────────
CRITICAL Hardcoded secrets in ENV/ARG Dockerfile:3 Secrets visible in image hi..
CRITICAL Docker socket mounted docker-compose.yml:14 Full control over Docker da..
HIGH curl piped to bash Dockerfile:10 Compromised script runs as ..
MEDIUM Running as root (no USER) Dockerfile:1 Increases blast radius of a..
Secrets
487 rules. --ai-filter-secrets reads surrounding code context and eliminates placeholders and test values:
▸ SECRETS (3 findings)
SEVERITY RULE FILE REDACTED
──────────────────────────────────────────────────────────────────────────────────
HIGH AWS API Key config/example.py:6 AKIA****MPLE
HIGH AWS API Credentials config/example.py:6 AKIA****KEY"
HIGH GitHub Personal Access Token config/example.py:9 ghp_****8B4a
SCA
19 ecosystems, 50+ lockfile formats. --ai-sca-reachability checks if the vulnerable function is actually called:
▸ SCA (3 findings)
SEVERITY VULN ID PACKAGE VERSION FIXED ECOSYSTEM
──────────────────────────────────────────────────────────────────────────────────────────────────
MEDIUM GHSA-9hjg-9r4m-mvj7 requests 2.31.0 no patch PyPI
MEDIUM GHSA-496j-2rq6-j6cc grpcio 1.54.0 no patch PyPI
MEDIUM GHSA-cfgp-2977-2fmm grpcio 1.54.0 no patch PyPI
Container
--container pulls an image, extracts OS packages, and matches against OSV. Each finding shows which layer introduced it:
▸ CONTAINER (5 findings)
SEVERITY VULN ID PACKAGE VERSION FIXED ECOSYSTEM LAYER
────────────────────────────────────────────────────────────────────────────────────────────────────────────────
MEDIUM ALPINE-CVE-2023-42.. busybox 1.36.1-r2 no patch Alpine:v3.18 #1
MEDIUM ALPINE-CVE-2025-26.. musl 1.2.4-r1 no patch Alpine:v3.18 #1
AI Triage
--ai-triage labels each finding TRUE/FALSE positive with a confidence score and a fix. For container/SCA findings with no patch, it suggests mitigations. --explain adds a one-sentence attack scenario:
CRITICAL SQL injection via unsanitize.. api/handlers.py:10 User input flows directly ..
🔺 TRUE_POSITIVE [HIGH] User input flows directly into raw SQL query without parameterization
An attacker sends id=1 OR 1=1 to dump the entire users table.
fix:
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
HIGH Path traversal in read_file api/handlers.py:20 Path traversal in read_fil..
🟢 FALSE_POSITIVE [HIGH] File path is validated against an allowlist before use
Developer Feedback Loop
Check a box in the PR comment to mark a finding as a false positive. Broly verifies write access, commits the fingerprint to .broly-baseline.yaml, and the finding never surfaces again.
- [ ] 🔴 CRITICAL · SQL injection in get_user() · api/handlers.py:7
Suppressions accumulate over time; each repo builds its own false positive memory.
GitHub App
Install once, scans every PR automatically. No per-repo workflow setup.
The app clones the repo at the PR head, runs Broly with AI triage, and posts findings as a check run + PR comment. Only findings in changed files are reported — no historic noise.
# run the app server locally
APP_ID=123456 PRIVATE_KEY_PATH=./app.pem WEBHOOK_SECRET=your_secret go run ./cmd/broly-app
Configuration
Config file
[!TIP]
.broly.yaml is loaded automatically from the repo root. CLI flags always override it.
min_severity: low
exclude_paths:
- vendor
- .git
workers: 8
# License policy (findings only emitted when configured)
allowed_licenses:
- MIT
- Apache-2.0
- BSD-2-Clause
- BSD-3-Clause
- ISC
denied_licenses:
- GPL-3.0
- AGPL-3.0
Baseline
[!NOTE]
suppress silences known false positives. require asserts specific findings must be detected every scan; missing entries cause a non-zero exit.
suppress:
- fingerprint: "abc123..."
reason: "test fixture"
require:
- rule_id: "SQL-INJECTION"
file: "api/handlers.py"
reason: "SQL injection in user lookup - must be detected"
Inline suppression
query = "SELECT * FROM users WHERE id = " + user_id # broly:ignore
query = f"SELECT * FROM users WHERE id = {user_id}" # broly:ignore SQL-INJECTION
Acknowledgments
License
MIT. See LICENSE for the full text.