Language-native SAST linters - the first-party "linter as SAST" family that runs inside each ecosystem's standard toolchain with no separate scanner server: Bandit (Python, 60+ B-rules, severity x confidence filtering), gosec (Go, 40+ G-rules, AST + SSA taint tracking, golangci-lint integration), eslint-plugin-security + eslint-plugin-no-unsanitized (JS/TS, 14 detect-* rules + DOM-sink XSS), and PMD's Apex security ruleset (Salesforce, ApexSOQLInjection / ApexCRUDViolation / ApexSharingViolations). Covers the shared adoption pattern - install as a dev dependency, first scan, suppression-with-justification discipline, baseline-diff adoption for legacy code, SARIF output + CI gating - with per-tool depth in references. Use when a repo needs in-toolchain security linting for Python, Go, JavaScript/TypeScript, or Apex; for cross-language or cross-file taint analysis use semgrep-rules / codeql-queries instead.
72
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Per-tool reference for language-native-sast.
Per github.com/securego/gosec:
gosec is the Go-specific SAST. It "performs static code analysis by scanning the Go AST and SSA code representation" and supports "taint analysis tracking data flow from user inputs to dangerous functions" per gs-gh. The taint analysis distinguishes gosec from regex-based linters - it tracks input flow through method chains, which catches injection patterns linters miss.
Per gs-gh:
go install github.com/securego/gosec/v2/cmd/gosec@latestFor pinned versions in CI:
go install github.com/securego/gosec/v2/cmd/gosec@v2.20.0Docker:
docker pull securego/gosec
docker run --rm -v "$PWD:/code" securego/gosec ./code/...Per gs-gh:
gosec ./...Common variations:
gosec -severity=high ./... # only HIGH severity
gosec -confidence=high ./... # only HIGH confidence
gosec -exclude=G104 ./... # skip "unhandled errors" rule
gosec -include=G101,G102 ./... # only run specific rulesPer gs-gh the common rule IDs; the authoritative current list is
emitted at runtime by gosec -list-rules:
| Rule | Description |
|---|---|
| G101 | Hardcoded credentials |
| G102 | Bind to all interfaces (0.0.0.0) |
| G103 | Audit unsafe block (use of unsafe package) |
| G104 | Unhandled errors |
| G106 | SSH InsecureIgnoreHostKey |
| G107 | URL with potential SSRF |
| G201 | SQL query construction by string concat |
| G202 | SQL query construction by string format |
| G204 | Subprocess launched with variable |
| G301 | Poor file permissions on directory |
| G302 | Poor file permissions on file |
| G303 | Predictable temp-file name |
| G304 | File path traversal vulnerabilities |
| G305 | File traversal in tar archive |
| G401 | Weak cryptographic algorithms |
| G402 | TLS InsecureSkipVerify |
| G403 | RSA key length too short |
| G404 | Insecure random number generation |
| G501-G505 | Insecure crypto primitives (DES, MD5, RC4, SHA1) |
| G601 | Implicit memory aliasing in for-range |
| G602 | Slice bounds out of range |
Prefix families at a glance: G1xx credential / injection / unsafe surface, G2xx SQL and subprocess construction, G3xx file and path handling, G4xx crypto and TLS misuse, G5xx insecure crypto primitives, G6xx Go memory and slice hazards.
Per gs-gh:
gosec -fmt sarif -out results.sarif ./...
gosec -fmt json -out results.json ./...
gosec -fmt junit-xml -out results.xml ./...
gosec -fmt html -out results.html ./...
gosec -fmt text -out results.txt ./...
gosec -fmt yaml -out results.yaml ./...For multi-scanner triage integration, use JSON.
Per gs-gh the canonical inline suppression syntax:
// #nosec G404 -- justification textFormat: #nosec [RuleList] [-- Justification].
| Mechanism | Example | When to use |
|---|---|---|
Per-line #nosec | // #nosec G101 -- test fixture; not deployed to prod | Single-line exception with justification |
Per-rule list #nosec | // #nosec G104,G115 -- intentional in this fast-path | Multi-rule single-line |
-exclude= flag | gosec -exclude=G104 ./... | Project-wide rule disable (CI flag) |
-confidence= filter | gosec -confidence=high ./... | Triage workflow: only high-confidence first |
Justification template (mandatory in code):
// #nosec G401 -- Reason: legacy MD5 required for vendor-mandated checksum format
// Reviewer: alice@example.com (2026-05-15)
// Expires: 2026-12-15
hash := md5.Sum(data)Cadence: every quarter, grep for #nosec patterns lacking -- Reason:
and flag for review.
Most Go teams run gosec via golangci-lint (the universal linter runner) rather than directly:
# .golangci.yml
linters:
enable:
- gosec
linters-settings:
gosec:
excludes:
- G104 # unhandled errors (often noise)
severity: medium
confidence: medium
config:
G306: "0644" # default file perm thresholdgolangci-lint run ./...This is the recommended pattern - golangci-lint handles parallelism,
caching, and unified output across multiple linters.
Standalone:
jobs:
gosec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- uses: securego/gosec@master
with:
args: -fmt sarif -out gosec.sarif ./...
- uses: github/codeql-action/upload-sarif@v3
if: always()
with: { sarif_file: gosec.sarif }Via golangci-lint (preferred):
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with: { go-version: '1.22' }
- uses: golangci/golangci-lint-action@v6
with:
version: latestUnlike Semgrep / CodeQL, gosec doesn't have a custom-rule DSL - adding
new rules requires writing Go code in gosec/rules/ and contributing
upstream OR forking. For most teams, leverage the 40+ built-in rules +
suppressions.
A Go microservice needs to clear a security review. Run
gosec -severity=high -confidence=high ./...; gosec reports a G401 on a
md5.Sum(data) call and a G104 on an unchecked w.Write return.
G401 is a false positive here - the MD5 is a vendor-mandated checksum, not a security hash. Suppress it with an audited justification:
// #nosec G401 -- Reason: legacy MD5 required for vendor-mandated checksum format
hash := md5.Sum(data)G104 is a real bug - the unhandled w.Write error is fixed by checking
its return value, not suppressed.
Re-run gosec -fmt sarif -out gosec.sarif ./...; the SARIF now reports
zero HIGH findings, and the golangci-lint gate passes in CI.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
#nosec without rule ID | Suppresses ALL rules on that line | // #nosec G401 (specific) |
#nosec without -- Justification | No audit trail | Required template |
Skip -confidence= filter | LOW confidence drowns the team | -confidence=high for triage |
| Run gosec separately from golangci-lint | Two linters with different config | golangci-lint integration |
| Exclude G104 globally | Loses entire "unhandled errors" coverage | Per-call // #nosec G104 -- intentional |
codeql-queries
catches.gosec -list-rules - current rule catalog