GitHub Action
Run OWASP Noir in GitHub Actions workflows for endpoint discovery and optional passive security scanning.
Run OWASP Noir in GitHub Actions for endpoint discovery and optional passive security scanning during CI.
Quick Start
Minimal workflow:
name: Noir Security Analysis
on: [push, pull_request]
jobs:
noir-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Run OWASP Noir
id: noir
uses: owasp-noir/noir@main
with:
base_path: '.'
- name: Display results
run: echo '${{ steps.noir.outputs.endpoints }}' | jq .
base_path: Directory to analyze (-b/--base-path)endpoints: JSON output for post-processing
Inputs
| Name | Description | Required | Default |
|---|---|---|---|
base_path |
Base path to analyze (equivalent to -b/--base-path) |
Yes | . |
url |
Base URL for endpoints (equivalent to -u/--url) |
No | `` |
format |
Output format (plain, yaml, json, jsonl, markdown-table, curl, httpie, oas2, oas3, etc.) |
No | json |
output_file |
Write results to a file (equivalent to -o/--output) |
No | `` |
techs |
Technologies to include (equivalent to -t/--techs) |
No | `` |
exclude_techs |
Technologies to exclude (--exclude-techs) |
No | `` |
passive_scan |
Enable passive security scan (-P/--passive-scan) |
No | false |
passive_scan_severity |
Minimum severity for passive scan (critical, high, medium, low) |
No | high |
use_all_taggers |
Enable all taggers (-T/--use-all-taggers) |
No | false |
use_taggers |
Enable specific taggers (--use-taggers) |
No | `` |
include_path |
Include source file paths in results (--include-path) |
No | false |
verbose |
Verbose output (--verbose) |
No | false |
debug |
Debug output (-d/--debug) |
No | false |
concurrency |
Concurrency level (--concurrency) |
No | `` |
exclude_codes |
Exclude HTTP response codes (comma‑separated) (--exclude-codes) |
No | `` |
status_codes |
Display HTTP status codes for discovered endpoints (--status-codes) |
No | false |
ai_provider |
AI provider prefix or full custom URL (--ai-provider) |
No | `` |
ai_model |
AI model name (--ai-model) |
No | `` |
ai_key |
AI API key (--ai-key) |
No | `` |
ai_agent |
Enable agentic AI workflow (--ai-agent) |
No | false |
probe |
Fire HTTP requests at discovered endpoints (--probe) |
No | false |
probe_via |
Route probes through a proxy URL (--probe-via) |
No | `` |
export_es |
Index endpoints in Elasticsearch or OpenSearch (--export-es) |
No | `` |
export_webhook |
POST endpoint catalog as JSON to a webhook URL (--export-webhook) |
No | `` |
diff_path |
Old code version path for diff analysis (--diff-path) |
No | `` |
diff_ref |
Git revision to diff the checkout against, e.g. the pull request base; fetched if the checkout lacks it (--diff-ref) |
No | `` |
fail_on |
Fail the step (exit code 3) when the diff has any of added, removed, changed, auth-removed (--fail-on) |
No | `` |
no_log |
Hide execution logs and show only results (--no-log) |
No | true |
Notes:
- Pass boolean options as strings (
'true'/'false') output_filewrites to a file; the step outputs are produced regardless- The
endpoints/passive_resultsoutputs are only populated forformat: 'json'or'jsonl'. Pick one of those if a later step consumes them
Outputs
| Name | Description |
|---|---|
endpoints |
The full Noir result document ({"endpoints":[...],"passive_results":[...]}) when format is json or jsonl; empty for any other format |
passive_results |
A JSON array of passive-scan findings when format is json or jsonl ([] when passive scan is off or found nothing); empty for any other format |
Consuming outputs:
- name: Count endpoints
run: echo '${{ steps.noir.outputs.endpoints }}' | jq '.endpoints | length'
- name: Show passive issues (if enabled)
run: echo '${{ steps.noir.outputs.passive_results }}' | jq '. | length'
Examples
Advanced Scan
name: Comprehensive Security Analysis
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Run OWASP Noir with Passive Scanning
id: noir
uses: owasp-noir/noir@main
with:
base_path: 'src'
format: 'json'
passive_scan: 'true'
passive_scan_severity: 'medium'
use_all_taggers: 'true'
include_path: 'true'
verbose: 'true'
output_file: 'noir-results.json'
- name: Process Results
run: |
echo "🔍 Endpoints discovered:"
echo '${{ steps.noir.outputs.endpoints }}' | jq '.endpoints | length'
echo "🚨 Security issues found:"
echo '${{ steps.noir.outputs.passive_results }}' | jq '. | length'
- name: Save detailed results
uses: actions/upload-artifact@v4
with:
name: noir-security-results
path: noir-results.json
Monorepo Matrix
name: Monorepo Noir
on: [push, pull_request]
jobs:
noir:
runs-on: ubuntu-latest
strategy:
matrix:
service: [service-a, service-b, service-c]
steps:
- uses: actions/checkout@v6
- name: Run Noir for ${{ matrix.service }}
id: noir
uses: owasp-noir/noir@main
with:
base_path: '${{ matrix.service }}'
format: 'json'
include_path: 'true'
Attack Surface Review on Pull Requests
Diff the pull request against its base branch, post the result as a comment, and fail the check when a route loses its authentication:
name: Attack Surface Review
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
noir-diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Diff the attack surface
uses: owasp-noir/noir@main
with:
base_path: '.'
diff_ref: ${{ github.event.pull_request.base.sha }}
format: 'markdown-table'
output_file: 'noir-diff.md'
fail_on: 'auth-removed'
- name: Comment on the pull request
if: always()
env:
GH_TOKEN: ${{ github.token }}
run: gh pr comment "${{ github.event.pull_request.number }}" --body-file noir-diff.md
actions/checkout fetches a single commit by default. If diff_ref isn't in the checkout, the action fetches that revision before scanning. The step fails with exit code 3 when the diff matches fail_on, and the report file is still written, so the comment step above runs with if: always(). For inline annotations instead of a comment, use format: 'sarif' and upload the file with github/codeql-action/upload-sarif.
Framework-Specific Scans
- uses: owasp-noir/noir@main
with:
base_path: '.'
techs: 'rails' # ruby on rails
passive_scan: 'true'
- uses: owasp-noir/noir@main
with:
base_path: 'src'
techs: 'express' # node.js express
format: 'json'
- uses: owasp-noir/noir@main
with:
base_path: '.'
techs: 'django' # python django
passive_scan: 'true'
passive_scan_severity: 'medium'
Status Code Configuration
- uses: owasp-noir/noir@main
with:
base_path: '.'
status_codes: 'true' # include HTTP status codes
exclude_codes: '404,429' # suppress noisy codes
Alternate Formats
- uses: owasp-noir/noir@main
with:
base_path: '.'
format: 'markdown-table' # or: 'curl', 'httpie', 'yaml', 'jsonl', 'oas3'
output_file: 'noir.md'
Best Practices
- Enable passive scanning (
passive_scan: 'true') to surface security smells early. - Tune noise with
passive_scan_severityandexclude_codes. - Include paths (
include_path: 'true') to speed up triage and code navigation. - Pin frameworks with
techswhen auto‑detection isn’t enough; useexclude_techsto avoid irrelevant analyzers. - Persist results with
actions/upload-artifact
Troubleshooting
No endpoints found:
- Verify
base_pathpoints to source directory - Check repository contains supported frameworks
- Specify
techsexplicitly
Output too large or slow:
- Use
format: 'jsonl'for streaming - Narrow
base_pathor usetechs/exclude_techs
Hard to diagnose:
- Enable
debug: 'true'andverbose: 'true' - Use
include_path: 'true'for traceability
HTTP status noise:
- Disable with
status_codes: 'false'or useexclude_codes
Implementation Notes
- Runs in Docker container for consistency
- Inputs map directly to CLI flags
- See supported technologies:
noir list techs