Running a source code vulnerability scan is one of the highest-ROI security activities your engineering team can perform. Unlike penetration testing — which requires a deployed, running application and manual testing time — source code scanning (SAST) works directly on your codebase, finds vulnerabilities at their root cause, and fits naturally into your existing build pipeline.
This guide walks through the complete workflow: choosing the right code vulnerability scanner, running your first scan, interpreting results, prioritizing what to fix, and integrating scanning into CI/CD so vulnerabilities are caught before they reach production.
What Source Code Scanning Finds (and What It Doesn’t)
A SAST (Static Application Security Testing) scanner analyzes your source code without executing it. It builds a model of how data flows through your application — tracing untrusted input from HTTP request parameters, form fields, and file uploads through your code to dangerous execution points like SQL queries, command execution, and HTML rendering.
What source code scanning reliably finds:
- SQL injection — user input flowing into database queries without parameterization
- Cross-site scripting (XSS) — user input rendered in HTML responses without encoding
- Command injection — user input passed to OS-level execution functions
- Hardcoded credentials — passwords, API keys, and tokens embedded in source files
- Insecure cryptography — use of MD5, DES, or other weak algorithms for security purposes
- Path traversal — user-controlled file paths without directory validation
- Server-side request forgery (SSRF) — user-controlled URLs in server-side HTTP requests
- Insecure deserialization — untrusted data deserialized without type restrictions
- Second-order injection — payloads stored in the database and executed in later queries
What source code scanning cannot find:
- Runtime misconfigurations (missing security headers in production, TLS configuration issues)
- Business logic flaws (a shopping cart that allows negative quantities)
- Authentication bypasses that depend on the deployment environment
- Vulnerabilities in running third-party services
For the items SAST misses, Dynamic Application Security Testing (DAST) against a live application fills the gap. For a complete security program, you need both.
Step 1: Choose a Code Vulnerability Scanner
The most important decision in your scanning workflow is which tool to use. Not all code vulnerability scanners work the same way — and the difference matters enormously for what they can detect.
Pattern Matching vs. Taint Analysis
Pattern-matching tools (Semgrep, basic SonarQube configurations) look for dangerous API calls and known-bad code patterns in local context — typically within a single function. They are fast and easy to set up but miss the majority of real injection vulnerabilities, which require tracing data across multiple function calls and file boundaries.
Taint analysis tools (Offensive360, Checkmarx, Fortify, CodeQL) build a full data-flow model of your application and trace how untrusted data flows from sources to sinks. This is the only approach that reliably finds:
- SQL injection where the query construction is spread across multiple methods
- Stored XSS where user input is written to the database in one request and rendered in another
- Second-order SQL injection where a payload is stored safely but used unsafely in a later query
The practical test: If a tool cannot detect SQL injection when the input parameter, the query construction, and the db.execute() call are in three separate functions — it is a pattern matcher, not a genuine SAST tool. This test case takes five minutes to write and separates the two categories definitively.
Key Criteria for Your Decision
| Criterion | What to Check |
|---|---|
| Language support | Does it support every language in your stack? Test with your actual code, not a demo. |
| Taint analysis depth | Can it trace data across function calls and files? Ask for a proof-of-concept on your code. |
| On-premise deployment | Can it run inside your network without sending source code to a third party? |
| CI/CD integration | Does it integrate with GitHub Actions, GitLab CI, Azure DevOps, or Jenkins? |
| Remediation guidance | Does each finding include a secure code example and a root-cause explanation? |
| False positive rate | What percentage of findings require manual dismissal? Ask to see their benchmark data. |
For enterprise teams needing combined SAST + DAST + SCA in a single platform with on-premise deployment, Offensive360 offers a one-time scan for $500 — useful as a proof-of-concept before committing to a subscription.
Step 2: Prepare Your Codebase for Scanning
Before running your first scan, a small amount of preparation makes the results more useful.
Ensure the Scanner Has the Full Code
Most SAST tools analyze the entire repository, not just the files you’re editing. Make sure the scan includes:
- Application source code (not just changed files for a baseline scan)
- Configuration files (YAML, JSON, XML,
.env.example— but never actual.envfiles with real secrets) - Infrastructure-as-code (Terraform, Kubernetes manifests, Helm charts) if your scanner supports it
Exclude generated files, build artifacts, and vendor/node_modules directories — scanning these creates noise without security value.
Build a Dependency Manifest
If your scanner includes Software Composition Analysis (SCA), ensure your dependency files are present and up to date:
# Ensure dependency manifests are current
npm install # Node.js — updates package-lock.json
pip install -r requirements.txt # Python — confirm requirements.txt is current
mvn dependency:tree # Java Maven — verify pom.xml is complete
dotnet restore # .NET — verify packages.lock.json
SCA scanning of your dependencies runs in parallel with SAST and often surfaces high-severity CVEs in transitive dependencies that your team may not be aware of.
Create a Scan Configuration File
Most enterprise SAST tools support a configuration file that tells the scanner which directories to include, which rules to enable, and how to classify findings:
# offensive360.yml (example configuration)
include:
- src/
- lib/
- config/
exclude:
- src/generated/
- node_modules/
- vendor/
- "**/*.test.js"
- "**/*.spec.ts"
severity_threshold: LOW # Report all findings; filter later
languages:
- javascript
- typescript
- python
Starting with severity_threshold: LOW gives you the full picture on a baseline scan. Once you’ve triaged the results and established a baseline, you can raise the threshold to focus on new Critical and High findings in CI/CD checks.
Step 3: Run Your First Scan
Local Scan (Baseline)
For an initial baseline scan, run against your complete codebase. This establishes your current vulnerability profile — the security debt that exists before any new improvements.
# Example: Running a scan via Offensive360 CLI
o360 scan \
--project ./src \
--output ./results/scan-baseline.json \
--format json,html \
--languages java,javascript
# The JSON output is machine-readable for pipeline integration
# The HTML report is human-readable for reviewing findings
Baseline scans of medium-size codebases (100K–500K lines of code) typically take 10–30 minutes. Very large codebases (1M+ lines) may take 1–2 hours.
Reading the Scan Output
A complete SAST finding typically includes:
Finding #1
────────────────────────────────────────────────────────
Severity: CRITICAL
Rule: SQL Injection (CWE-89)
File: src/api/users/UserService.java
Line: 247
Method: getUserByEmail(String)
Description:
User-controlled input from request parameter 'email' flows unsanitized
to a SQL query construction at line 247.
Taint trace:
[Line 89] String email = request.getParameter("email"); ← SOURCE
[Line 89→192] Passed through getUserSearchRequest() → buildQuery()
[Line 247] conn.execute("SELECT * FROM users WHERE email = '" + email + "'"); ← SINK
Remediation:
Use a parameterized query:
PreparedStatement stmt = conn.prepareStatement(
"SELECT * FROM users WHERE email = ?"
);
stmt.setString(1, email);
The taint trace is the key element — it shows you exactly which path the data takes from the user-controlled input to the vulnerable execution point. This is what distinguishes a SAST finding from a generic “SQL injection risk” warning.
Step 4: Triage and Prioritize Findings
A baseline scan of a medium-size codebase typically returns anywhere from a handful to hundreds of findings. Prioritizing which to fix first requires a structured approach.
Severity + Exploitability Matrix
Not every Critical-severity finding is equally urgent. Use this matrix to prioritize:
| Priority | Severity | Exploitability | Action |
|---|---|---|---|
| P0 | Critical | Direct path from user input | Fix immediately |
| P1 | High | Multi-step or indirect path | Fix within 7 days |
| P2 | Medium | Requires authentication or specific conditions | Fix within 30 days |
| P3 | Low | Theoretical or highly constrained | Address in next sprint |
| Backlog | Informational | Best-practice deviations | Document and schedule |
Distinguishing Real Findings from False Positives
SAST tools can produce false positives — findings where the code appears vulnerable from a data-flow perspective but is actually safe due to validation or sanitization that the scanner couldn’t model. Signs that a finding may be a false positive:
- The input is validated against a strict allowlist before the flagged operation
- The value comes from a restricted source (e.g., a configuration value, not user input)
- The finding is in a test file where the “user input” is hardcoded test data
When a finding is confirmed as a false positive, mark it in your SAST tool’s interface with a justification note — never silently suppress it without documentation.
Critical Finding Categories to Fix First
Regardless of your priority framework, these categories should always be addressed immediately:
- SQL injection with a direct path from HTTP parameters — exploitable without authentication
- Hardcoded production credentials — passwords, API keys, database connection strings in source code
- Reflected XSS in search/display parameters — directly exploitable
- Command injection — allows arbitrary OS command execution
- Path traversal with file read — allows reading arbitrary files from the server
Step 5: Fix the Findings
A SAST tool that shows you vulnerabilities but not how to fix them creates frustration without security improvement. The best tools provide specific, language-accurate remediation guidance for every finding.
The General Fix Pattern for Injection Vulnerabilities
Every injection vulnerability follows the same root cause: untrusted data reaching a dangerous execution point without proper sanitization or parameterization. The fix in every case is the same approach:
Use parameterized/prepared statements instead of string construction:
# VULNERABLE — SQL injection
def get_user(username):
db.execute(f"SELECT * FROM users WHERE name = '{username}'")
# SECURE — parameterized query
def get_user(username):
db.execute("SELECT * FROM users WHERE name = ?", (username,))
// VULNERABLE — XSS
app.get('/search', (req, res) => {
res.send(`<p>Results for: ${req.query.q}</p>`);
});
// SECURE — encode output
const he = require('he');
app.get('/search', (req, res) => {
res.send(`<p>Results for: ${he.encode(req.query.q)}</p>`);
});
// VULNERABLE — command injection
String filename = request.getParameter("file");
Runtime.getRuntime().exec("convert " + filename + " output.pdf");
// SECURE — validate input + use argument list (no shell interpretation)
String filename = request.getParameter("file");
if (!filename.matches("[a-zA-Z0-9_-]+\\.pdf")) {
throw new IllegalArgumentException("Invalid filename");
}
ProcessBuilder pb = new ProcessBuilder("convert", filename, "output.pdf");
pb.start();
Fix Hardcoded Credentials
# VULNERABLE — credential in source code
API_KEY = "sk-live-abc123def456"
# SECURE — load from environment at runtime
import os
API_KEY = os.environ["API_KEY"] # Set in deployment environment, never in code
Verify the Fix with a Rescan
After applying fixes, run a targeted rescan of the affected files to confirm the findings are resolved:
# Rescan only changed files to verify fixes quickly
o360 scan \
--files src/api/users/UserService.java \
--output ./results/rescan-users.json
Step 6: Integrate Source Code Scanning into CI/CD
A one-time scan is valuable for establishing a baseline, but the real security value comes from running scans continuously on every code change. This “shift-left” approach catches vulnerabilities at the pull request stage — before they’re merged and before they reach production.
GitHub Actions Integration
name: Security Scan
on:
pull_request:
branches: [main, develop]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history needed for accurate diff scanning
- name: Run SAST scan
env:
O360_API_KEY: ${{ secrets.O360_API_KEY }}
run: |
# Submit code for taint-analysis scan
curl -X POST https://api.offensive360.com/scan/code \
-H "X-API-Key: $O360_API_KEY" \
-H "Content-Type: application/json" \
-d '{"branch": "${{ github.head_ref }}", "compare_base": "${{ github.base_ref }}"}'
- name: Fail on new critical findings
run: |
# Block the PR if new Critical or High vulnerabilities are introduced
python scripts/check_scan_results.py \
--max-critical 0 \
--max-high 0 \
--report ./scan-results.json
GitLab CI Integration
# .gitlab-ci.yml
security-scan:
stage: test
script:
- |
curl -X POST https://api.offensive360.com/scan/code \
-H "X-API-Key: $O360_API_KEY" \
--data-binary @./src
artifacts:
reports:
sast: scan-results.json
paths:
- scan-results.json
expire_in: 30 days
rules:
- if: $CI_MERGE_REQUEST_ID
Key Policy Decisions
When integrating SAST into CI/CD, configure these policies:
For pull request checks (PR gate):
- Block merges only when new Critical or High findings are introduced (don’t fail on pre-existing backlog items)
- Allow suppressions with documented justification (not silent ignores)
- Set a maximum scan time (fail the build if scan takes more than 10 minutes — investigate scan configuration)
For scheduled full scans (weekly):
- Scan the entire codebase, not just changed files
- Apply the latest rule updates from your SAST vendor
- Send results to your security team’s dashboard even if nothing new is found
For release gates:
- Require a clean scan (zero Critical, zero unresolved High) before any production deployment
- Capture the scan results as a release artifact for audit trails
Step 7: Maintain Your Scanning Program
Running a SAST scan once is the starting point. A mature source code scanning program requires ongoing maintenance.
Update Rules Regularly
SAST tools receive regular rule updates that detect new vulnerability patterns, framework-specific issues, and emerging attack techniques. Schedule rule updates as part of your regular dependency maintenance cycle.
Track Metrics Over Time
Key metrics to track in your SAST program:
| Metric | Why It Matters |
|---|---|
| Mean time to fix (by severity) | Measures how quickly your team responds to findings |
| New findings per sprint | Trending up = new vulnerabilities being introduced; down = improved practices |
| False positive rate | High FP rate = developers ignore findings; needs rule tuning |
| Coverage (% of codebase scanned) | Are all languages and repositories included? |
| Backlog age | Critical findings older than 30 days are a policy failure |
Combine SAST with Other Security Testing
Source code scanning is most powerful as part of a layered program:
- SAST (source code scanning) — finds vulnerabilities at the code level
- DAST (dynamic scanning) — finds runtime misconfigurations, business logic flaws, and deployment issues
- SCA (dependency scanning) — finds known CVEs in third-party packages
- Secret scanning — dedicated tools for finding credentials in code (detect-secrets, GitLeaks)
- Periodic penetration testing — manual testing for complex exploit chains and logic flaws
Offensive360 includes SAST, DAST, and SCA in a single platform — eliminating the integration overhead of running three separate tools.
Frequently Asked Questions
How long does a source code vulnerability scan take?
Scan time scales with codebase size and language complexity. For most applications:
- Small (< 50K LOC): 2–5 minutes
- Medium (50K–500K LOC): 10–30 minutes
- Large (500K–2M LOC): 30–90 minutes
- Enterprise (2M+ LOC): 1–4 hours
In CI/CD, use incremental scanning (scanning only changed files and their dependencies) for PR checks — this keeps scan time under 5 minutes for most PRs.
Do I need the source code to scan for vulnerabilities?
SAST tools require source code (or compiled bytecode for binary analysis tools like Veracode). If you’re scanning a third-party application without source access, you need a dynamic scanner (DAST) or binary analysis tool instead.
How do I scan code that includes secrets?
Your SAST tool should never receive files containing real production credentials. Configure the scan to exclude .env files, secrets.yaml, and any files with actual secret values. Use .env.example files (with placeholder values) in your repository instead.
What if my scan returns thousands of findings?
A large number of findings on a first scan is normal for codebases that haven’t been scanned before. Don’t try to fix everything at once. Start with the severity + exploitability matrix above: fix Critical findings first, especially those with a direct path from user input. Set up your CI/CD integration to block only new Critical findings going forward — this prevents the backlog from growing while you address existing issues.
Can source code scanning miss vulnerabilities?
Yes. SAST tools have false negatives — vulnerabilities they don’t detect. Common limitations include:
- Novel vulnerability patterns not yet in the rule set
- Complex data flows through third-party libraries the scanner doesn’t model
- Business logic flaws that require understanding application context
- Runtime configuration issues (missing headers, TLS settings)
This is why DAST, penetration testing, and code review complement SAST — each technique finds different vulnerability classes.
Getting Started
The fastest way to understand what a source code scan will find in your codebase:
- One-time SAST scan for $500 — submit your source code, get a full taint-analysis report within 48 hours, no subscription required
- Book a demo — see the Offensive360 platform scan a real codebase, including taint traces for every finding and remediation guidance
- SAST product page — full feature list, 60+ language coverage, and deployment options including on-premise OVA for air-gapped environments
A one-time scan is the most practical way to baseline your current vulnerability profile before committing to a tooling decision. Many teams discover Critical vulnerabilities — SQL injection, hardcoded credentials, insecure deserialization — that were completely invisible to their existing linters and code review processes.
Offensive360 SAST performs deep interprocedural taint analysis across 60+ languages. Source code is scanned on-premise — it never leaves your network.