Scanners
Architecture
The argus Python SDK (argus scan) is the primary interface for running scanners. Composite actions remain available for GitHub Actions users.
- Argus SDK (
argus/) - Primary interface, works locally and in any CI - Composite Actions (
.github/actions/scanner-*/) - GitHub Actions integration - Example Workflows (
examples/github-enterprise/) - Templates for GHES users
SDK usage (recommended):
pip install argus-security
argus scan gitleaks bandit --severity-threshold high
Post-scan triage: Once you have argus-results.json, run
argus view terminal for an interactive terminal UI — filter by
severity, product, or scanner; export to CSV/JSON/Markdown/SARIF; open an
executive dashboard. Install via pip install 'argus-security[terminal]',
or combine with scanning in one shot: argus scan --interface=terminal.
Composite action usage (GitHub Actions):
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: huntridge-labs/argus/.github/actions/scanner-gitleaks@1.1.0
with:
enable_code_security: true
fail_on_severity: high
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
See examples/github-enterprise/ for GHES templates.
Table of Contents
- SAST Scanners
- CodeQL
- Gitleaks
- Bandit
- OpenGrep (Semgrep)
- Container Scanners
- Trivy Container
- Grype
- Syft (SBOM)
- Infrastructure Scanners
- Trivy IaC
- Checkov
- Malware Scanner
- ClamAV
- DAST Scanners
- ZAP
- Common Configuration Patterns
SAST Scanners
CodeQL
GitHub's semantic code analysis engine for finding security vulnerabilities and coding errors.
Supported languages: python, javascript, typescript, java, csharp, cpp, go, ruby
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `codeql_languages` | Comma-separated list of languages | `python,javascript` | No | | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `post_pr_comment` | Post findings as PR comments | `true` | No | **Example:**with:
scanners: codeql
codeql_languages: 'python,javascript,go'
enable_code_security: true
Gitleaks
Scans git history and code for hardcoded secrets, API keys, passwords, and tokens.
Scan behavior: Scans PR changes, new commits, or full history depending on event type.
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `gitleaks_enable_comments` | Enable inline PR comments | `true` | No | | `gitleaks_notify_user_list` | Users to notify (e.g., `@user1,@user2`) | `''` | No | | `gitleaks_enable_summary` | Enable job summary | `true` | No | | `gitleaks_enable_upload_artifact` | Upload SARIF artifact | `true` | No | | `gitleaks_config` | Path to custom config file | `''` | No | | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `fail_on_severity` | Fail on any secret detection | `none` | No | **Required secrets:** | Secret | Description | Required | |--------|-------------|----------| | `GITLEAKS_LICENSE` | License key from [gitleaks.io](https://gitleaks.io) | Yes (for organizations) | **Scan behavior by event type:** - `pull_request`: Scans all changes in the PR - `push`: Scans only new commits - `workflow_dispatch`/`schedule`: Full repository history scan **Example:**with:
scanners: gitleaks
gitleaks_enable_comments: true
gitleaks_notify_user_list: '@security-team'
fail_on_severity: critical
secrets:
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
Bandit
Python security linter for finding common security issues using static analysis.
Severity levels: LOW, MEDIUM, HIGH
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `post_pr_comment` | Post findings as PR comments | `true` | No | | `fail_on_severity` | Fail on any finding | `none` | No | **Example:**with:
scanners: bandit
enable_code_security: true
fail_on_severity: high
OpenGrep (Semgrep)
Fast, customizable static analysis with extensive rule sets for multiple languages.
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `post_pr_comment` | Post findings as PR comments | `true` | No | | `fail_on_severity` | Severity threshold | `none` | No | **Example:**with:
scanners: opengrep
enable_code_security: true
fail_on_severity: medium
Container Scanners
Trivy Container
Comprehensive vulnerability scanner for container images and filesystems.
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `image_ref` | Container image to scan | - | Yes | | `registry_username` | Username for private registry authentication | `''` | No | | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `post_pr_comment` | Post findings as PR comments | `false` | No | | `fail_on_severity` | Severity threshold | `none` | No | **Required secrets (for private registries):** | Secret | Description | Required | |--------|-------------|----------| | `registry_password` | Password/token for registry authentication | No | **Example:**# Public image
with:
scanners: trivy-container
image_ref: 'nginx:latest'
enable_code_security: true
fail_on_severity: critical
# Private registry
with:
scanners: trivy-container
image_ref: 'ghcr.io/myorg/myapp:latest'
registry_username: ${{ github.actor }}
enable_code_security: true
fail_on_severity: critical
secrets:
registry_password: ${{ secrets.GITHUB_TOKEN }}
Grype
Fast, accurate vulnerability scanner with excellent detection rates.
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `image_ref` | Container image to scan | - | Yes | | `registry_username` | Username for private registry authentication | `''` | No | | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `post_pr_comment` | Post findings as PR comments | `false` | No | | `fail_on_severity` | Severity threshold | `none` | No | **Required secrets (for private registries):** | Secret | Description | Required | |--------|-------------|----------| | `registry_password` | Password/token for registry authentication | No | **Example:**# Public image
with:
scanners: grype
image_ref: 'nginx:latest'
fail_on_severity: high
# Private registry
with:
scanners: grype
image_ref: 'ghcr.io/myorg/myapp:latest'
registry_username: ${{ github.actor }}
fail_on_severity: high
secrets:
registry_password: ${{ secrets.GITHUB_TOKEN }}
Syft (SBOM)
Generates detailed Software Bill of Materials (SBOM) for images and filesystems.
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `scan-path` | Directory or file path to scan | `.` | No | | `scan-image` | Container image to scan | - | No | | `registry_username` | Username for private registry authentication | `''` | No | | `enable_code_security` | Upload to GitHub Security tab | `false` | No | **Required secrets (for private registries):** | Secret | Description | Required | |--------|-------------|----------| | `registry_password` | Password/token for registry authentication | No | **Example:**# Scan filesystem
with:
scanners: sbom
scan-path: 'dist/'
# Scan public container image
with:
scanners: sbom
scan-image: 'nginx:latest'
# Scan private container image
with:
scanners: sbom
scan-image: 'ghcr.io/myorg/myapp:latest'
registry_username: ${{ github.actor }}
secrets:
registry_password: ${{ secrets.GITHUB_TOKEN }}
Exposed-port surface
Reports the network endpoints a container image declares via Dockerfile
EXPOSE — separate from whether those endpoints have known CVEs. "Image
exposes 6379/tcp" is a different question from "image has a vulnerable
Redis package" and most security reviewers want both. Runs as a
sub-scanner inside the container scanner (no new module); the data is
free since the container scanner already runs docker inspect on every
pull.
Output shape: one Finding per declared port:
INFO EXPOSE-8080-tcp Port 8080/tcp declared exposed
MEDIUM EXPOSE-22-tcp Port 22/tcp (SSH) declared exposed
MEDIUM EXPOSE-3306-tcp Port 3306/tcp (MySQL) declared exposed
Findings flow through every reporter (terminal, markdown, sarif, json,
github, gitlab, junit), --severity-threshold filtering, audit trail,
and the view-terminal / view-browser UIs without per-reporter custom
code.
Built-in risky-defaults watchlist (MEDIUM severity by default — each
entry's rationale is cited in argus/scanners/container.py::RISKY_PORTS):
| Port | Service | Port | Service |
|---|---|---|---|
| 21/tcp | FTP | 3306/tcp | MySQL |
| 22/tcp | SSH | 3389/tcp | RDP |
| 23/tcp | Telnet | 5432/tcp | PostgreSQL |
| 25/tcp | SMTP | 6379/tcp | Redis |
| 110/tcp | POP3 | 9200/tcp | Elasticsearch |
| 143/tcp | IMAP | 11211/tcp | Memcached |
| 161/udp | SNMP | 27017/tcp | MongoDB |
| 389/tcp | LDAP | ||
| 445/tcp | SMB |
Configuring via argus.yml:
scanners:
container:
image_ref: "myapp:latest"
# Default sub-scanner set; remove "exposure" to opt out
scanners: "trivy,grype,syft,exposure,services"
# Override the built-in WARN list (replaces the defaults).
# Pass [] to demote every declared port to INFO.
expose_warn_ports:
- 22/tcp
- 3306/tcp
- 8080/tcp # promote app port to WARN
# Suppress findings entirely for ports the team has accepted.
expose_ignore_ports:
- 443/tcp
- 9090/tcp
Both lists accept "PORT/PROTO" strings; bare "PORT" defaults to tcp.
Schema validator errors on malformed entries at config-load time. See
docs/config-reference.md for the full schema.
Out of scope: runtime port enumeration (start the container, probe
with nmap/ss). Static EXPOSE data is the bulk of the value at a
fraction of the operational cost.
Service enumeration
Walks the container image's filesystem at scan time, parses systemd
unit files (/etc/systemd/system, /lib/systemd/system,
/usr/lib/systemd/system) and SysV init scripts (/etc/init.d/),
and emits one Finding per declared service. Runs as a sub-scanner
inside the container scanner — same Docker requirement, no new
heavy dependencies. Works on distroless / scratch images because
file extraction goes through docker create + docker cp (tar
stream) rather than docker run. Per-file reads are bounded at 1
MB to prevent hostile-image memory blow-up.
Output shape: one Finding per service:
INFO SVC-nginx Service "nginx" declared (systemd unit)
MEDIUM SVC-sshd Service "sshd" declared (systemd unit)
MEDIUM SVC-postgresql Service "postgresql" declared (systemd unit)
Findings flow through every reporter (terminal, markdown, sarif,
json, github, gitlab, junit), --severity-threshold filtering,
audit trail, and the view-terminal / view-browser UIs without
per-reporter custom code.
Built-in risky-defaults watchlist (MEDIUM severity by default —
each entry cites a "why" in
argus/scanners/container.py::RISKY_SERVICES):
| Service | Service | Service |
|---|---|---|
| sshd | postgresql | redis-server |
| telnetd | mysqld | redis |
| vsftpd | mariadb | mongod |
| memcached | elasticsearch | snmpd |
| rpcbind | nfs-server |
Configuring via argus.yml:
scanners:
container:
image_ref: "myapp:latest"
# Default sub-scanner set; remove "services" to opt out
scanners: "trivy,grype,syft,exposure,services"
# Override the built-in WARN list (replaces the defaults).
# Pass [] to demote every declared service to INFO.
services_warn:
- sshd
- postgresql
- nginx # promote app service to WARN
# Suppress findings entirely for services the team has accepted.
services_ignore:
- cron
- rsyslog
Matching is case-insensitive. .timer, .socket, .target, and
.mount units are skipped (only .service units and init.d
scripts are reported). Schema validator errors on malformed entries
at config-load time. See
docs/config-reference.md for the full schema.
Out of scope: runtime service probing (start the container, observe what binds). Static unit-file declarations are the bulk of the value at a fraction of the operational cost.
Infrastructure Scanners
Trivy IaC
Scans Infrastructure as Code files for misconfigurations and security issues.
Supported frameworks: Terraform, CloudFormation, Kubernetes, Dockerfile
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `iac_path` | Path to IaC directory | `infrastructure` | No | | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `post_pr_comment` | Post findings as PR comments | `false` | No | | `fail_on_severity` | Severity threshold | `none` | No | **Example:**with:
scanners: trivy-iac
iac_path: 'terraform/'
enable_code_security: true
fail_on_severity: high
Checkov
Policy as Code scanner for cloud infrastructure configurations.
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `iac_path` | Path to IaC directory | `infrastructure` | No | | `framework` | IaC framework | `terraform` | No | | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `post_pr_comment` | Post findings as PR comments | `false` | No | | `fail_on_severity` | Fail on any check failure | `none` | No | **Example:**with:
scanners: checkov
iac_path: 'infrastructure/'
framework: terraform
enable_code_security: true
Malware Scanner
ClamAV
Open-source antivirus engine for detecting trojans, viruses, and malware.
Configuration & Examples
**Configuration:** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `clamav_scan_path` | Path to scan | `.` | No | | `enable_code_security` | Upload to GitHub Security tab | `false` | No | | `post_pr_comment` | Post findings as PR comments | `true` | No | | `fail_on_severity` | Fail if malware detected | `none` | No | **Example:**# Scan entire repository
with:
scanners: clamav
# Scan specific directory
with:
scanners: clamav
clamav_scan_path: 'uploads/'
fail_on_severity: critical
DAST Scanners
ZAP
ZAP (Zed Attack Proxy) provides Dynamic Application Security Testing (DAST) for running web applications and APIs.
Key Features: - Config-file driven: Define multiple scans with different targets, types, and settings in a single YAML/JSON file - Parallel scan groups: Run URL-based and container-based scans in parallel pipelines - Flexible defaults: Set defaults once, override per-scan as needed - Multiple target modes: URL (already running), docker-run (single container), or compose (multi-container) - Multiple scan types: baseline, full, or API scans with OpenAPI/Swagger specs
Quick Start (Recommended: Config File)
1. Create a ZAP config file (e.g., .github/zap-config.yml):
# Simple flat config - single target mode
defaults:
max_duration_minutes: 10
fail_on_severity: medium
allow_failure: false
target:
mode: url
scans:
- name: baseline-scan
type: baseline
target_url: https://example.com
- name: api-scan
type: api
target_url: https://api.example.com
api_spec: https://api.example.com/openapi.json
2. Run the scan:
# Via SDK
python -m argus scan zap --config argus.yml
Or via composite action in GitHub Actions:
jobs:
zap-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: huntridge-labs/argus/.github/actions/scanner-zap@1.1.0
with:
zap_config_file: .github/zap-config.yml
Configuring ZAP via argus.yml (recommended for SDK users)
ADR-024 decided that ZAP-specific tuning lives in the standard argus.yml
under scanners.zap.*, working identically across SDK-direct, composite-
action, and any-CI use. The composite action surface stays minimal
(target identification + common cross-scanner inputs); everything else
configures from one place.
scanners:
zap:
enabled: true
target_url: "https://app.example.com"
# Tuning (container backend; all optional)
scan_type: baseline # baseline | full | api (api is implicit
# when api_spec is set)
api_spec: "https://app.example.com/openapi.json"
rules_file: ".zap/rules.tsv" # mounted at /zap/wrk/rules.tsv
max_duration_minutes: 30
cmd_options:
- "-z"
- "-config view.locale=en_GB"
# Registry auth for app_image_ref pulls (private images).
# Name the env var; argus reads os.environ at scan time.
# Setting registry_password directly is back-compat-supported
# but warned at config-load if the value looks like a vendor secret.
registry_username_env: REGISTRY_USER
registry_password_env: REGISTRY_TOKEN
# Web-app authentication — ZAP context-file passthrough.
# User authors the context file (DOM selectors, logged-in regex,
# session management); argus mounts it and exports ZAP_AUTH_USERNAME
# / ZAP_AUTH_PASSWORD into the container for the {%username%} /
# {%password%} placeholders to substitute.
auth:
context_file: ".zap/context.xml"
username_env: ZAP_APP_USER
password_env: ZAP_APP_PASSWORD
See docs/config-reference.md for
the full credential-field contract and the list of every scanners.zap.*
key.
Configuration Options
#### Configuration Options **Via workflow inputs (legacy/simple mode):** | Input | Description | Default | Required | |-------|-------------|---------|----------| | `scanners` | Include `zap` (opt-in; not included in `all`) | - | Yes | | `zap_config_file` | Path to ZAP config file (YAML/JSON). **Recommended approach** - drives all scan configuration. | `''` | No | | `zap_scan_mode` | `url`, `docker-run`, or `compose` (ignored if `zap_config_file` set) | `url` | No | | `zap_target_urls` | Comma-separated URLs to scan (ignored if `zap_config_file` set) | `''` | Conditional | | `zap_scan_type` | `baseline`, `full`, or `api` (ignored if `zap_config_file` set) | `baseline` | No | | `zap_api_spec` | OpenAPI/Swagger spec URL or path (ignored if `zap_config_file` set) | `''` | Conditional | | `allow_failure` | Allow workflow to continue on failures | `false` | No | | `severity_threshold` | Minimum severity to fail (`none`, `low`, `medium`, `high`, `critical`) | `high` | No | > **Note**: When `zap_config_file` is provided, it takes precedence and other `zap_*` inputs are ignored.Config File Reference
#### Config File Reference **Schema URL**: [`zap-config.schema.json`](https://github.com/huntridge-labs/argus/blob/1.1.0/.github/actions/parse-zap-config/schemas/zap-config.schema.json) **Two config styles supported:** 1. **Flat** - single target, multiple scans 2. **Grouped** - multiple scan groups with different targets (enables parallel pipelines) ##### Flat Config Example All scans share the same target configuration:target:
mode: url # or docker-run, compose
defaults:
max_duration_minutes: 10
fail_on_severity: medium
allow_failure: false
post_pr_comment: true
scans:
- name: baseline-scan
type: baseline
target_url: https://app.example.com
- name: api-scan
type: api
target_url: https://api.example.com
api_spec: https://api.example.com/openapi.json
fail_on_severity: high # Override default
defaults:
max_duration_minutes: 10
fail_on_severity: medium
allow_failure: true
scan_groups:
# Group 1: URL-based scans (external targets)
- name: url-scans
description: "External URL Scans"
target:
mode: url
scans:
- name: baseline-prod
type: baseline
target_url: https://example.com
- name: api-prod
type: api
target_url: https://api.example.com
api_spec: https://api.example.com/openapi.json
# Group 2: Container scans (start app, then scan)
- name: docker-scans
description: "Container-based Scans"
target:
mode: docker-run
image: ghcr.io/myorg/myapp:latest
ports: "8080:8080"
defaults:
target_url: http://localhost:8080
scans:
- name: baseline-container
type: baseline
- name: full-container
type: full
max_duration_minutes: 20
target:
mode: docker-run
image: myapp:latest
ports: "3000:3000,8080:8080"
healthcheck_url: http://localhost:3000/health
# Optional: build from local Dockerfile
build:
context: .
dockerfile: ./Dockerfile
tag: myapp:test
# Optional: private registry auth
registry:
host: ghcr.io
username: ${{ github.actor }}
auth_secret: GITHUB_TOKEN # Secret name
target:
mode: compose
compose_file: docker-compose.test.yml
compose_build: true
healthcheck_url: http://localhost:8080/health
scans:
- name: authenticated-scan
type: baseline
target_url: https://app.example.com
auth:
header_name: Authorization
header_secret: API_TOKEN # GitHub secret name
# OR for non-secret values:
# header_value: "Bearer ${MY_TOKEN}"
defaults:
max_duration_minutes: 15
fail_on_severity: medium
allow_failure: false
post_pr_comment: true
target_url: http://localhost:8080 # Default target
rules_file: .zap/rules.tsv
auth:
header_name: X-API-Key
header_secret: API_KEY
Complete Examples
#### Complete Examples ##### Example 1: Simple URL Scan# .github/zap-config.yml
target:
mode: url
scans:
- name: baseline
type: baseline
target_url: https://example.com
max_duration_minutes: 5
fail_on_severity: high
# Via SDK
python -m argus scan zap --config argus.yml
# .github/zap-config.yml
target:
mode: docker-run
build:
context: .
dockerfile: Dockerfile
tag: app:test
ports: "8080:8080"
healthcheck_url: http://localhost:8080/health
defaults:
max_duration_minutes: 10
fail_on_severity: medium
target_url: http://localhost:8080
scans:
- name: baseline
type: baseline
- name: api
type: api
api_spec: http://localhost:8080/openapi.json
# .github/zap-config.yml
defaults:
max_duration_minutes: 10
fail_on_severity: medium
scan_groups:
- name: url-scans
description: "Production URL Scans"
target:
mode: url
scans:
- name: prod-baseline
type: baseline
target_url: https://example.com
- name: prod-api
type: api
target_url: https://api.example.com
api_spec: https://api.example.com/openapi.json
- name: container-scans
description: "Local Container Scans"
target:
mode: docker-run
image: myapp:latest
ports: "3000:3000"
defaults:
target_url: http://localhost:3000
scans:
- name: container-baseline
type: baseline
- name: container-full
type: full
max_duration_minutes: 20
Legacy Input-Based Configuration
#### Legacy Input-Based Configuration For simple single-scan scenarios via composite actions, you can pass inputs directly (no config file): **URL-only scan:**jobs:
zap-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: huntridge-labs/argus/.github/actions/scanner-zap@1.1.0
with:
zap_scan_mode: url
zap_target_urls: https://example.com
fail_on_severity: medium
Common Configuration Patterns
Enable GitHub Security Tab (Actions)
Upload SARIF results when using composite actions:
with:
enable_code_security: true
Disable PR Comments (Actions)
Useful for scheduled scans using composite actions:
with:
post_pr_comment: false
Scanner Selection Patterns
SDK:
# SAST only
python -m argus scan codeql opengrep bandit gitleaks
# Infrastructure only
python -m argus scan trivy-iac checkov
# Container only
python -m argus scan container
# Focused mix
python -m argus scan gitleaks trivy-iac checkov
Composite actions: Use individual scanner-* actions in your workflow steps.