# LLM Sast Scanner

> General-purpose Static Application Security Testing (SAST) skill for code vulnerability analysis. Trigger when the user asks to: "analyze code for vulnerabilities", "review code security", "find security bugs", "do a SAST scan", "check for [vulnerability type] in code", "audit source code", or requests a security code review of any language or framework. Covers 34 vulnerability classes across web, API, auth, mobile, and logic layers.

- Skill: `sunweb3sec/llm-sast-scanner` (Agent Skill, multi-file: 35 files)
- Install (CLI): `npx skillmds@latest add sunweb3sec/llm-sast-scanner`
- Raw SKILL.md: https://api.skillmd.com/api/skills/sunweb3sec/llm-sast-scanner/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: SunWeb3Sec (https://skillmd.com/u/sunweb3sec)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/sunweb3sec/llm-sast-scanner

---


# SAST Vulnerability Analysis

## Purpose

Systematically analyze source code for security vulnerabilities using structured Source→Sink taint tracking,
pattern matching, and vulnerability-class-specific detection heuristics. Produce actionable findings with
severity ratings, affected code locations (file + line number), and remediation guidance.

## Scope

This skill covers the following 34 vulnerability classes. Each has a dedicated reference file loaded on demand:

| Category | Vulnerabilities |
|----------|----------------|
| **Injection** | SQL Injection, XSS, SSTI, NoSQL Injection, GraphQL Injection, XXE, RCE / Command Injection, Expression Language Injection |
| **Access Control & Auth** | IDOR, Privilege Escalation, Authentication/JWT, Default Credentials, Brute Force, Business Logic, HTTP Method Tampering, Verification Code Abuse, Session Fixation |
| **Data Exposure & Crypto** | Weak Crypto/Hash, Information Disclosure, Insecure Cookie, Trust Boundary |
| **Server-Side** | SSRF, Path Traversal/LFI/RFI, Insecure Deserialization, Arbitrary File Upload, JNDI Injection, Race Conditions |
| **Protocol & Infrastructure** | CSRF, Open Redirect, HTTP Request Smuggling/Desync, Denial of Service, CVE Patterns |
| **Language/Platform** | PHP Security, Mobile Security (Android/iOS) |

---

## Workflow

### Step 1: Understand Scope

Determine:
- Target: single file, directory, API endpoint, module, or full repo
- Language(s) and framework(s) in use
- User's goal: quick scan, deep audit, specific vuln class, or full report

### Step 2: Load Relevant References

Based on the code being reviewed, load the appropriate reference files from `references/`:

```
references/sql_injection.md          — SQL / ORM injection
references/xss.md                    — Cross-site scripting
references/ssrf.md                   — Server-side request forgery
references/rce.md                    — Remote code execution
references/idor.md                   — Insecure direct object reference
references/authentication_jwt.md     — Auth flaws, JWT weaknesses
references/csrf.md                   — Cross-site request forgery
references/path_traversal_lfi_rfi.md — Path traversal, LFI/RFI
references/ssti.md                   — Server-side template injection
references/xxe.md                    — XML external entity
references/insecure_deserialization.md    — Insecure deserialization
references/arbitrary_file_upload.md      — Arbitrary file upload
references/privilege_escalation.md       — Privilege escalation
references/nosql_injection.md            — NoSQL injection
references/graphql_injection.md          — GraphQL injection
references/weak_crypto_hash.md           — Weak cryptography / hash
references/information_disclosure.md     — Information disclosure
references/insecure_cookie.md            — Insecure cookie attributes
references/open_redirect.md              — Open redirect
references/trust_boundary.md             — Trust boundary violations
references/race_conditions.md            — Race conditions / TOCTOU
references/brute_force.md                — Brute force / credential stuffing
references/default_credentials.md        — Default / hardcoded credentials
references/verification_code_abuse.md    — Verification code abuse
references/business_logic.md             — Business logic flaws
references/http_method_tamper.md         — HTTP method tampering
references/smuggling_desync.md           — HTTP request smuggling / desync
references/cve_patterns.md               — Known CVE patterns
references/expression_language_injection.md — Expression language injection (SpEL / OGNL)
references/jndi_injection.md             — JNDI injection (Log4Shell class)
references/denial_of_service.md          — Denial of service / resource exhaustion
references/php_security.md               — PHP-specific security issues
references/mobile_security.md            — Mobile security (Android / iOS)
references/session_fixation.md           — Session fixation
```

**Loading strategy:**
- For a targeted review (e.g., "check for SQL injection"), load only the relevant reference(s).
- For a full audit, load all 34 references and scan systematically.
- Always load references for the top OWASP risks even if not explicitly requested.

---

### Step 3: Analyze Code — Bidirectional Taint Tracking

Taint analysis runs in **two passes**. Pass A is mandatory and runs first.

#### Pass A — Sink-First Sweep (mandatory, run before Pass B)

Forward tracing from every source does not converge on a real codebase: a mid-size service has
hundreds of request parameters but only a handful of dangerous sinks. Enumerate the sinks first,
then walk backward. This pass is cheap, bounded, and has far higher recall.

1. **Inventory every dangerous sink in scope** with a grep sweep. Record the total count — it is
   usually 5–50 for a whole repo, and every one must be individually accounted for.

| Class | Sink grep (Java example — adapt per language) |
|-------|---------------------------------------------|
| Filesystem read/write | `new File(`, `FileOutputStream`, `FileInputStream`, `Files.(write|copy|newOutputStream|newInputStream|createFile|delete)`, `Paths.get(`, `transferTo(`, `RandomAccessFile` |
| Command exec | `Runtime.getRuntime().exec`, `ProcessBuilder`, `bash -c` |
| SQL | `createQuery`, `createNativeQuery`, `@Query`, `jdbcTemplate.(query|update)`, string-concatenated SQL |
| Deserialization | `readObject`, `ObjectMapper.readValue` with polymorphic typing, `Yaml.load`, `XMLDecoder` |
| HTTP client | `RestTemplate`, `WebClient`, `HttpClient`, `URL.openConnection` |
| Expression / template | `SpelExpressionParser`, `Ognl`, `ScriptEngine`, `freemarker.Template` |
| Redirect | `sendRedirect`, `RedirectView`, `"redirect:" +` |

2. **For each sink, walk backward** to every caller until you reach either an entry point
   (controller/handler/listener/scheduled job) or a hardcoded constant.
3. **Account for 100% of sinks.** Every sink gets one of: `TAINTED` (reaches an entry point),
   `CONSTANT` (only ever fed literals), or `UNREACHED` (no caller found — state this explicitly).
   Never leave a sink unexamined; never sample.

**Report the sink inventory in the scan output** — total sinks found, and the disposition of each.
A scan that does not state its sink count has not completed Pass A.

#### Pass B — Source-First Tracing

After Pass A, trace forward from sources to catch classes whose sink is diffuse
(XSS, IDOR, business logic, authz) and therefore not greppable as a sink list.

1. **Identify Sources** — User-controlled input entry points:
   - HTTP params, headers, cookies, request body
   - File uploads
   - WebSocket messages
   - Environment variables
   - Database reads of user-supplied data, deserialized objects

2. **Trace Data Flow** — Follow the data through:
   - Variable assignments, function arguments, return values
   - Framework helpers, ORM calls, template rendering
   - Cross-module/service boundaries

3. **Check Sinks** — Dangerous operations receiving tainted data:
   - Query execution (SQL, NoSQL, LDAP, XPath)
   - Shell/OS command execution
   - File system operations
   - HTTP client calls
   - Template rendering / eval / expression parsing
   - Serialization/deserialization

4. **Evaluate Sanitization** — Between source and sink, look for:
   - Input validation (allowlist vs denylist)
   - Context-appropriate encoding/escaping
   - Parameterization (prepared statements)
   - Framework-native protections

   **Never infer sanitization from a name.** A helper called `createFileName`, `sanitize`,
   `normalizePath`, `buildSafeX`, or `validateY` is only a sanitizer if you have read its body and
   confirmed every branch constrains the value. Open the function. Trace each return path.

   **Decoy-sanitizer shape (high-frequency real-world bug):** a helper whose *safe* behaviour lives
   in the fall-through branch while an early return passes user input through untouched.

   ```java
   static String createFileName(String fileName, String feature, LocalDateTime t, String ext) {
       if (StringUtils.isNotBlank(fileName)) {
           return fileName + ext;          // ← tainted, zero filtering: the ATTACKER-REACHED branch
       }
       return feature + "_" + format(t) + ext;   // ← the safe branch, only taken when input is absent
   }
   ```

   Rule: when a helper branches on "did the caller supply a value?", the supplied-value branch is
   the attacker's branch. Evaluate the sink against **that** branch, not the default one.

5. **Cross-Layer & Async Continuation** — a taint path does not end at a layer boundary. Continue
   tracing through:
   - Controller → Service → Repository/Util hops, including into a different file/package
   - `@Async`, `@Scheduled`, `CompletableFuture`, thread pools, message queues, event listeners.
     Leaving the request thread does **not** detoxify data — the value is still attacker-supplied.
   - **Re-derived arguments**: check what the caller actually passes. A controller may compute a
     local `String fileName = sanitize(param.getFileName())` and then still hand the raw
     `param.getFileName()` to the service. Compare the argument expression at the call site
     against the sanitized local — they are frequently not the same value.
   - Values re-entering a helper a second time downstream (the sink-side call is the one that counts)

6. **Determine Preliminary Verdict**:
   - **VULN**: Taint reaches sink with no effective sanitization
   - **LIKELY VULN**: Sanitization present but bypassable per reference heuristics
   - **SAFE**: Effective sanitization or no taint path

---

### Step 4: Business Logic & Auth Analysis

Beyond taint tracking, check for:
- Missing authentication/authorization on sensitive endpoints
- Insecure state machine transitions
- Race conditions in concurrent operations
- Improper trust boundaries between components
- JWT algorithm confusion, token fixation, session issues
- Default/hardcoded credentials
- Enumeration via timing or response differences

---

### Step 5: Judge — Validity Re-Verification

Before reporting, every preliminary finding (VULN or LIKELY VULN) **must pass a Judge review**. The Judge acts as an adversarial second opinion to eliminate false positives.

For each candidate finding, answer all of the following:

#### Reachability Check
- [ ] Is the source actually user-controlled, or is it internal/trusted data?
- [ ] Is the vulnerable code path reachable from an HTTP endpoint / entry point, or is it dead code / internal-only?
- [ ] Are there upstream guards (auth middleware, input filters) that block the path before it reaches the sink?

#### Sanitization Re-Evaluation
- [ ] Is there sanitization that was missed in Step 3? (Check parent functions, middleware, framework internals)
- [ ] Is the sanitization method sufficient for this specific sink and context?
- [ ] Does the framework provide implicit protection for this pattern?

#### Exploitability Check
- [ ] Can the tainted value actually reach the sink in a form that triggers the vulnerability?
- [ ] Is exploitation conditional on a specific environment, config, or privilege level?
- [ ] For logic bugs: is the business impact real, or hypothetical?
- [ ] Is the chosen tag the most precise valid label for this finding?

#### Judge Verdict

| Verdict | Meaning | Action |
|---------|---------|--------|
| **CONFIRMED** | All reachability/sanitization/exploitability checks pass | Include in report |
| **LIKELY** | Most checks pass; one uncertainty remains | Include in report, flag uncertainty |
| **NEEDS CONTEXT** | Cannot determine without runtime behavior / config / additional files | Note as "unverifiable without X" |
| **FALSE POSITIVE** | Positive evidence of protection found — cite the exact file+line of the sanitization, allowlist check, guard, or framework-level auto-protection that makes the sink safe | Drop silently |

**Only CONFIRMED and LIKELY findings are reported.**

**FP burden of proof**: `UNCERTAIN` on any check is NOT sufficient to declare FALSE POSITIVE. If a check result is UNCERTAIN after inspecting the sink, its callers, and the framework internals, use `NEEDS CONTEXT` instead. Only use FALSE POSITIVE when you have found and can cite positive evidence that the path is protected.

#### Judge Output Format (internal, before reporting)

```
Finding: VULN-NNN — <class>
Reachability:   PASS / FAIL / UNCERTAIN — <reason>
Sanitization:   PASS / FAIL / UNCERTAIN — <reason>
Exploitability: PASS / FAIL / UNCERTAIN — <reason>
Judge Verdict:  CONFIRMED / LIKELY / NEEDS CONTEXT / FALSE POSITIVE
```

#### False Positive Guardrails

**Tags**
- `default_credentials`: require a reachable auth path that accepts the hardcoded credential.
- `weak_crypto_hash`: require direct use of weak hash/algo — not just an import or third-party component. Covers both weak algorithms (DES, RC4, ECB) and weak hashes (MD5, SHA-1 for passwords); do not use `weak_crypto` as a separate tag.
- `rce` → prefer `command_injection` for direct shell/process execution. Do not replace `spel_injection` with `rce`/`command_injection`.
- `jndi_injection` in demos: only if the JNDI sink is the primary exploit path.
- Broad tags (`trust_boundary`, `authentication`, `privilege_escalation`): prefer the narrowest valid tag (`xff_spoofing`, `session_fixation`, `verification_code`).
- `open_redirect`: only if the attacker-controlled redirect is the primary exploit (not infra/parser misconfiguration).
- `csrf`: skip for stateless Bearer-token-only APIs (`SessionCreationPolicy.STATELESS`).
- `insecure_deserialization`: skip if `component_vulnerability` covers the same sink.
- `arbitrary_file_upload`: skip for avatar/profile upload with type restrictions and non-webroot storage.
- `session_fixation`: skip when Spring Security default session management is active.
- `information_disclosure`: skip for DB credentials in config files — deployment issue, not app-level.

**Scope**
- Demo/example code: skip any finding whose ONLY vulnerable path is in `examples/`, `demo/`, `sample/` (or similar). Report only if the bug is in the library/SDK itself.
- Non-default config: verify the DEFAULT value before reporting. Requires non-default/deprecated → cap `Low`. Explicitly labeled `legacy` or deprecated in code/docs → cap `Informational`.

**Trust Boundary**
- Operator self-harm: skip findings where the "attacker" input comes from operator-written config files (YAML/JSON/TOML), CLI flags the operator supplies themselves (`--file`, `--url`, `--chain-id`), or commands the operator must explicitly run.
- Trusted admin role: skip `privilege_escalation`/`business_logic` for actions behind `onlyAdmin`/`onlyOwner`/`onlyPoolAdmin` when that role is trusted by design. Only report if an unprivileged user can reach the same path.
- Internal-only service: skip `authentication` and `information_disclosure` when the entire codebase has zero auth AND references internal infra (VPC vars, `EC2_INSTANCE_ID`, Eureka, Consul). Auth is at the network layer.
- Code generators: skip `injection`/`path_traversal`/`rce` for codegen tools (`protoc`, `swagger-codegen`, etc.) whose input comes from developer-controlled source comments, annotations, or local config.

**Protocol & Architecture**
- Protocol-designed SSRF: skip `ssrf` when fetching a peer-supplied URL is required by spec (LNURL, UMA, OAuth discovery, WebFinger, OIDC discovery). Only report if the impl allows schemes the protocol does not require (e.g., `file://`) or skips required domain validation.
- Blind SSRF: downgrade to `Informational` when all three hold: (a) response never reaches the attacker, (b) no meaningful side effect on the target, (c) no error oracle.
- Bounded DoS: skip `denial_of_service` unless the upper bound of the iterated/allocated data is attacker-controllable and unbounded. Naturally bounded data (blockchain validator set, gas limits, etcd/request-body size caps) → not a finding.
- Brute force: skip `brute_force` only if rate limiting is visible in code, framework config, or referenced middleware in the repo. Do not assume infrastructure-level rate limiting.
- Idempotent replay: skip replay/`business_logic` when the operation is idempotent AND parameters are cryptographically signed (no tampering possible).
- Library dead path: if no real caller in the codebase triggers the vulnerable parameter combination AND the code has a warning log for that path → `NEEDS CONTEXT`, not a finding.

**Platform**
- Android app-private storage: skip `insecure_storage`/`information_disclosure` for `SharedPreferences`/`DataStore` in app-private storage without `android:allowBackup="true"` in a production manifest.
- Terraform state: skip `information_disclosure` for providers writing secrets to state when attributes are marked `Sensitive: true`.
- Intra-org CI/CD: skip `supply_chain` for mutable action tags (e.g., `@v3`) when the action org matches the repo org. Only report third-party org actions.
- Local dev tools: skip `authentication` for README-described local dev tools with no production docs. Exception: report (reduced severity) if the tool does not bind to `localhost`, exposes tokens in API responses, or allows destructive ops.

---

#### Under-Reporting Guardrails

The guardrails above suppress. These **override** them — a suppression rule must never fire on the
cases below. Every rule here exists because a real vulnerability was previously missed.

**Authority-escalation overrides the trusted-role rules.** The `Trusted admin role` and
`Operator self-harm` rules apply *only* to the authority the role legitimately holds. They do
**not** apply when the input crosses from application-data authority into host, filesystem,
network, or process authority.

| Role authority (in scope, suppressible) | Escalated authority (ALWAYS report) |
|---|---|
| Edit/export/delete records the role owns | Write, read, or delete arbitrary paths on the app server |
| Configure app settings via the admin UI | Execute commands, load classes, deserialize into gadgets |
| Query data the role is entitled to | Make the server originate requests to attacker-chosen hosts |

A CMS admin is authorized to export a CSV. A CMS admin is **not** authorized to choose where on the
server's disk that CSV lands. That is a trust-boundary crossing, and it is a finding at full
severity regardless of the authentication in front of it.

**Do not suppress on constrained impact.** A finding stands even when the primitive is partial:
- Extension is forced (`.csv`, `.png`) → still arbitrary-path write. Report it.
- Attacker cannot read back what was written → still a write primitive. Report it.
- Overwrite only, no new-file creation (or vice versa) → still a finding.
- No obvious escalation to RCE → impact is still overwrite / disk exhaustion / web-root drop.
  Absence of an RCE chain is not absence of a vulnerability.

**Do not suppress because a parameter's name sounds benign.** `fileName`, `name`, `title`, `label`,
`id`, `key`, `type`, `format` are routinely passed straight to dangerous sinks. Dual-use parameters
are the classic miss: the same `fileName` may be used safely in a `Content-Disposition` header in
one method and unsafely in `new File()` in another. Seeing the safe use **first** does not clear the
parameter. Evaluate every sink it reaches, independently.

**Severity Downgrade Rule does not apply to authority escalation.** Requiring authentication
downgrades a finding only when the authenticated role was already entitled to the outcome. When
authentication merely gates access to an escalated-authority primitive (table above), do not
downgrade — and never let a downgrade drop a finding out of the report entirely. Downgrading
changes the severity label; it never changes whether the finding is reported.

**Sink accounting is not optional.** If Pass A found N sinks and the report explains fewer than N,
the scan is incomplete — not clean. Re-run the unexplained sinks before reporting.

---

#### Pre-Report Checklist

- [ ] Public-facing service, or internal-by-design (zero auth everywhere + internal infra refs)?
- [ ] Production code, or demo/example/sample directory?
- [ ] Attacker is genuinely untrusted, not an admin/operator within their own trust boundary?
- [ ] Verify DEFAULT config value — does the attack work with defaults?
- [ ] SSRF required by protocol spec?
- [ ] SSRF response reachable by attacker (readable / side effect / error oracle)?
- [ ] Sensitive storage protected by OS sandbox (Android app-private)?
- [ ] Replay: is the operation idempotent with signature-bound parameters?
- [ ] Library: does any real caller trigger the vulnerable path?
- [ ] Terraform state with `Sensitive: true` — by design?
- [ ] DoS: is the upper bound attacker-controllable and unbounded?
- [ ] CI/CD mutable tags: same org or third-party?
- [ ] Admin action within the admin's designed trust boundary — or does it reach host/filesystem/process authority the role was never granted?
- [ ] Pass A sink inventory: is every sink found accounted for as TAINTED / CONSTANT / UNREACHED?
- [ ] Did any "sanitizer" get credited on its name alone, without reading every return path?
- [ ] Did any trace stop at an `@Async` / queue / thread-pool boundary instead of continuing?
- [ ] At each call site, is the argument passed the sanitized local — or the raw source re-read?
- [ ] Was a finding dropped only because impact was constrained (fixed extension, no read-back, no RCE chain)?

---

### Step 6: Report Findings

#### Severity Classification

| Severity | Criteria |
|----------|----------|
| **Critical** | Direct RCE, authentication bypass, unauthenticated data exposure |
| **High** | SQLi, SSRF, IDOR with sensitive data, stored XSS, privilege escalation |
| **Medium** | Reflected XSS, CSRF, path traversal, insecure deserialization |
| **Low** | Information disclosure, open redirect, weak crypto, insecure cookie |
| **Info** | Missing security headers, verbose errors, defense-in-depth gaps |

**Severity Downgrade Rule:** When exploitation requires authentication, specific non-default configuration, chained prerequisites, or is only reachable through an internal/admin-only path, downgrade severity by one level from the class default; LIKELY-verdict findings whose exploitability is marked UNCERTAIN must be capped at one level below the class default regardless of vulnerability type.

#### Finding Format

```
[SEVERITY] VULN-NNN — <Vulnerability Class>  [CONFIRMED | LIKELY]
File: <path>:<line_number>
Description: <one sentence — what the vulnerability is>
Impact: <what an attacker can achieve>
Evidence:
  <relevant code snippet>
Judge: <one sentence — why this passed re-verification>
Remediation: <specific fix — not generic advice>
Reference: references/<vuln>.md
```

For NEEDS CONTEXT findings:

```
[UNVERIFIABLE] VULN-NNN — <Vulnerability Class>
File: <path>:<line_number>
Blocked by: <what additional context is needed>
```

#### Report Structure

When producing a full report, write to `sast_report.md` (or user-specified path):

```markdown
# SAST Security Report — <target>
Date: <date>
Analyzer: llm-sast-scanner v1.4

## Executive Summary
<2-3 sentences: total findings by severity, most critical issue>

## Coverage — Sink Inventory (Pass A)
<Total sinks found per class, and the disposition of each: TAINTED / CONSTANT / UNREACHED.
A report without this section is incomplete, not clean.>

| Sink | File:Line | Disposition | Traced to |
|------|-----------|-------------|-----------|

## Critical Findings
## High Findings
## Medium Findings
## Low Findings
## Informational
## Unverifiable Findings

## Remediation Priority
<ordered fix list>
```

---

## Key Principles

- **Evidence over assertion**: always show the vulnerable code path, not just the pattern name
- **Context matters**: a finding is only valid if the sink is reachable with user-controlled data
- **Avoid false positives**: if sanitization exists, verify it is bypassable before marking VULN
- **Be precise**: include exact file paths and line numbers — never approximate
- **Fix > flag**: always provide a concrete remediation, not just a problem statement
- **Language-aware**: adapt sink/source patterns to the specific language and framework in use

