What SAST does β€” and what it doesn't

SAST (Static Application Security Testing) analyses your source code without executing it, looking for patterns that indicate security vulnerabilities: SQL injection sinks, unvalidated input flowing into dangerous functions, hardcoded credentials, insecure cryptography, and hundreds of other vulnerability classes.

What it does: finds known vulnerability patterns quickly across a large codebase, runs on every commit with no runtime environment required, catches issues before they reach production.

What it doesn't: understand business logic flaws, find runtime-only vulnerabilities (race conditions, auth bypass that requires specific state), or replace manual code review. SAST is a filter, not a complete security programme.

The right expectation: A well-configured SAST tool in CI should catch 20–40% of security bugs before review. It's not 100%, and that's okay. The goal is to automate the obvious ones so human reviewers can focus on the subtle ones.

Choosing a SAST tool for your stack

Tool choice depends on your primary language and how much you want to customise rules:

  • Semgrep β€” open-source, polyglot (Python, JS/TS, Go, Java, Ruby, PHP, C/C++), fast, highly customisable rules. Best default choice for most teams.
  • CodeQL β€” GitHub's static analysis engine, powerful taint-flow analysis, available free for public repos via GitHub Advanced Security. Slower but very accurate for Java, JS, Python, Go, C/C++.
  • Bandit β€” Python-only, extremely fast, good for catching common Python security issues. Pair with Semgrep for broader coverage.
  • ESLint with eslint-plugin-security β€” JavaScript/TypeScript, integrates into existing ESLint config, good for React and Node.js codebases.
  • AquilaX SAST β€” multi-language, AI-enhanced, low false-positive rate, integrates via API or CLI in any CI system.

GitHub Actions: SAST setup with Semgrep and CodeQL

Option 1: Semgrep (fast, any language)

.github/workflows/sast.ymlyaml
name: SAST
on:
  push:
    branches: [main]
  pull_request:

jobs:
  semgrep:
    runs-on: ubuntu-latest
    container:
      image: semgrep/semgrep
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
      - name: Run Semgrep
        run: |
          semgrep scan \
            --config=auto \
            --error \
            --severity=ERROR \
            --output=semgrep-results.sarif \
            --sarif .
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: semgrep-results.sarif

Option 2: CodeQL (deep taint-flow analysis)

.github/workflows/codeql.ymlyaml
name: CodeQL
on:
  push:
    branches: [main]
  pull_request:

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    strategy:
      matrix:
        language: [javascript, python]
    steps:
      - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
      - uses: github/codeql-action/init@v3
        with:
          languages: ${{{ matrix.language }}}
      - uses: github/codeql-action/analyze@v3

GitLab CI: SAST setup

GitLab has built-in SAST support via its security templates. One include directive enables it for most languages automatically:

.gitlab-ci.ymlyaml
include:
  - template: Security/SAST.gitlab-ci.yml
  - template: Security/Secret-Detection.gitlab-ci.yml

variables:
  SAST_EXCLUDED_PATHS: spec,test,tests,tmp
  SAST_EXCLUDED_ANALYZERS: ""
  SECURE_ANALYZERS_PREFIX: "registry.gitlab.com/security-products"

# Block merge requests on critical findings
sast:
  variables:
    SAST_SEVERITY: critical
  allow_failure: false

GitLab's SAST template automatically selects the right analyser for each language in the repository (Bandit for Python, ESLint for JS, SpotBugs for Java, Gosec for Go, and so on). Results appear in the Merge Request security widget.

Blocking builds on security findings

The key configuration decision: at what severity level should a SAST finding block a merge or deployment? A common starting policy:

  • CRITICAL/HIGH: Block the build. These are exploitable vulnerabilities that should not merge without a fix or explicit exception.
  • MEDIUM: Report but don't block. Create a ticket automatically. Review in the next sprint.
  • LOW/INFO: Informational only. Suppress in CI output but track in a dashboard.

Start with reporting-only mode. If you enable blocking on day one in an existing codebase, you will likely find hundreds of findings and completely stop your team's workflow. Run in report mode for two weeks, triage findings, fix the genuine ones, suppress false positives, then enable blocking.

Semgrep β€” block only on ERROR severityshell
# --error exits with code 1 on any ERROR-severity finding β†’ blocks CI
$ semgrep scan \
    --config=auto \
    --severity=ERROR \
    --error \
    .

# Exit codes: 0 = no findings, 1 = findings found (with --error)

Reducing false positives without disabling rules

False positives are the main reason SAST programmes fail β€” developers start ignoring alerts when too many are wrong. The right approach is targeted suppression, not disabling entire rule categories.

Inline suppression comments

Inline suppression β€” Semgrep and Banditpython
# Semgrep: suppress a specific rule on this line
result = subprocess.run(cmd, shell=True)  # nosemgrep: dangerous-subprocess-use

# Bandit: suppress a specific check
password = "test_password_not_real"  # nosec B105

# With justification (recommended)
result = subprocess.run(cmd, shell=True)  # nosemgrep: reason=cmd is constructed from allowlisted values only

Path exclusions for test and generated code

.semgrepignoretext
# Ignore test files (intentionally insecure patterns)
tests/
test/
**/*_test.go
**/*.test.js
**/*.spec.ts

# Ignore generated code
**/generated/
**/vendor/
**/node_modules/
dist/
build/

What to add after SAST is running

Once SAST is stable and blocking in CI, the natural next steps:

  • SCA (Software Composition Analysis) β€” scan your dependencies for known CVEs. Run npm audit, Grype, or Snyk alongside SAST.
  • Secrets scanning β€” detect hardcoded credentials with Trufflehog or detect-secrets. Runs in under 10 seconds on most repos.
  • IaC scanning β€” if you have Terraform, Kubernetes manifests, or Dockerfiles, add tfsec or Checkov to the same pipeline.
  • DAST β€” once you have a staging environment, add dynamic scanning against the running application with OWASP ZAP or a managed DAST tool.

The shift-left pyramid: SAST is the widest layer β€” cheapest to run, catches the most volume. Each layer above (SCA, secrets, IaC, DAST) adds a different category of finding. Together they give you coverage from code to cloud without any single tool being a bottleneck.

Add SAST to your pipeline in under 5 minutes

AquilaX integrates SAST, SCA, secrets scanning, and IaC scanning into a single pipeline step β€” with low noise, actionable results, and support for 30+ languages.

Get started with SAST β†’