Files
Charon/docs/plans/current_spec.md
GitHub Actions 70bd60dbce chore: Implement CodeQL CI Alignment and Security Scanning
- Added comprehensive QA report for CodeQL CI alignment implementation, detailing tests, results, and findings.
- Created CodeQL security scanning guide in documentation, outlining usage and common issues.
- Developed pre-commit hooks for CodeQL scans and findings checks, ensuring security issues are identified before commits.
- Implemented scripts for running CodeQL Go and JavaScript scans, aligned with CI configurations.
- Verified all tests passed, including backend and frontend coverage, TypeScript checks, and SARIF file generation.
2025-12-24 14:35:33 +00:00

1630 lines
50 KiB
Markdown

# CWE-918 (SSRF) Comprehensive Mitigation Plan
**Status:** Ready for Implementation
**Priority:** CRITICAL
**CWE Reference:** CWE-918 (Server-Side Request Forgery)
**Created:** December 24, 2025
**Supersedes:** `ssrf_remediation_spec.md` (Previous plan - now archived reference)
---
## Executive Summary
This plan implements a **three-layer defense-in-depth strategy** for SSRF mitigation:
1. **Input Validation** - Strictly allowlist schemes and domains
2. **Network Layer ("Safe Dialer")** - Validate IP addresses at connection time (prevents DNS Rebinding)
3. **Client Configuration** - Disable/validate redirects, enforce timeouts
### Current State Analysis
**Good News:** The codebase already has substantial SSRF protection:
-`internal/security/url_validator.go` - Comprehensive URL validation with IP blocking
-`internal/utils/url_testing.go` - SSRF-safe dialer implementation exists
-`internal/services/notification_service.go` - Uses security validation
**Gaps Identified:**
- ⚠️ Multiple `isPrivateIP` implementations exist (should be consolidated)
- ⚠️ HTTP clients not using the safe dialer consistently
- ⚠️ Some services create their own `http.Client` without SSRF protection
---
## Phase 1: Create the Safe Network Package
**Goal:** Centralize all SSRF protection into a single, reusable package.
### 1.1 File Location
**New File:** `/backend/internal/network/safeclient.go`
### 1.2 Functions to Implement
#### `isPrivateIP(ip net.IP) bool`
Checks if an IP is in private/reserved ranges. Consolidates existing implementations.
**CIDR Ranges to Block:**
```go
var privateBlocks = []string{
// IPv4 Private Networks (RFC 1918)
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
// IPv4 Link-Local (RFC 3927) - includes AWS/GCP metadata
"169.254.0.0/16",
// IPv4 Loopback
"127.0.0.0/8",
// IPv4 Reserved ranges
"0.0.0.0/8", // "This network"
"240.0.0.0/4", // Reserved for future use
"255.255.255.255/32", // Broadcast
// IPv6 Loopback
"::1/128",
// IPv6 Unique Local Addresses (RFC 4193)
"fc00::/7",
// IPv6 Link-Local
"fe80::/10",
}
```
#### `safeDialer(timeout time.Duration) func(ctx context.Context, network, addr string) (net.Conn, error)`
Custom dial function that:
1. Parses host:port from address
2. Resolves DNS with context timeout
3. Validates ALL resolved IPs against `isPrivateIP`
4. Dials using the validated IP (prevents DNS rebinding)
```go
func safeDialer(timeout time.Duration) func(ctx context.Context, network, addr string) (net.Conn, error) {
return func(ctx context.Context, network, addr string) (net.Conn, error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, fmt.Errorf("invalid address: %w", err)
}
// Resolve DNS
ips, err := net.DefaultResolver.LookupIPAddr(ctx, host)
if err != nil {
return nil, fmt.Errorf("DNS resolution failed: %w", err)
}
if len(ips) == 0 {
return nil, fmt.Errorf("no IP addresses found")
}
// Validate ALL IPs
for _, ip := range ips {
if isPrivateIP(ip.IP) {
return nil, fmt.Errorf("connection to private IP blocked: %s", ip.IP)
}
}
// Connect to first validated IP
dialer := &net.Dialer{Timeout: timeout}
return dialer.DialContext(ctx, network, net.JoinHostPort(ips[0].IP.String(), port))
}
}
```
#### `NewSafeHTTPClient(opts ...Option) *http.Client`
Creates an HTTP client with:
- Safe dialer for SSRF protection
- Configurable timeout (default: 10s)
- Disabled keep-alives (prevents connection reuse attacks)
- Redirect validation (blocks redirects to private IPs)
```go
type ClientOptions struct {
Timeout time.Duration
AllowRedirects bool
MaxRedirects int
AllowLocalhost bool // For testing only
}
func NewSafeHTTPClient(opts ...Option) *http.Client {
cfg := defaultOptions()
for _, opt := range opts {
opt(&cfg)
}
return &http.Client{
Timeout: cfg.Timeout,
Transport: &http.Transport{
DialContext: safeDialer(cfg.Timeout),
DisableKeepAlives: true,
MaxIdleConns: 1,
IdleConnTimeout: cfg.Timeout,
TLSHandshakeTimeout: 10 * time.Second,
},
CheckRedirect: func(req *http.Request, via []*http.Request) error {
if !cfg.AllowRedirects {
return http.ErrUseLastResponse
}
if len(via) >= cfg.MaxRedirects {
return fmt.Errorf("too many redirects (max %d)", cfg.MaxRedirects)
}
// Validate redirect destination
return validateRedirectTarget(req.URL)
},
}
}
```
### 1.3 Test File
**New File:** `/backend/internal/network/safeclient_test.go`
**Test Cases:**
```go
func TestIsPrivateIP(t *testing.T) {
tests := []struct {
ip string
isPrivate bool
}{
// IPv4 Private (RFC 1918)
{"10.0.0.1", true},
{"10.255.255.255", true},
{"172.16.0.1", true},
{"172.31.255.255", true},
{"192.168.0.1", true},
{"192.168.255.255", true},
// IPv4 Loopback
{"127.0.0.1", true},
{"127.255.255.255", true},
// Cloud metadata endpoints
{"169.254.169.254", true}, // AWS/Azure
{"169.254.0.1", true},
// IPv4 Reserved
{"0.0.0.0", true},
{"240.0.0.1", true},
{"255.255.255.255", true},
// IPv6 Loopback
{"::1", true},
// IPv6 Unique Local (fc00::/7)
{"fc00::1", true},
{"fd00::1", true},
// IPv6 Link-Local
{"fe80::1", true},
// Public IPs (should NOT be blocked)
{"8.8.8.8", false},
{"1.1.1.1", false},
{"203.0.113.1", false},
{"2001:4860:4860::8888", false},
}
for _, tt := range tests {
t.Run(tt.ip, func(t *testing.T) {
ip := net.ParseIP(tt.ip)
if ip == nil {
t.Fatalf("invalid IP: %s", tt.ip)
}
got := isPrivateIP(ip)
if got != tt.isPrivate {
t.Errorf("isPrivateIP(%s) = %v, want %v", tt.ip, got, tt.isPrivate)
}
})
}
}
func TestSafeDialer_BlocksPrivateIPs(t *testing.T) {
// Test with mock DNS resolver
}
func TestNewSafeHTTPClient_BlocksSSRF(t *testing.T) {
// Integration tests
}
```
---
## Phase 2: Update Existing Code to Use Safe Client
### 2.1 Files Requiring Updates
| File | Current Pattern | Change Required |
|------|----------------|-----------------|
| `internal/services/notification_service.go:205` | `&http.Client{Timeout: 10s}` | Use `network.NewSafeHTTPClient()` |
| `internal/services/security_notification_service.go:130` | `&http.Client{Timeout: 10s}` | Use `network.NewSafeHTTPClient()` |
| `internal/services/update_service.go:112` | `&http.Client{Timeout: 5s}` | Use `network.NewSafeHTTPClient(WithTimeout(5s))` |
| `internal/crowdsec/registration.go:136,176,211` | `&http.Client{Timeout: defaultHealthTimeout}` | Use `network.NewSafeHTTPClient()` (localhost-only allowed) |
| `internal/crowdsec/hub_sync.go:185` | Custom Transport | Use `network.NewSafeHTTPClient()` with hub domain allowlist |
### 2.2 Specific Changes
#### notification_service.go (Lines 204-212)
**Current:**
```go
client := &http.Client{
Timeout: 10 * time.Second,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
```
**Change To:**
```go
import "github.com/Wikid82/charon/backend/internal/network"
client := network.NewSafeHTTPClient(
network.WithTimeout(10 * time.Second),
network.WithAllowLocalhost(), // For testing
)
```
#### security_notification_service.go (Line 130)
**Current:**
```go
client := &http.Client{Timeout: 10 * time.Second}
```
**Change To:**
```go
client := network.NewSafeHTTPClient(
network.WithTimeout(10 * time.Second),
network.WithAllowLocalhost(),
)
```
#### update_service.go (Line 112)
**Current:**
```go
client := &http.Client{Timeout: 5 * time.Second}
```
**Change To:**
```go
// Note: update_service.go already has domain allowlist (github.com only)
// Add safe client for defense in depth
client := network.NewSafeHTTPClient(
network.WithTimeout(5 * time.Second),
)
```
#### crowdsec/hub_sync.go (Lines 173-190)
**Current:**
```go
func newHubHTTPClient(timeout time.Duration) *http.Client {
transport := &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 10 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
// ...
}
return &http.Client{...}
}
```
**Change To:**
```go
func newHubHTTPClient(timeout time.Duration) *http.Client {
// Hub URLs are already validated by validateHubURL() which:
// - Enforces HTTPS for production
// - Allowlists known CrowdSec domains
// - Allows localhost for testing
// Add safe dialer for defense-in-depth
return network.NewSafeHTTPClient(
network.WithTimeout(timeout),
network.WithAllowedDomains(
"hub-data.crowdsec.net",
"hub.crowdsec.net",
"raw.githubusercontent.com",
),
)
}
```
#### crowdsec/registration.go
**Current (Lines 136, 176, 211):**
```go
client := &http.Client{Timeout: defaultHealthTimeout}
```
**Change To:**
```go
// LAPI is validated to be localhost only by validateLAPIURL()
// Use safe client but allow localhost
client := network.NewSafeHTTPClient(
network.WithTimeout(defaultHealthTimeout),
network.WithAllowLocalhost(),
)
```
---
## Phase 3: Comprehensive Testing
### 3.1 Unit Test Files
| Test File | Purpose |
|-----------|---------|
| `internal/network/safeclient_test.go` | Unit tests for IP validation, safe dialer |
| `internal/security/url_validator_test.go` | Already exists - extend with edge cases |
| `internal/utils/url_testing_test.go` | Already has SSRF tests - verify alignment |
### 3.2 Integration Test File
**New File:** `/backend/integration/ssrf_protection_test.go`
```go
//go:build integration
package integration
import (
"net/http/httptest"
"testing"
)
func TestSSRFProtection_EndToEnd(t *testing.T) {
// Test 1: Webhook to private IP is blocked
// Test 2: Webhook to public IP works
// Test 3: DNS rebinding attack is blocked
// Test 4: Redirect to private IP is blocked
// Test 5: Cloud metadata endpoint is blocked
}
func TestSSRFProtection_DNSRebinding(t *testing.T) {
// Setup mock DNS that changes resolution
// First: returns public IP (passes validation)
// Second: returns private IP (should be blocked at dial time)
}
```
### 3.3 Test Coverage Targets
| Package | Current Coverage | Target |
|---------|-----------------|--------|
| `internal/network` | NEW | 95%+ |
| `internal/security` | ~85% | 95%+ |
| `internal/utils` (url_testing.go) | ~80% | 90%+ |
---
## Phase 4: Code Consolidation
### 4.1 Duplicate `isPrivateIP` Functions to Consolidate
Currently found in:
1. `internal/security/url_validator.go:isPrivateIP()` - Comprehensive
2. `internal/utils/url_testing.go:isPrivateIP()` - Comprehensive
3. `internal/services/notification_service.go:isPrivateIP()` - Partial
4. `internal/utils/ip_helpers.go:IsPrivateIP()` - IPv4 only
**Action:** Keep `internal/network/safeclient.go:IsPrivateIP()` as the canonical implementation and update all other files to import from `network` package.
### 4.2 Migration Strategy
1. Create `internal/network/safeclient.go` with `IsPrivateIP()` exported
2. Update `internal/security/url_validator.go` to use `network.IsPrivateIP()`
3. Update `internal/utils/url_testing.go` to use `network.IsPrivateIP()`
4. Update `internal/services/notification_service.go` to use `network.IsPrivateIP()`
5. Deprecate `internal/utils/ip_helpers.go:IsPrivateIP()` (keep for backward compat, wrap network package)
---
## Phase 5: Documentation Updates
### 5.1 Files to Update
| File | Change |
|------|--------|
| `docs/security/ssrf-protection.md` | Already exists - update with new package location |
| `SECURITY.md` | Add section on SSRF protection |
| Inline code docs | Add godoc comments to all new functions |
### 5.2 API Documentation
Document in `docs/api.md`:
- Webhook URL validation requirements
- Allowed/blocked URL patterns
- Error messages and their meanings
---
## Configuration Files Review
### .gitignore ✅
Already ignores:
- `codeql-db-*/`
- `*.sarif`
- Test artifacts
**No changes needed.**
### .dockerignore ✅
Already ignores:
- `codeql-db-*/`
- `*.sarif`
- Test artifacts
- `coverage/`
**No changes needed.**
### codecov.yml
**Verify coverage thresholds include new package:**
```yaml
coverage:
status:
project:
default:
target: 85%
patch:
default:
target: 90%
```
**No changes needed** (new package will be automatically included).
### Dockerfile ✅
The SSRF protection is runtime code - no Dockerfile changes needed.
---
## Implementation Checklist
### Week 1: Core Implementation
- [ ] Create `/backend/internal/network/` directory
- [ ] Implement `safeclient.go` with:
- [ ] `IsPrivateIP()` function
- [ ] `safeDialer()` function
- [ ] `NewSafeHTTPClient()` function
- [ ] Option pattern (WithTimeout, WithAllowLocalhost, etc.)
- [ ] Create `safeclient_test.go` with comprehensive tests
- [ ] Run tests: `go test ./internal/network/...`
### Week 2: Integration
- [ ] Update `internal/services/notification_service.go`
- [ ] Update `internal/services/security_notification_service.go`
- [ ] Update `internal/services/update_service.go`
- [ ] Update `internal/crowdsec/registration.go`
- [ ] Update `internal/crowdsec/hub_sync.go`
- [ ] Consolidate duplicate `isPrivateIP` implementations
- [ ] Run full test suite: `go test ./...`
### Week 3: Testing & Documentation
- [ ] Create integration tests
- [ ] Run CodeQL scan to verify SSRF fixes
- [ ] Update documentation
- [ ] Code review
- [ ] Merge to main
---
## Risk Mitigation
### Risk 1: Breaking Localhost Testing
**Mitigation:** `WithAllowLocalhost()` option explicitly enables localhost for testing environments.
### Risk 2: Breaking Legitimate Internal Services
**Mitigation:**
- CrowdSec LAPI: Allowed via localhost exception
- CrowdSec Hub: Domain allowlist (crowdsec.net, github.com)
- Internal services should use service discovery, not hardcoded IPs
### Risk 3: DNS Resolution Overhead
**Mitigation:** Safe dialer performs DNS resolution during dial, which is the standard pattern. No additional overhead for most use cases.
---
## Success Criteria
1. ✅ All HTTP clients use `network.NewSafeHTTPClient()`
2. ✅ No direct `&http.Client{}` construction in service code
3. ✅ CodeQL scan shows no CWE-918 findings
4. ✅ All tests pass (unit + integration)
5. ✅ Coverage > 85% for new package
6. ✅ Documentation updated
---
## File Tree Summary
```
backend/
├── internal/
│ ├── network/ # NEW PACKAGE
│ │ ├── safeclient.go # IsPrivateIP, safeDialer, NewSafeHTTPClient
│ │ └── safeclient_test.go # Comprehensive unit tests
│ │
│ ├── security/
│ │ ├── url_validator.go # UPDATE: Use network.IsPrivateIP
│ │ └── url_validator_test.go # Existing tests
│ │
│ ├── services/
│ │ ├── notification_service.go # UPDATE: Use NewSafeHTTPClient
│ │ ├── security_notification_service.go # UPDATE: Use NewSafeHTTPClient
│ │ └── update_service.go # UPDATE: Use NewSafeHTTPClient
│ │
│ ├── crowdsec/
│ │ ├── hub_sync.go # UPDATE: Use NewSafeHTTPClient
│ │ └── registration.go # UPDATE: Use NewSafeHTTPClient
│ │
│ └── utils/
│ ├── url_testing.go # UPDATE: Use network.IsPrivateIP
│ └── ip_helpers.go # DEPRECATE: Wrap network.IsPrivateIP
├── integration/
│ └── ssrf_protection_test.go # NEW: Integration tests
```
---
## References
- Previous spec: `docs/plans/ssrf_remediation_spec.md`
- OWASP SSRF Prevention: https://owasp.org/www-community/vulnerabilities/SSRF
- CWE-918: https://cwe.mitre.org/data/definitions/918.html
- Go net package: https://pkg.go.dev/net
---
**Document Version:** 1.0
**Last Updated:** December 24, 2025
**Owner:** Security Team
**Status:** READY FOR IMPLEMENTATION
---
## Phase 1: CodeQL Task Alignment (PRIORITY 1)
### Problem Analysis
**Current Local Configuration** (`.vscode/tasks.json`):
```bash
# Go Task
codeql database create codeql-db-go --language=go --source-root=backend --overwrite && \
codeql database analyze codeql-db-go \
/projects/codeql/codeql/go/ql/src/codeql-suites/go-security-extended.qls \
--format=sarif-latest --output=codeql-results-go.sarif
# JavaScript Task
codeql database create codeql-db-js --language=javascript --source-root=frontend --overwrite && \
codeql database analyze codeql-db-js \
/projects/codeql/codeql/javascript/ql/src/codeql-suites/javascript-security-extended.qls \
--format=sarif-latest --output=codeql-results-js.sarif
```
**Issues:**
1. **Wrong Query Suite:** Using `security-extended` instead of `security-and-quality`
2. **Hardcoded Paths:** Using `/projects/codeql/...` which doesn't exist (causes fallback to installed packs)
3. **Missing CI Parameters:** Not using same threading, memory limits, or build flags as CI
4. **No Results Summary:** Raw SARIF output without human-readable summary
**GitHub Actions CI Configuration** (`.github/workflows/codeql.yml`):
- Uses `github/codeql-action/init@v4` which defaults to `security-and-quality` suite
- Runs autobuild step for compilation
- Uploads results to GitHub Security tab
- Matrix strategy: `['go', 'javascript-typescript']`
### Solution: Exact CI Replication
**File:** `.vscode/tasks.json`
**New Task Configuration:**
```json
{
"label": "Security: CodeQL Go Scan (CI-Aligned)",
"type": "shell",
"command": "bash -c 'set -e && \
echo \"🔍 Creating CodeQL database for Go...\" && \
rm -rf codeql-db-go && \
codeql database create codeql-db-go \
--language=go \
--source-root=backend \
--overwrite \
--threads=0 && \
echo \"\" && \
echo \"📊 Running CodeQL analysis (security-and-quality suite)...\" && \
codeql database analyze codeql-db-go \
codeql/go-queries:codeql-suites/go-security-and-quality.qls \
--format=sarif-latest \
--output=codeql-results-go.sarif \
--sarif-add-baseline-file-info \
--threads=0 && \
echo \"\" && \
echo \"✅ CodeQL scan complete. Results: codeql-results-go.sarif\" && \
echo \"\" && \
echo \"📋 Summary of findings:\" && \
codeql database interpret-results codeql-db-go \
--format=text \
--output=/dev/stdout \
codeql/go-queries:codeql-suites/go-security-and-quality.qls 2>/dev/null || \
(echo \"⚠️ Use SARIF Viewer extension to view detailed results\" && jq -r \".runs[].results[] | \\\"\\(.level): \\(.message.text) (\\(.locations[0].physicalLocation.artifactLocation.uri):\\(.locations[0].physicalLocation.region.startLine))\\\"\" codeql-results-go.sarif 2>/dev/null | head -20 || echo \"No findings or jq not available\")'",
"group": "test",
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": false
}
},
{
"label": "Security: CodeQL JS Scan (CI-Aligned)",
"type": "shell",
"command": "bash -c 'set -e && \
echo \"🔍 Creating CodeQL database for JavaScript/TypeScript...\" && \
rm -rf codeql-db-js && \
codeql database create codeql-db-js \
--language=javascript \
--source-root=frontend \
--overwrite \
--threads=0 && \
echo \"\" && \
echo \"📊 Running CodeQL analysis (security-and-quality suite)...\" && \
codeql database analyze codeql-db-js \
codeql/javascript-queries:codeql-suites/javascript-security-and-quality.qls \
--format=sarif-latest \
--output=codeql-results-js.sarif \
--sarif-add-baseline-file-info \
--threads=0 && \
echo \"\" && \
echo \"✅ CodeQL scan complete. Results: codeql-results-js.sarif\" && \
echo \"\" && \
echo \"📋 Summary of findings:\" && \
codeql database interpret-results codeql-db-js \
--format=text \
--output=/dev/stdout \
codeql/javascript-queries:codeql-suites/javascript-security-and-quality.qls 2>/dev/null || \
(echo \"⚠️ Use SARIF Viewer extension to view detailed results\" && jq -r \".runs[].results[] | \\\"\\(.level): \\(.message.text) (\\(.locations[0].physicalLocation.artifactLocation.uri):\\(.locations[0].physicalLocation.region.startLine))\\\"\" codeql-results-js.sarif 2>/dev/null | head -20 || echo \"No findings or jq not available\")'",
"group": "test",
"problemMatcher": [],
"presentation": {
"echo": true,
"reveal": "always",
"focus": false,
"panel": "shared",
"showReuseMessage": false,
"clear": false
}
},
{
"label": "Security: CodeQL All (CI-Aligned)",
"type": "shell",
"dependsOn": ["Security: CodeQL Go Scan (CI-Aligned)", "Security: CodeQL JS Scan (CI-Aligned)"],
"dependsOrder": "sequence",
"group": "test",
"problemMatcher": []
}
```
**Key Changes:**
1.**Correct Query Suite:** `security-and-quality` (matches CI default)
2.**Proper Pack References:** `codeql/go-queries:codeql-suites/...` format
3.**Threading:** `--threads=0` (auto-detect, same as CI)
4.**Baseline Info:** `--sarif-add-baseline-file-info` flag
5.**Human-Readable Output:** Attempts text summary, falls back to jq parsing
6.**Clean Database:** Removes old DB before creating new one
7.**Combined Task:** "Security: CodeQL All" runs both sequentially
**SARIF Viewing:**
- Primary: VS Code SARIF Viewer extension (recommended: `MS-SarifVSCode.sarif-viewer`)
- Fallback: `jq` command-line parsing for quick overview
- Alternative: Upload to GitHub Security tab manually
---
## Phase 2: Pre-Commit Integration
### Problem Analysis
**Current Pre-Commit Configuration** (`.pre-commit-config.yaml`):
- ✅ Has manual-stage hook for `security-scan` (govulncheck only)
- ❌ No CodeQL integration
- ❌ No severity-based blocking
### Solution: Add CodeQL Pre-Commit Hooks
**File:** `.pre-commit-config.yaml`
**Add to `repos[local].hooks` section:**
```yaml
- id: codeql-go-scan
name: CodeQL Go Security Scan (Manual - Slow)
entry: scripts/pre-commit-hooks/codeql-go-scan.sh
language: script
files: '\.go$'
pass_filenames: false
verbose: true
stages: [manual] # Performance: 30-60s, only run on-demand
- id: codeql-js-scan
name: CodeQL JavaScript/TypeScript Security Scan (Manual - Slow)
entry: scripts/pre-commit-hooks/codeql-js-scan.sh
language: script
files: '^frontend/.*\.(ts|tsx|js|jsx)$'
pass_filenames: false
verbose: true
stages: [manual] # Performance: 30-60s, only run on-demand
- id: codeql-check-findings
name: Block HIGH/CRITICAL CodeQL Findings
entry: scripts/pre-commit-hooks/codeql-check-findings.sh
language: script
pass_filenames: false
verbose: true
stages: [manual] # Only runs after CodeQL scans
```
### New Script: `scripts/pre-commit-hooks/codeql-go-scan.sh`
```bash
#!/bin/bash
# Pre-commit CodeQL Go scan - CI-aligned
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}🔍 Running CodeQL Go scan (CI-aligned)...${NC}"
echo ""
# Clean previous database
rm -rf codeql-db-go
# Create database
echo "📦 Creating CodeQL database..."
codeql database create codeql-db-go \
--language=go \
--source-root=backend \
--threads=0 \
--overwrite
echo ""
echo "📊 Analyzing with security-and-quality suite..."
# Analyze with CI-aligned suite
codeql database analyze codeql-db-go \
codeql/go-queries:codeql-suites/go-security-and-quality.qls \
--format=sarif-latest \
--output=codeql-results-go.sarif \
--sarif-add-baseline-file-info \
--threads=0
echo -e "${GREEN}✅ CodeQL Go scan complete${NC}"
echo "Results saved to: codeql-results-go.sarif"
echo ""
echo "Run 'pre-commit run codeql-check-findings' to validate findings"
```
### New Script: `scripts/pre-commit-hooks/codeql-js-scan.sh`
```bash
#!/bin/bash
# Pre-commit CodeQL JavaScript/TypeScript scan - CI-aligned
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
echo -e "${BLUE}🔍 Running CodeQL JavaScript/TypeScript scan (CI-aligned)...${NC}"
echo ""
# Clean previous database
rm -rf codeql-db-js
# Create database
echo "📦 Creating CodeQL database..."
codeql database create codeql-db-js \
--language=javascript \
--source-root=frontend \
--threads=0 \
--overwrite
echo ""
echo "📊 Analyzing with security-and-quality suite..."
# Analyze with CI-aligned suite
codeql database analyze codeql-db-js \
codeql/javascript-queries:codeql-suites/javascript-security-and-quality.qls \
--format=sarif-latest \
--output=codeql-results-js.sarif \
--sarif-add-baseline-file-info \
--threads=0
echo -e "${GREEN}✅ CodeQL JavaScript/TypeScript scan complete${NC}"
echo "Results saved to: codeql-results-js.sarif"
echo ""
echo "Run 'pre-commit run codeql-check-findings' to validate findings"
```
### New Script: `scripts/pre-commit-hooks/codeql-check-findings.sh`
```bash
#!/bin/bash
# Check CodeQL SARIF results for HIGH/CRITICAL findings
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
FAILED=0
check_sarif() {
local sarif_file=$1
local lang=$2
if [ ! -f "$sarif_file" ]; then
echo -e "${YELLOW}⚠️ No SARIF file found: $sarif_file${NC}"
echo "Run CodeQL scan first: pre-commit run codeql-$lang-scan --all-files"
return 0
fi
echo "🔍 Checking $lang findings..."
# Check for findings using jq (if available)
if command -v jq &> /dev/null; then
# Count high/critical severity findings
HIGH_COUNT=$(jq -r '.runs[].results[] | select(.level == "error" or .level == "warning") | .level' "$sarif_file" 2>/dev/null | wc -l || echo 0)
if [ "$HIGH_COUNT" -gt 0 ]; then
echo -e "${RED}❌ Found $HIGH_COUNT potential security issues in $lang code${NC}"
echo ""
echo "Summary:"
jq -r '.runs[].results[] | "\(.level): \(.message.text) (\(.locations[0].physicalLocation.artifactLocation.uri):\(.locations[0].physicalLocation.region.startLine))"' "$sarif_file" 2>/dev/null | head -10
echo ""
echo "View full results: code $sarif_file"
FAILED=1
else
echo -e "${GREEN}✅ No security issues found in $lang code${NC}"
fi
else
# Fallback: check if file has results
if grep -q '"results"' "$sarif_file" && ! grep -q '"results": \[\]' "$sarif_file"; then
echo -e "${YELLOW}⚠️ CodeQL findings detected in $lang (install jq for details)${NC}"
echo "View results: code $sarif_file"
FAILED=1
else
echo -e "${GREEN}✅ No security issues found in $lang code${NC}"
fi
fi
}
echo "🔒 Checking CodeQL findings..."
echo ""
check_sarif "codeql-results-go.sarif" "go"
check_sarif "codeql-results-js.sarif" "js"
if [ $FAILED -eq 1 ]; then
echo ""
echo -e "${RED}❌ CodeQL scan found security issues. Please fix before committing.${NC}"
echo ""
echo "To view results:"
echo " - VS Code: Install SARIF Viewer extension"
echo " - Command line: jq . codeql-results-*.sarif"
exit 1
fi
echo ""
echo -e "${GREEN}✅ All CodeQL checks passed${NC}"
```
**Make scripts executable:**
```bash
chmod +x scripts/pre-commit-hooks/codeql-*.sh
```
### Usage Instructions for Developers
**Quick Security Check (Fast - 5s):**
```bash
pre-commit run security-scan --all-files
```
**Full CodeQL Scan (Slow - 2-3min):**
```bash
# Scan Go code
pre-commit run codeql-go-scan --all-files
# Scan JavaScript/TypeScript code
pre-commit run codeql-js-scan --all-files
# Check for HIGH/CRITICAL findings
pre-commit run codeql-check-findings --all-files
```
**Combined Workflow:**
```bash
# Run all security checks
pre-commit run security-scan codeql-go-scan codeql-js-scan codeql-check-findings --all-files
```
---
## Phase 3: CI/CD Enhancement
### Current CI Analysis
**Strengths:**
- ✅ Runs on push/PR to main, development, feature branches
- ✅ Matrix strategy for multiple languages
- ✅ Results uploaded to GitHub Security tab
- ✅ Scheduled weekly scan (Monday 3 AM)
**Weaknesses:**
- ❌ No blocking on HIGH/CRITICAL findings
- ❌ No PR comments with findings summary
- ❌ Forked PRs skip security checks (intentional, but should be documented)
### Solution: Enhanced CI Workflow
**File:** `.github/workflows/codeql.yml`
**Add after analysis step:**
```yaml
- name: Check CodeQL Results
if: always()
run: |
echo "## 🔒 CodeQL Security Analysis Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Language:** ${{ matrix.language }}" >> $GITHUB_STEP_SUMMARY
echo "**Query Suite:** security-and-quality" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Check if SARIF file exists and has results
SARIF_FILE="${HOME}/work/_temp/codeql-action-results/codeql-action-results-${{ matrix.language }}.sarif"
if [ -f "$SARIF_FILE" ]; then
RESULT_COUNT=$(jq '.runs[].results | length' "$SARIF_FILE" 2>/dev/null || echo 0)
ERROR_COUNT=$(jq '[.runs[].results[] | select(.level == "error")] | length' "$SARIF_FILE" 2>/dev/null || echo 0)
WARNING_COUNT=$(jq '[.runs[].results[] | select(.level == "warning")] | length' "$SARIF_FILE" 2>/dev/null || echo 0)
NOTE_COUNT=$(jq '[.runs[].results[] | select(.level == "note")] | length' "$SARIF_FILE" 2>/dev/null || echo 0)
echo "**Findings:**" >> $GITHUB_STEP_SUMMARY
echo "- 🔴 Errors: $ERROR_COUNT" >> $GITHUB_STEP_SUMMARY
echo "- 🟡 Warnings: $WARNING_COUNT" >> $GITHUB_STEP_SUMMARY
echo "- 🔵 Notes: $NOTE_COUNT" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$ERROR_COUNT" -gt 0 ]; then
echo "❌ **CRITICAL:** High-severity security issues found!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Top Issues:" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
jq -r '.runs[].results[] | select(.level == "error") | "\(.ruleId): \(.message.text)"' "$SARIF_FILE" 2>/dev/null | head -5 >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
else
echo "✅ No high-severity issues found" >> $GITHUB_STEP_SUMMARY
fi
else
echo "⚠️ SARIF file not found - check analysis logs" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "View full results in the [Security tab](https://github.com/${{ github.repository }}/security/code-scanning)" >> $GITHUB_STEP_SUMMARY
- name: Fail on High-Severity Findings
if: always()
run: |
SARIF_FILE="${HOME}/work/_temp/codeql-action-results/codeql-action-results-${{ matrix.language }}.sarif"
if [ -f "$SARIF_FILE" ]; then
ERROR_COUNT=$(jq '[.runs[].results[] | select(.level == "error")] | length' "$SARIF_FILE" 2>/dev/null || echo 0)
if [ "$ERROR_COUNT" -gt 0 ]; then
echo "::error::CodeQL found $ERROR_COUNT high-severity security issues. Fix before merging."
exit 1
fi
fi
```
### New Workflow: Security Issue Creation
**File:** `.github/workflows/codeql-issue-reporter.yml`
```yaml
name: CodeQL - Create Issues for Findings
on:
workflow_run:
workflows: ["CodeQL - Analyze"]
types:
- completed
branches: [main, development]
permissions:
contents: read
security-events: read
issues: write
jobs:
create-issues:
name: Create GitHub Issues for CodeQL Findings
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
steps:
- uses: actions/checkout@v4
- name: Get CodeQL Alerts
id: get-alerts
uses: actions/github-script@v7
with:
script: |
const alerts = await github.rest.codeScanning.listAlertsForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
severity: 'high,critical'
});
console.log(`Found ${alerts.data.length} high/critical alerts`);
for (const alert of alerts.data.slice(0, 5)) { // Limit to 5 issues
const title = `[Security] ${alert.rule.security_severity_level}: ${alert.rule.description}`;
const body = `
## Security Alert from CodeQL
**Severity:** ${alert.rule.security_severity_level}
**Rule:** ${alert.rule.id}
**Location:** ${alert.most_recent_instance.location.path}:${alert.most_recent_instance.location.start_line}
### Description
${alert.rule.description}
### Message
${alert.most_recent_instance.message.text}
### View in CodeQL
${alert.html_url}
---
*This issue was automatically created from a CodeQL security scan.*
*Fix this issue and the corresponding CodeQL alert will automatically close.*
`;
// Check if issue already exists
const existingIssues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: 'security,codeql',
state: 'open'
});
const exists = existingIssues.data.some(issue =>
issue.title.includes(alert.rule.id)
);
if (!exists) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: title,
body: body,
labels: ['security', 'codeql', 'automated']
});
console.log(`Created issue for alert ${alert.number}`);
}
}
```
---
## Phase 4: Documentation & Training
### New Documentation: `docs/security/codeql-scanning.md`
**File:** `docs/security/codeql-scanning.md`
```markdown
# CodeQL Security Scanning Guide
## Overview
Charon uses GitHub's CodeQL for static application security testing (SAST). CodeQL analyzes code to find security vulnerabilities and coding errors.
## Quick Start
### Run CodeQL Locally (CI-Aligned)
**Via VS Code Tasks:**
1. Open Command Palette (`Ctrl+Shift+P` / `Cmd+Shift+P`)
2. Type "Tasks: Run Task"
3. Select:
- `Security: CodeQL Go Scan (CI-Aligned)` - Scan backend
- `Security: CodeQL JS Scan (CI-Aligned)` - Scan frontend
- `Security: CodeQL All (CI-Aligned)` - Scan both
**Via Pre-Commit:**
```bash
# Quick security check (govulncheck - 5s)
pre-commit run security-scan --all-files
# Full CodeQL scan (2-3 minutes)
pre-commit run codeql-go-scan --all-files
pre-commit run codeql-js-scan --all-files
pre-commit run codeql-check-findings --all-files
```
**Via Command Line:**
```bash
# Go scan
codeql database create codeql-db-go --language=go --source-root=backend --overwrite
codeql database analyze codeql-db-go \
codeql/go-queries:codeql-suites/go-security-and-quality.qls \
--format=sarif-latest --output=codeql-results-go.sarif
# JavaScript/TypeScript scan
codeql database create codeql-db-js --language=javascript --source-root=frontend --overwrite
codeql database analyze codeql-db-js \
codeql/javascript-queries:codeql-suites/javascript-security-and-quality.qls \
--format=sarif-latest --output=codeql-results-js.sarif
```
### View Results
**Method 1: VS Code SARIF Viewer (Recommended)**
1. Install extension: `MS-SarifVSCode.sarif-viewer`
2. Open `codeql-results-go.sarif` or `codeql-results-js.sarif`
3. Navigate findings with inline annotations
**Method 2: Command Line (jq)**
```bash
# Summary
jq '.runs[].results | length' codeql-results-go.sarif
# Details
jq -r '.runs[].results[] | "\(.level): \(.message.text) (\(.locations[0].physicalLocation.artifactLocation.uri):\(.locations[0].physicalLocation.region.startLine))"' codeql-results-go.sarif
```
**Method 3: GitHub Security Tab**
- CI automatically uploads results to: `https://github.com/YourOrg/Charon/security/code-scanning`
## Understanding Query Suites
Charon uses the **security-and-quality** suite (GitHub Actions default):
| Suite | Go Queries | JS Queries | Use Case |
|-------|-----------|-----------|----------|
| `security-extended` | 39 | 106 | Security-only, faster |
| `security-and-quality` | 61 | 204 | Security + quality, comprehensive (CI default) |
⚠️ **Important:** Local scans MUST use `security-and-quality` to match CI behavior.
## Severity Levels
- 🔴 **Error (High/Critical):** Must fix before merge - CI will fail
- 🟡 **Warning (Medium):** Should fix - CI continues
- 🔵 **Note (Low/Info):** Consider fixing - CI continues
## Common Issues & Fixes
### Issue: "CWE-918: Server-Side Request Forgery (SSRF)"
**Location:** `backend/internal/api/handlers/url_validator.go`
**Fix:**
```go
// BAD: Unrestricted URL
resp, err := http.Get(userProvidedURL)
// GOOD: Validate against allowlist
if !isAllowedHost(userProvidedURL) {
return ErrSSRFAttempt
}
resp, err := http.Get(userProvidedURL)
```
**Reference:** [docs/security/ssrf-protection.md](ssrf-protection.md)
### Issue: "CWE-079: Cross-Site Scripting (XSS)"
**Location:** `frontend/src/components/...`
**Fix:**
```typescript
// BAD: Unsafe HTML rendering
element.innerHTML = userInput;
// GOOD: Safe text content
element.textContent = userInput;
// GOOD: Sanitized HTML (if HTML is required)
import DOMPurify from 'dompurify';
element.innerHTML = DOMPurify.sanitize(userInput);
```
### Issue: "CWE-089: SQL Injection"
**Fix:** Use parameterized queries (GORM handles this automatically)
```go
// BAD: String concatenation
db.Raw("SELECT * FROM users WHERE name = '" + userName + "'")
// GOOD: Parameterized query
db.Where("name = ?", userName).Find(&users)
```
## CI/CD Integration
### When CodeQL Runs
- **Push:** Every commit to `main`, `development`, `feature/*`
- **Pull Request:** Every PR to `main`, `development`
- **Schedule:** Weekly scan on Monday at 3 AM UTC
### CI Behavior
**Allowed to merge:**
- No findings
- Only warnings/notes
- Forked PRs (security scanning skipped for permission reasons)
**Blocked from merge:**
- Any error-level (high/critical) findings
- CodeQL analysis failure
### Viewing CI Results
1. **PR Checks:** See "CodeQL analysis (go)" and "CodeQL analysis (javascript-typescript)" checks
2. **Security Tab:** Navigate to repo → Security → Code scanning alerts
3. **Workflow Summary:** Click on failed check → View job summary
## Troubleshooting
### "CodeQL passes locally but fails in CI"
**Cause:** Using wrong query suite locally
**Fix:** Ensure tasks use `security-and-quality`:
```bash
codeql database analyze DB_PATH \
codeql/LANGUAGE-queries:codeql-suites/LANGUAGE-security-and-quality.qls \
...
```
### "SARIF file not found"
**Cause:** Database creation or analysis failed
**Fix:**
1. Check terminal output for errors
2. Ensure CodeQL is installed: `codeql version`
3. Verify source-root exists: `ls backend/` or `ls frontend/`
### "Too many findings to fix"
**Strategy:**
1. Fix all **error** level first (CI blockers)
2. Create issues for **warning** level (non-blocking)
3. Document **note** level for future consideration
**Suppress false positives:**
```go
// codeql[go/sql-injection] - Safe: input is validated by ACL
db.Raw(query).Scan(&results)
```
## Performance Tips
- **Incremental Scans:** CodeQL caches databases, second run is faster
- **Parallel Execution:** Use `--threads=0` for auto-detection
- **CI Only:** Run full scans in CI, quick checks locally
## References
- [CodeQL Documentation](https://codeql.github.com/docs/)
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [CWE Database](https://cwe.mitre.org/)
- [Charon Security Policy](../SECURITY.md)
```
### Update Definition of Done
**File:** `.github/instructions/copilot-instructions.md`
**Section: "✅ Task Completion Protocol (Definition of Done)"**
**Replace Step 1 with:**
```markdown
1. **Security Scans** (MANDATORY - Zero Tolerance):
- **CodeQL Go Scan**: Run VS Code task "Security: CodeQL Go Scan (CI-Aligned)" OR `pre-commit run codeql-go-scan --all-files`
- Must use `security-and-quality` suite (CI-aligned)
- **Zero high/critical (error-level) findings allowed**
- Medium/low findings should be documented and triaged
- **CodeQL JS Scan**: Run VS Code task "Security: CodeQL JS Scan (CI-Aligned)" OR `pre-commit run codeql-js-scan --all-files`
- Must use `security-and-quality` suite (CI-aligned)
- **Zero high/critical (error-level) findings allowed**
- Medium/low findings should be documented and triaged
- **Validate Findings**: Run `pre-commit run codeql-check-findings --all-files` to check for HIGH/CRITICAL issues
- **Trivy Container Scan**: Run VS Code task "Security: Trivy Scan" for container/dependency vulnerabilities
- **Results Viewing**:
- Primary: VS Code SARIF Viewer extension (`MS-SarifVSCode.sarif-viewer`)
- Alternative: `jq` command-line parsing: `jq '.runs[].results' codeql-results-*.sarif`
- CI: GitHub Security tab for automated uploads
- **⚠️ CRITICAL:** CodeQL scans are NOT run by default pre-commit hooks (manual stage for performance). You MUST run them explicitly via VS Code tasks or pre-commit manual commands before completing any task.
- **Why:** CI enforces security-and-quality suite and blocks HIGH/CRITICAL findings. Local verification prevents CI failures and ensures security compliance.
- **CI Alignment:** Local scans now use identical parameters to CI:
- Query suite: `security-and-quality` (61 Go queries, 204 JS queries)
- Database creation: `--threads=0 --overwrite`
- Analysis: `--sarif-add-baseline-file-info`
```
---
## Implementation Checklist
### Phase 1: CodeQL Alignment
- [ ] Update `.vscode/tasks.json` with new CI-aligned tasks
- [ ] Remove old tasks: "Security: CodeQL Go Scan", "Security: CodeQL JS Scan"
- [ ] Add new tasks: "Security: CodeQL Go Scan (CI-Aligned)", "Security: CodeQL JS Scan (CI-Aligned)", "Security: CodeQL All (CI-Aligned)"
- [ ] Test Go scan: Run task and verify it uses `security-and-quality` suite
- [ ] Test JS scan: Run task and verify it uses `security-and-quality` suite
- [ ] Install VS Code SARIF Viewer extension for result viewing
- [ ] Verify SARIF files are generated correctly
### Phase 2: Pre-Commit Integration
- [ ] Create `scripts/pre-commit-hooks/codeql-go-scan.sh`
- [ ] Create `scripts/pre-commit-hooks/codeql-js-scan.sh`
- [ ] Create `scripts/pre-commit-hooks/codeql-check-findings.sh`
- [ ] Make scripts executable: `chmod +x scripts/pre-commit-hooks/codeql-*.sh`
- [ ] Update `.pre-commit-config.yaml` with new hooks
- [ ] Test hooks: `pre-commit run codeql-go-scan --all-files`
- [ ] Test findings check: `pre-commit run codeql-check-findings --all-files`
- [ ] Update `.gitignore` (already has `codeql-db-*/`, `*.sarif` - verify)
### Phase 3: CI/CD Enhancement
- [ ] Update `.github/workflows/codeql.yml` with result checking steps
- [ ] Create `.github/workflows/codeql-issue-reporter.yml` (optional)
- [ ] Test CI workflow on a test branch
- [ ] Verify step summary shows findings count
- [ ] Verify CI fails on high-severity findings
- [ ] Document CI behavior in workflow comments
### Phase 4: Documentation
- [ ] Create `docs/security/codeql-scanning.md`
- [ ] Update `.github/instructions/copilot-instructions.md` Definition of Done
- [ ] Update `docs/security.md` with CodeQL section (if needed)
- [ ] Add CodeQL badge to `README.md` (optional)
- [ ] Create troubleshooting guide section
- [ ] Document CI-local alignment in CONTRIBUTING.md
### Phase 5: Verification
- [ ] Run full security scan locally: `pre-commit run codeql-go-scan codeql-js-scan codeql-check-findings --all-files`
- [ ] Push to test branch and verify CI matches local results
- [ ] Verify no false positives between local and CI
- [ ] Test SARIF viewer integration in VS Code
- [ ] Confirm all documentation links work
---
## Success Metrics
### Before Implementation
- ❌ Local CodeQL uses different query suite than CI (security-extended vs security-and-quality)
- ❌ Security issues pass locally but fail in CI
- ❌ No pre-commit integration for CodeQL
- ❌ No clear developer workflow for security scans
- ❌ No automated issue creation for findings
### After Implementation
- ✅ Local CodeQL uses identical parameters to CI
- ✅ Local scan results match CI 100%
- ✅ Pre-commit hooks catch HIGH/CRITICAL issues before push
- ✅ Clear documentation and workflow for developers
- ✅ CI blocks merge on high-severity findings
- ✅ Automated GitHub Issues for critical vulnerabilities (optional)
---
## Timeline Estimate
- **Phase 1 (CodeQL Alignment):** 1-2 hours
- Update tasks.json: 30 min
- Testing and verification: 1 hour
- SARIF viewer setup: 30 min
- **Phase 2 (Pre-Commit Integration):** 2-3 hours
- Create scripts: 1 hour
- Update pre-commit config: 30 min
- Testing: 1 hour
- Troubleshooting: 30 min
- **Phase 3 (CI/CD Enhancement):** 1-2 hours
- Update codeql.yml: 30 min
- Create issue reporter (optional): 1 hour
- Testing: 30 min
- **Phase 4 (Documentation):** 2-3 hours
- Write security scanning guide: 1.5 hours
- Update copilot instructions: 30 min
- Update other docs: 1 hour
**Total Estimate:** 6-10 hours
---
## Risks & Mitigations
### Risk 1: Performance Impact on Pre-Commit
**Impact:** CodeQL scans take 2-3 minutes, slowing down commits
**Mitigation:** Use `stages: [manual]` - developers run scans on-demand, not on every commit
**Alternative:** CI catches issues, but slower feedback loop
### Risk 2: Breaking Changes to Existing Workflows
**Impact:** Developers accustomed to old tasks
**Mitigation:**
- Keep old task names with deprecation notice for 1 week
- Send announcement with migration guide
- Update all documentation immediately
### Risk 3: CI May Fail on Existing Code
**Impact:** Blocking all PRs if existing code has high-severity findings
**Mitigation:**
- Run full scan on main branch FIRST
- Fix or suppress existing findings before enforcing CI blocking
- Grandfather existing issues, block only new findings (use baseline)
### Risk 4: False Positives
**Impact:** Developers frustrated by incorrect findings
**Mitigation:**
- Document suppression syntax: `// codeql[rule-id] - Reason`
- Create triage process for false positives
- Contribute fixes to CodeQL queries if needed
---
## Rollout Plan
### Week 1: Development & Testing
- Implement Phase 1 (Tasks)
- Implement Phase 2 (Pre-Commit)
- Test on development branch
### Week 2: CI & Documentation
- Implement Phase 3 (CI Enhancement)
- Implement Phase 4 (Documentation)
- Run full scan on main branch, triage findings
### Week 3: Team Training
- Send announcement email with guide
- Hold team meeting to demo new workflow
- Create FAQ based on questions
### Week 4: Enforcement
- Enable CI blocking on HIGH/CRITICAL findings
- Monitor for issues
- Iterate on documentation
---
## References
- [CodeQL CLI Manual](https://codeql.github.com/docs/codeql-cli/)
- [CodeQL Query Suites](https://codeql.github.com/docs/codeql-cli/creating-codeql-query-suites/)
- [GitHub Actions CodeQL Action](https://github.com/github/codeql-action)
- [SARIF Format](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html)
- [Pre-Commit Manual Stages](https://pre-commit.com/#passing-arguments-to-hooks)
---
## Appendix A: Query Suite Comparison
### Go Queries
**security-extended (39 queries):**
- Focus: Pure security vulnerabilities
- CWE coverage: SSRF, XSS, SQL Injection, Command Injection, Path Traversal
**security-and-quality (61 queries):**
- All of security-extended PLUS:
- Code quality issues that may lead to security bugs
- Error handling problems
- Resource leaks
- Concurrency issues
**Recommendation:** Use `security-and-quality` (CI default) for comprehensive coverage
### JavaScript/TypeScript Queries
**security-extended (106 queries):**
- Focus: Web security vulnerabilities
- Covers: XSS, Prototype Pollution, CORS misconfig, Cookie security
**security-and-quality (204 queries):**
- All of security-extended PLUS:
- React/Angular/Vue specific patterns
- Async/await error handling
- Type confusion bugs
- DOM manipulation issues
**Recommendation:** Use `security-and-quality` (CI default) for comprehensive coverage
---
## Appendix B: Example SARIF Output
```json
{
"version": "2.1.0",
"$schema": "https://json.schemastore.org/sarif-2.1.0.json",
"runs": [
{
"tool": {
"driver": {
"name": "CodeQL",
"version": "2.16.0"
}
},
"results": [
{
"ruleId": "go/ssrf",
"level": "error",
"message": {
"text": "Untrusted URL in HTTP request"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "backend/internal/api/handlers/url_validator.go"
},
"region": {
"startLine": 45,
"startColumn": 10
}
}
}
]
}
]
}
]
}
```
---
**END OF PLAN**
**Status:** Ready for implementation
**Next Steps:** Begin Phase 1 implementation - update `.vscode/tasks.json`
**Owner:** Development Team
**Approval Required:** Tech Lead review of CI changes (Phase 3)