Lightweight multi-language SAST & code-quality orchestrator — a self-hosted, open-source alternative to SonarQube.
PolyScan does not reinvent parsers. It orchestrates best-of-breed OSS engines (Semgrep, Bandit, ESLint, …) and normalizes their output into one Finding schema, applies a Quality Gate, and renders reports for CLI, PR comments, and a web dashboard.
| Single engine | PolyScan | |
|---|---|---|
| Multi-language | one tool per language | one command, all engines |
| Output format | each tool differs | unified Finding schema |
| Quality Gate | manual | built-in, exit-code |
| PR comment | DIY | Markdown report ready |
| History/Trend | none | planned dashboard |
# with uv (recommended — uses uv.lock for reproducible installs)
uv sync --extra dev --extra scan
uv run polyscan scan ./myrepo
# or classic pip
pip install polyscan
pip install semgrep bandit
npm install -g eslint# create lockfile and project venv
uv lock
uv sync --extra dev --extra scan
# eslint is a Node tool and remains npm-managed
npm install -g eslint# scan a repo (auto-detects languages via engines)
polyscan scan ./myrepo
# only Python + JS
polyscan scan ./myrepo -e bandit -e eslint
# JSON for CI / SARIF pipelines
polyscan scan ./myrepo --format json
# SARIF (GitHub code scanning compatible) -> file
polyscan scan ./myrepo --format sarif -o polyscan.sarif
# CycloneDX SBOM from dependency manifests
polyscan sbom ./myrepo -o bom.jsonWith uv:
uv run polyscan scan ./myrepo
uv run polyscan sbom ./myrepo -o bom.jsonPolyScan ships a container image (published to GHCR on every main push):
# pull from GHCR (auto-built by CI)
docker pull ghcr.io/sraisl/polyscan:latest
# scan a local repo — mount it at /code
docker run --rm \
-v /pfad/zu/deinem/repo:/code \
ghcr.io/sraisl/polyscan:latest scan /code \
-e semgrep -e bandit -e eslint --format md
# SARIF into a host volume
docker run --rm \
-v /pfad/zu/deinem/repo:/code \
-v $(pwd)/out:/out \
ghcr.io/sraisl/polyscan:latest scan /code \
--format sarif -o /out/polyscan.sarif
# SBOM
docker run --rm \
-v /pfad/zu/deinem/repo:/code \
-v $(pwd)/out:/out \
ghcr.io/sraisl/polyscan:latest sbom /code -o /out/bom.jsonOr build the image locally (no publish needed):
docker build -t polyscan:local .
docker run --rm -v $(pwd):/code polyscan:local scan /code --format mdPolyScan ships as a reusable Action (Docker-based, image from GHCR). Drop it into any workflow:
jobs:
polyscan:
runs-on: ubuntu-latest
permissions:
security-events: write # upload SARIF to code scanning
contents: read
steps:
- uses: actions/checkout@v7
- name: PolyScan self-scan
uses: sraisl/polyscan@v1
with:
target: "."
engines: "semgrep,bandit,eslint" # add "spotbugs" for Java/Kotlin
format: "sarif"
- name: Upload SARIF to GitHub code scanning
uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: polyscan.sarif
- name: Job Summary
if: always()
run: cat polyscan-summary.md >> "$GITHUB_STEP_SUMMARY"The Action writes two artifacts into the runner workspace:
polyscan.sarif— consumed byupload-sarifpolyscan-summary.md— a Markdown report you can pipe into$GITHUB_STEP_SUMMARY(shown above) or post as a PR comment.
Notes:
- The Action pulls
ghcr.io/sraisl/polyscan:latest(auto-built & pushed by this repo's CI on everymainpush). - Pin to a release tag (e.g.
sraisl/polyscan@v1) — Dependabot will bump it to@v2automatically when released (configued withversioning-strategy: increase).@mainis not tracked by Dependabot. - Add
spotbugstoenginesfor Java/Kotlin (needs a JDK on the runner; the action installs one if missing).
Fails (exit 1) when:
- ≥ 1 critical finding, or
- ≥ 1 high finding, or
- ≥ 50 medium findings
Configure thresholds in polyscan.core.gate.QualityGate.
uvicorn polyscan.server:app --port 8000
# open http://localhost:8000
# scan: GET /scan?target=/path/to/repo- SARIF export (GitHub Code Scanning compatible)
- CycloneDX SBOM
- Docker image published to GHCR
- Historical trends dashboard (per-branch/per-repo)
- More engines: golangci-lint, PMD, hadolint, Trivy (IaC)
- GitHub PR-Comment bot
AGPL-3.0