Skip to main content

secscan

Static analysis engine extracted from SecScan. It walks JavaScript and TypeScript trees, looks for hardcoded secrets and sensitive API paths, scores the workspace, and fails CI when a policy is exceeded.

Python package secscan-sast and CLI secscan. The same engine ships as the Ruby gem secscan. The React dashboard stays in the original repository. The PyPI name cannot be secscan because it collides with sec-scan.

pip install secscan-sast
secscan .
secscan . --fail-on high --max-risk 50
secscan . --format sarif --output secscan.sarif

Serialized reports mask the matched value. Use --reveal-secrets only when the artifact is restricted. The in-memory Finding object still keeps the literal.

Contents

What it does

Capability Detail
Secret scan AWS, GCP/Gemini, GitHub, Stripe, JWT, Slack, private keys, database URIs, hardcoded passwords, OpenAI, SendGrid
Route scan Admin/internal paths and hardcoded /api, /v1, /graphql, /webhook endpoints
Entropy Shannon entropy; a match below minEntropy is dropped
File walk .js, .jsx, .ts, .tsx, .mjs, .cjs, .json, .env, .yaml, .yml
Ignore node_modules, vendor, dist, build, lockfiles, plus --ignore
Score Severity × file criticality, then an impact score from 0 to 100
Reports table, json, csv, sarif, markdown
CI gate --fail-on and --max-risk, exit 1

Use cases

Pre-commit or local review. Scan a branch before you open a PR. The default table is meant for a terminal.

secscan ./src --fail-on high

CI quality gate. Block a merge when a critical secret lands, or when the workspace impact score goes above the team cap.

secscan . --fail-on critical --max-risk 50 --format json --output secscan.json

GitHub Code Scanning. Emit SARIF 2.1.0 and upload it so findings show on the Security tab.

secscan . --format sarif --output secscan.sarif

Compliance export. CSV for a spreadsheet, Markdown for an audit note.

secscan . --format csv --output findings.csv
secscan . --format markdown --output findings.md

Library in a Python tool. Call secscan.scan_path or secscan.scan_text from a script, a bot, or a larger AppSec pipeline.

Custom policy. Add org-specific regex in a JSON file and merge it with the built-in set.

secscan . --rules ./examples/poc/custom-rules.json

Proof of concept

The repo ships a tiny tree under examples/poc: one source file with public sample values (the official AWS example access key, a fake database URI, and an admin route).

# from the package root
PYTHONPATH=src python3 -m secscan examples/poc --format table

After pip install secscan-sast:

secscan examples/poc --format table
secscan examples/poc --format json
secscan examples/poc --fail-on high; echo $?

The POC source is:

// Proof of concept only. Values are public samples, not live credentials.
const awsKey = "AKIAIOSFODNN7EXAMPLE";
const db = "postgres://admin:SuperSecretPass123@db.prod.internal:5432/main";

app.get("/api/v1/admin/users", handler);

Expected findings (masked in every serialized format):

Rule Severity Why it fires
sec-aws-akid CRITICAL AWS access key pattern
sec-db-uri CRITICAL Database URI with a password
sec-sensitive-api-path MEDIUM /api/v1/admin/...
sec-api-endpoint LOW Hardcoded /api/... path

The same file also maps GET /api/v1/admin/users as a sensitive API endpoint.

A clean tree exits 0 and prints no findings:

mkdir -p /tmp/secscan-clean && echo 'const ok = 1;' > /tmp/secscan-clean/app.js
secscan /tmp/secscan-clean --fail-on critical --format table

Installation

pip install secscan-sast

From this repository, without publishing:

pip install -e .
secscan --version

Requires Python 3.10 or newer.

CLI

Usage: secscan [path] [options]

  --format FORMAT        table (default), json, sarif, csv, markdown
  --rules FILE           extra rules JSON, merged with the built-in set
  --ignore LIST          extra ignore patterns, comma-separated
  --fail-on LEVEL        critical | high | medium | low | info
  --max-risk SCORE       fail if impact score (0-100) exceeds SCORE
  --output FILE          write the report to FILE instead of stdout
  --reveal-secrets       include the matched literal (restricted artifacts only)
  --version              print version

path may be a file or a directory. Default is ..

Exit Meaning
0 Scan finished; quality gate passed or was not set
1 Quality gate failed (--fail-on or --max-risk)
2 Invalid path, rules file, or CLI option

--output writes the chosen format to a file and prints Report written to … on stderr. --fail-on / --max-risk still apply.

Output formats

Every serialized format masks secrets unless you pass --reveal-secrets. JSON never includes matchedSecret by default.

table (default)

Human-readable terminal report.

SecScan Static Security Analysis
Target: examples/poc | Files: 1 | Findings: 4 | 4ms
Impact: 66/100 (HIGH) | Security score: 43/100
------------------------------------------------------------------------
CRITICAL [AWS Access Key ID]
  File:    src/app.js:2
  Secret:  AKIA••••••••••••MPLE (entropy 3.84)
  Snippet: const awsKey = "AKIA••••••••••••MPLE";
  Info:    AWS public access key hardcoded in source.

json

Machine-readable report for CI, bots, and later processing.

{
  "scanner": "SecScan SAST",
  "version": "0.1.0",
  "target": "examples/poc",
  "totalFiles": 1,
  "scannedFilesCount": 1,
  "ignoredFilesCount": 0,
  "findings": [
    {
      "id": "finding-1",
      "ruleId": "sec-aws-akid",
      "ruleName": "AWS Access Key ID",
      "category": "CLOUD_CREDENTIAL",
      "severity": "CRITICAL",
      "file": "src/app.js",
      "line": 2,
      "column": 17,
      "snippet": "const awsKey = \"AKIA••••••••••••MPLE\";",
      "maskedSecret": "AKIA••••••••••••MPLE",
      "entropy": 3.84,
      "fileCriticality": "MEDIUM",
      "fileCriticalityWeight": 1.0,
      "weightedScore": 25.0
    }
  ],
  "apiEndpoints": [
    {
      "id": "endpoint-1",
      "file": "src/app.js",
      "line": 5,
      "method": "GET",
      "path": "/api/v1/admin/users",
      "isInternalOrAdmin": true
    }
  ],
  "metrics": {
    "criticalCount": 2,
    "highCount": 0,
    "mediumCount": 1,
    "lowCount": 1,
    "infoCount": 0,
    "securityScore": 43,
    "securityImpactScore": 66,
    "impactLevel": "HIGH",
    "totalWeightedRisk": 60.0,
    "averageEntropy": 4.12
  },
  "durationMs": 4
}

Finding fields: id, ruleId, ruleName, category, severity, file, line, column, snippet, maskedSecret, entropy, description, remediation, fileCriticality, fileCriticalityWeight, weightedScore. With --reveal-secrets, matchedSecret is added.

csv

One row per finding. Header:

ID,Severity,Rule Name,Category,File,Line,Column,Entropy,Masked Secret,Remediation
finding-1,CRITICAL,AWS Access Key ID,CLOUD_CREDENTIAL,src/app.js,2,17,3.84,AKIA••••••••••••MPLE,Use IAM roles, environment variables, or AWS Secrets Manager.

Useful in Sheets or Excel. Endpoints are not included; use JSON if you need them.

sarif

OASIS SARIF 2.1.0 for GitHub Code Scanning (github/codeql-action/upload-sarif).

{
  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
  "version": "2.1.0",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "SecScan",
          "semanticVersion": "0.1.0",
          "informationUri": "https://github.com/saulofilho/secscan"
        }
      },
      "results": [
        {
          "ruleId": "sec-aws-akid",
          "level": "error",
          "message": { "text": "AWS Access Key ID. Value: AKIA••••••••••••MPLE" },
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": { "uri": "src/app.js" },
                "region": { "startLine": 2, "startColumn": 17 }
              }
            }
          ]
        }
      ]
    }
  ]
}

CRITICAL and HIGH map to SARIF error. Everything else maps to warning.

markdown

Audit-style document for pull requests or tickets.

# SecScan report

- **Target:** `examples/poc`
- **Files scanned:** 1 (ignored: 0)
- **Findings:** 4
- **Security score:** 43/100
- **Impact score:** 66/100 (HIGH)

## Findings

### [CRITICAL] AWS Access Key ID
- **File:** `src/app.js` (line 2)
- **Value:** `AKIA••••••••••••MPLE`
- **Entropy:** 3.84

Impact and file counts in the samples above are representative of examples/poc. Entropy and duration can vary slightly by platform.

Quality gates

--fail-on uses this order: info < low < medium < high < critical. A finding at the chosen level or above fails the process.

secscan . --fail-on critical    # only CRITICAL fails the build
secscan . --fail-on high        # HIGH and CRITICAL fail
secscan . --max-risk 50         # impact score 51+ fails
secscan . --fail-on high --max-risk 50

Stderr on failure:

SecScan quality gate: findings with severity >= HIGH
SecScan quality gate: impact score 66 above 50

A scan with findings still exits 0 if you set neither flag.

Scoring

Each finding gets weightedScore = severityWeight × fileCriticality.

Severity Weight
CRITICAL 25
HIGH 14
MEDIUM 7
LOW 3
INFO 1
File class Multiplier Examples
CRITICAL 2.0 .env, secrets, keys, Dockerfile, config/
HIGH 1.5 services/, routes/, auth, payments
MEDIUM 1.0 regular application code
LOW 0.5 tests, docs, fixtures
impactScore = 100 × (1 − e^(−totalWeightedRisk / 55))
Impact score Level
0 NOMINAL
1–14 LOW
15–34 MODERATE
35–59 ELEVATED
60–79 HIGH
80–100 CRITICAL

securityScore starts at 100 and subtracts 25 per CRITICAL, 12 per HIGH, 5 per MEDIUM, 2 per LOW.

Built-in rules

ID Severity Category
sec-aws-akid CRITICAL CLOUD_CREDENTIAL
sec-aws-secret CRITICAL CLOUD_CREDENTIAL
sec-github-pat CRITICAL AUTH_TOKEN
sec-stripe-secret CRITICAL API_KEY
sec-private-key CRITICAL PRIVATE_KEY
sec-db-uri CRITICAL DATABASE_URI
sec-openai-key CRITICAL API_KEY
sec-google-api HIGH API_KEY
sec-jwt-token HIGH AUTH_TOKEN
sec-slack-webhook HIGH AUTH_TOKEN
sec-hardcoded-pass HIGH PASSWORD
sec-sendgrid-key HIGH API_KEY
sec-sensitive-api-path MEDIUM API_PATH
sec-api-endpoint LOW API_PATH

Rule IDs, names, severities, categories, descriptions, and remediations are English.

Custom rules

--rules loads a JSON array and appends it to the built-in set. See examples/poc/custom-rules.json.

[
  {
    "id": "sec-demo-token",
    "name": "Demo corp token",
    "pattern": "\\bCORP-[A-Z0-9]{24}\\b",
    "severity": "HIGH",
    "category": "CUSTOM",
    "description": "Internal token format for the POC.",
    "remediation": "Move CORP-* tokens to an environment variable.",
    "flags": "g",
    "minEntropy": 3.0
  }
]
Field Required Notes
id yes Stable rule id
name yes Shown in reports
pattern yes Python/JavaScript-style regular expression
severity no INFO, LOW, MEDIUM, HIGH, CRITICAL (default MEDIUM)
category no Default CUSTOM
description no
remediation no
flags no g, i, or gi. (?i) prefixes are accepted
minEntropy / min_entropy no Drop matches below this Shannon value
enabled no Default true

An invalid file exits 2 (rules file not found or rules file is not valid JSON).

Ignore patterns

Always skipped: node_modules, vendor, bower_components, .git, dist, build, out, coverage, .cache, *.min.js, *.bundle.js, package-lock.json, yarn.lock, pnpm-lock.yaml.

--ignore adds comma-separated patterns:

secscan . --ignore "tests/*,docs/*,*.spec.ts"

Supported shapes: tests/*, vendor/**, *.test.*, package-lock.json, or a directory name such as fixtures.

Programmatic API

import secscan
from secscan.report import render

report = secscan.scan_path("./src", ignore=["tests/*"])
report.metrics.security_impact_score  # 0..100
report.metrics.impact_level           # NOMINAL, LOW, MODERATE, ELEVATED, HIGH, CRITICAL
for finding in report.findings:
    print(f"{finding.severity} {finding.file}:{finding.line} {finding.masked_secret}")

inline = secscan.scan_text(
    'const key = "AKIAIOSFODNN7EXAMPLE";\n',
    path="src/app.js",
)

rules = secscan.load_rules("examples/poc/custom-rules.json")
secscan.scan_path(".", rules=rules)

secscan.calculate_entropy("AKIAIOSFODNN7EXAMPLE")
secscan.mask_secret("AKIAIOSFODNN7EXAMPLE")
# => "AKIA••••••••••••MPLE"

text = render(report, "sarif")

secscan.scan_path accepts a file or a directory. scan_text scans one string. Both return a ScanReport.

GitHub Actions

name: SecScan

on:
  push:
  pull_request:

permissions:
  contents: read
  security-events: write

jobs:
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - run: pip install secscan-sast
      - name: Scan
        run: |
          secscan . \
            --format sarif \
            --output secscan.sarif \
            --ignore "tests/*,docs/*"
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: secscan.sarif
      - name: Quality gate
        run: secscan . --fail-on critical --max-risk 50

Publish to PyPI

Trusted Publishing (OIDC) from GitHub Actions. No API token in the repo.

  1. Push main so .github/workflows/publish.yml exists on the default branch.

  2. In PyPI publishing, add a pending publisher:

    Field Value
    PyPI project name secscan-sast
    Owner saulofilho
    Repository secscan-python
    Workflow name publish.yml
    Environment name leave empty
  3. On GitHub: Actions → Publish to PyPI → Run workflow, or create release v0.1.0.

The first successful run creates the project on PyPI. After that the pending publisher becomes a trusted publisher on the project.

What it does not do

  • DAST, fuzzing, live HTTP attacks, WAF, or EDR
  • Dynamic confirmation that a secret is still valid
  • Auto-remediation patches
  • Skills, agents, or MCP (next step)

License: MIT. Changelog: CHANGELOG.md.

Metadata

Release files for secscan-sast 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for secscan-sast 0.1.0
File Size Uploaded
secscan_sast-0.1.0.tar.gz 27.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for secscan-sast 0.1.0
File Interpreter ABI Platform
secscan_sast-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 49.8 kB

Release files / secscan_sast-0.1.0.tar.gz

Download URL secscan_sast-0.1.0.tar.gz
Size 27.8 kB
Tags Source
SHA-256 checksum
How to use checksums
df72ff2793e8104af6a033613093b79e63a87386b96a1c0d129f166812ee00a8
BLAKE2b-256 checksum
How to use checksums
7f01d3e54acd6613c1fba94a9c1f73c65186c64333290723d752565074dca622
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / secscan_sast-0.1.0-py3-none-any.whl

Download URL secscan_sast-0.1.0-py3-none-any.whl
Size 22.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c501a4dd1efa5218905ecf792aebed7d97e1bb05e4452360f368d3f1bc10f0a4
BLAKE2b-256 checksum
How to use checksums
c015b79d2bbec754b5988c12bd62f431cf72d859dd6351e63ed2c9430ba02b6e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page