Applying SAST to Any Application with CodeQL
Static Application Security Testing (SAST) helps you inspect source code before an attacker ever reaches the running application. OWASP maintains a community page for Source Code Analysis Tools, which is a useful starting point when you need to compare static analysis options.
For this article, I intentionally avoid Sonar, Snyk, Semgrep, and Veracode. Instead, the hands-on example uses CodeQL, GitHub's semantic code analysis engine.
Repository for the demo: https://github.com/andre-carbajal/codeql-sast-demo
What we are building
The repo contains a small Node.js/Express app with educational vulnerabilities and a GitHub Actions workflow that runs CodeQL automatically.
The goal is not to build a production app. The goal is to show the repeatable pattern you can apply to almost any application:
- Identify the language and build process.
- Add a SAST workflow to CI.
- Configure the query set.
- Push code and review code scanning results.
- Fix findings and keep the workflow running on every pull request.
Demo application
The Express app has a safe landing page and a few intentionally unsafe routes:
app.get('/profile', (request, response) => {
const name = request.query.name || 'developer';
response.send(`<h1>Hello ${name}</h1>`);
});
That route reflects user input into HTML. A safer variant escapes the output first:
app.get('/safe/profile', (request, response) => {
const name = escapeHtml(request.query.name || 'developer');
response.send(`<h1>Hello ${name}</h1>`);
});
The app also includes examples for command execution and file path handling so CodeQL has realistic security patterns to analyze.
Add CodeQL to GitHub Actions
Create .github/workflows/codeql.yml:
name: CodeQL SAST
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
schedule:
- cron: "31 7 * * 1"
permissions:
contents: read
jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
permissions:
security-events: write
packages: read
actions: read
contents: read
strategy:
fail-fast: false
matrix:
language: ["javascript-typescript"]
steps:
- name: Checkout repository
uses: actions/checkout@v7
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@v4
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"
For JavaScript and TypeScript, the important language identifier is javascript-typescript. If you apply this to a different stack, update the matrix language and make sure the repository can be built in CI.
Configure the query suite
Create .github/codeql/codeql-config.yml:
name: CodeQL SAST demo config
queries:
- uses: security-extended
- uses: security-and-quality
paths-ignore:
- node_modules/**
- test/**
The security-extended and security-and-quality suites make the scan broader than the default configuration. In a real production repository, start broad, review the signal-to-noise ratio, and then tune the config.
Run the demo locally
Clone the repository and run:
npm install
npm test
npm start
Then open:
http://localhost:3000
The tests focus on the safe path. The vulnerable routes are there so the SAST workflow has something meaningful to inspect.
Apply the same pattern to any application
Use this checklist when applying SAST to another project:
- Pick the language: use the CodeQL language identifier for your app.
- Understand the build: compiled languages may need a real build step before analysis.
- Start with CI: run SAST on pull requests, pushes to main, and a scheduled scan.
- Review findings in context: not every alert has the same business impact.
- Fix and test: patch the vulnerable code and add regression tests.
- Keep tuning: adjust ignored paths and query suites as the codebase grows.
What to look for in GitHub
After pushing the workflow, go to the repository on GitHub and check:
-
Actions: confirm the
CodeQL SASTworkflow finished successfully. - Security > Code scanning: review CodeQL alerts.
- Pull requests: confirm future changes are scanned before merge.
Result from the demo repo
The first CodeQL run completed successfully and produced five open code scanning alerts in the intentionally vulnerable demo app:
-
js/path-injection: uncontrolled data used in a path expression. -
js/command-line-injection: uncontrolled command line. -
js/missing-rate-limiting: missing rate limiting on exposed routes. -
js/reflected-xss: reflected cross-site scripting.
That is exactly what we want from a teaching repository: the workflow runs, uploads SARIF results to GitHub code scanning, and gives developers concrete findings to review.
Final thoughts
SAST is most valuable when it becomes part of the development workflow instead of a one-time audit. CodeQL works well for this because the configuration can live next to the code, run in GitHub Actions, and produce code scanning alerts where developers already review changes.
Use the demo repo as a starting point, then adapt the workflow and query configuration to your own language, build process, and risk profile.
Top comments (0)