Skill: k2-l/codeql-skill (version 1.0.0)

--- name: codeql description: > CodeQL security audit pipeline: static scanning, SARIF triage, and QL query optimization. Trigger on: CodeQL, .ql, .sarif, taint tracking, source→sink, LGTM, GitHub Code Scanning, "scan this repo", "analyze this vulnerability", "optimize this query". --- # CodeQL Security Audit Skill Three independent modes — identify which one the user needs and run the corresponding script. | User Intent | Mode | Script | |-------------|------|--------| | Scan a repo / create a DB / generate SARIF | **[SCAN]** | `scripts/scan.sh` | | Read SARIF / triage vulns / generate report | **[AUDIT]** | `scripts/audit.py` | | Optimize or debug a .ql query file | **[TUNE]** | `scripts/tune.py` | --- ## [SCAN] ```bash bash scripts/scan.sh <repo_path> [language] [output.sarif] # language: java | javascript | python | cpp | auto (default) ``` The script handles: language detection → build command selection → CodeQL DB creation → security suite scan → SARIF output. For writing **custom queries**, refer to the relevant language reference: `references/lang-java.md` / `lang-javascript.md` / `lang-python.md` / `lang-cpp.md` --- ## [AUDIT] ```bash python3 scripts/audit.py <results.sarif> --output exp.md ``` The script handles: SARIF parsing → attack surface inventory → vuln family grouping → source→sink evidence chain extraction → exp.md output. **Claude's responsibility** (what the script cannot do): - Manually assess `[SUSPICIOUS]` entries with no data flow — determine if they are real vulnerabilities - Write POC requests based on business context - Provide concrete remediation code --- ## [TUNE] ```bash python3 scripts/tune.py <query.ql> ``` The script outputs a tuning checklist covering seven checks: coverage, false positives, performance, and metadata completeness. **Claude's responsibility** (what the script cannot do): - Rewrite source / sink / sanitizer logic based on checklist findings - Debug queries with no results or unexpected output — refer to `references/debugging.md`

path: scripts/audit.py
content: #!/usr/bin/env python3
"""
[AUDIT] CodeQL SARIF 二次分析脚本
用法: python3 audit.py <results.sarif> [--output exp.md]

流程:
  1. 解析 SARIF findings
  2. 按攻击面入口优先排序
  3. 按漏洞族分组，提取 source→sink 证据链
  4. 输出 exp.md（含 POC 模板）
"""

import json
import sys
import argparse
from pathlib import Path
from collections import defaultdict
from datetime import datetime

# ── 漏洞族映射 ───────────────────────────────────────────────────
VULN_FAMILIES = {
    "sql":            ["sql-injection", "SqlInjection", "query-injection"],
    "deserialize":    ["unsafe-deserialization", "Deserialization", "readObject"],
    "cmd-injection":  ["command-injection", "CommandInjection", "process-injection"],
    "path-traversal": ["path-traversal", "PathTraversal", "zip-slip"],
    "xxe":            ["xxe", "XxeLocal", "XxeRemote", "xml-injection"],
    "ssrf":           ["ssrf", "ServerSideRequestForgery", "request-forgery"],
    "xss":            ["xss", "Xss", "cross-site-scripting", "DomBasedXss"],
    "unauth":         ["missing-auth", "broken-auth", "authorization"],
}

# ── 攻击面优先级（入口类型关键词） ──────────────────────────────
SURFACE_PRIORITY = [
    ("HTTP 入口",   ["Servlet", "Controller", "Router", "Handler", "Endpoint", "RestController"]),
    ("RPC 入口",    ["Service", "Provider", "Consumer", "Dubbo", "Thrift", "grpc"]),
    ("文件上传",    ["upload", "multipart", "FileWrite", "FileUpload"]),
    ("认证边界",    ["login", "auth", "token", "session", "password", "credential"]),
    ("内部调度",    ["scheduler", "job", "task", "consumer", "listener", "cron"]),
    ("其他",        []),
]

SEVERITY_ORDER = {"error": 0, "warning": 1, "note": 2, "none": 3}


def parse_sarif(sarif_path: str) -> list[dict]:
    """解析 SARIF 文件，提取结构化 findings"""
    with open(sarif_path) as f:
        data = json.load(f)

    findings = []
    for run in data.get("runs", []):
        rules = {r["id"]: r for r in run.get("tool", {}).get("driver", {}).get("rules", [])}

        for result in run.get("results", []):
            rule_id = result.get("ruleId", "unknown")
            rule    = rules.get(rule_id, {})

            # 提取位置
            locs = result.get("locations", [])
            primary_loc = ""
            if locs:
                pl = locs[0].get("physicalLocation", {})
                uri  = pl.get("artifactLocation", {}).get("uri", "")
                line = pl.get("region", {}).get("startLine", "?")
                primary_loc = f"{uri}:{line}"

            # 提取数据流路径（source→sink 证据链）
            flow_steps = []
            for cf in result.get("codeFlows", []):
                for tf in cf.get("threadFlows", []):
                    for loc in tf.get("locations", []):
                        pl = loc.get("location", {}).get("physicalLocation", {})
                        uri  = pl.get("artifactLocation", {}).get("uri", "")
                        line = pl.get("region", {}).get("startLine", "?")
                        msg  = loc.get("location", {}).get("message", {}).get("text", "")
                        flow_steps.append(f"{uri}:{line}  ← {msg}")

            findings.append({
                "rule_id":   rule_id,
                "severity":  result.get("level", "warning"),
                "message":   result.get("message", {}).get("text", ""),
                "location":  primary_loc,
                "flow":      flow_steps,
                "rule_name": rule.get("name", rule_id),
                "rule_desc": rule.get("shortDescription", {}).get("text", ""),
            })

    return findings


def classify_surface(location: str) -> str:
    for label, keywords in SURFACE_PRIORITY:
        if any(kw.lower() in location.lower() for kw in keywords):
            return label
    return "其他"


def classify_family(rule_id: str) -> str:
    for family, keywords in VULN_FAMILIES.items():
        if any(kw.lower() in rule_id.lower() for kw in keywords):
            return family
    return "other"


def render_exp_md(findings: list[dict], sarif_path: str) -> str:
    """生成 exp.md 报告"""
    lines = []
    lines.append(f"# 安全审计报告\n")
    lines.append(f"- **扫描来源**: `{sarif_path}`")
    lines.append(f"- **生成时间**: {datetime.now().strftime('%Y-%m-%d %H:%M')}")
    lines.append(f"- **发现数量**: {len(findings)} 个\n")
    lines.append("---\n")

    # ── 攻击面清单（Rule 1）──────────────────────────────────────
    lines.append("## 攻击面清单\n")
    surface_groups = defaultdict(list)
    for f in findings:
        surface_groups[classify_surface(f["location"])].append(f)

    for label, _ in SURFACE_PRIORITY:
        count = len(surface_groups.get(label, []))
        if count:
            lines.append(f"- **{label}**: {count} 个 finding")
    lines.append("")

    # ── 按漏洞族分组输出（Rule 2）────────────────────────────────
    family_groups = defaultdict(list)
    for f in findings:
        family_groups[classify_family(f["rule_id"])].append(f)

    # 按严重程度排序
    sorted_findings = sorted(findings, key=lambda x: SEVERITY_ORDER.get(x["severity"], 9))

    vuln_idx = 1
    for family, items in sorted(family_groups.items()):
        lines.append(f"## 漏洞族: {family.upper()}\n")

        for f in sorted(items, key=lambda x: SEVERITY_ORDER.get(x["severity"], 9)):
            severity_label = f["severity"].upper()
            lines.append(f"### [VULN-{vuln_idx:03d}] {f['rule_name']} — `{f['location']}`\n")
            lines.append(f"**严重程度**: {severity_label}  ")
            lines.append(f"**规则**: `{f['rule_id']}`  ")
            lines.append(f"**描述**: {f['rule_desc'] or f['message']}\n")

            # Rule 3: source→sink 证据链
            lines.append("#### 证据链\n")
            if f["flow"]:
                lines.append("```")
                for i, step in enumerate(f["flow"]):
                    prefix = "SOURCE" if i == 0 else ("SINK  " if i == len(f["flow"]) - 1 else f"FLOW  ")
                    lines.append(f"{prefix}  {step}")
                lines.append("```\n")
            else:
                lines.append("> ⚠️ `[SUSPICIOUS - 待验证]` SARIF 中无完整数据流，需人工确认 source→sink 路径\n")

            # Rule 4: POC 模板
            lines.append("#### POC (待填写)\n")
            lines.append("```http")
            lines.append("# TODO: 根据证据链填写实际请求")
            lines.append("GET /your-endpoint?param=<payload> HTTP/1.1")
            lines.append("Host: target.example.com")
            lines.append("```\n")

            lines.append("#### 修复方案\n")
            lines.append("> TODO: 研发根据漏洞类型补充修复代码\n")
            lines.append("---\n")
            vuln_idx += 1

    return "\n".join(lines)


def main():
    parser = argparse.ArgumentParser(description="CodeQL SARIF 二次分析")
    parser.add_argument("sarif", help="SARIF 文件路径")
    parser.add_argument("--output", "-o", default="exp.md", help="输出报告路径")
    args = parser.parse_args()

    print(f"📖 解析 SARIF: {args.sarif}")
    findings = parse_sarif(args.sarif)
    print(f"✔ 发现 {len(findings)} 个 finding")

    print("⏳ 按攻击面 + 漏洞族分析...")
    report = render_exp_md(findings, args.sarif)

    Path(args.output).write_text(report, encoding="utf-8")
    print(f"✅ 报告生成完成 → {args.output}")
    print(f"   下一步: 人工补全 POC 和修复方案")


if __name__ == "__main__":
    main()


path: scripts/scan.sh
content: #!/usr/bin/env bash
# [SCAN] CodeQL 静态扫描脚本
# 用法: ./scan.sh <repo_path> [language] [output_sarif]
# language 可选: java | javascript | python | cpp | auto (默认 auto)
# 示例: ./scan.sh ./my-project java results.sarif

set -euo pipefail

REPO="${1:-.}"
LANG="${2:-auto}"
OUTPUT="${3:-results.sarif}"
DB_PATH="./codeql-db"

# ── 1. 自动检测语言 ──────────────────────────────────────────────
detect_language() {
  local counts=()
  [[ $(find "$REPO" -name "*.java" -o -name "*.kt" 2>/dev/null | wc -l) -gt 0 ]] && counts+=("java")
  [[ $(find "$REPO" -name "*.js" -o -name "*.ts" 2>/dev/null | wc -l) -gt 0 ]]   && counts+=("javascript")
  [[ $(find "$REPO" -name "*.py" 2>/dev/null | wc -l) -gt 0 ]]                    && counts+=("python")
  [[ $(find "$REPO" -name "*.c" -o -name "*.cpp" 2>/dev/null | wc -l) -gt 0 ]]   && counts+=("cpp")

  if [[ ${#counts[@]} -eq 0 ]]; then
    echo "ERROR: 未检测到支持的语言" >&2; exit 1
  fi

  # 多语言时用逗号拼接
  IFS=','; echo "${counts[*]}"
}

if [[ "$LANG" == "auto" ]]; then
  LANG=$(detect_language)
  echo "✔ 检测到语言: $LANG"
fi

# ── 2. 选择构建命令 ──────────────────────────────────────────────
build_cmd=""
case "$LANG" in
  java)
    if [[ -f "$REPO/pom.xml" ]]; then
      build_cmd="mvn clean install -DskipTests -f $REPO/pom.xml"
    elif [[ -f "$REPO/build.gradle" ]]; then
      build_cmd="gradle build -x test -p $REPO"
    fi
    ;;
  cpp)
    [[ -f "$REPO/Makefile" ]] && build_cmd="make -C $REPO"
    ;;
  javascript|python)
    build_cmd=""  # 无需构建
    ;;
esac

# ── 3. 创建 CodeQL 数据库 ────────────────────────────────────────
echo "⏳ 创建 CodeQL 数据库 (语言: $LANG)..."
codeql_args=(database create "$DB_PATH"
  --language="$LANG"
  --source-root="$REPO"
  --overwrite
)
[[ -n "$build_cmd" ]] && codeql_args+=(--command="$build_cmd")

codeql "${codeql_args[@]}"
echo "✔ 数据库创建完成: $DB_PATH"

# ── 4. 选择查询套件 ──────────────────────────────────────────────
declare -A SUITES=(
  [java]="codeql/java-queries:codeql-suites/java-security-extended.qls"
  [javascript]="codeql/javascript-queries:codeql-suites/javascript-security-extended.qls"
  [python]="codeql/python-queries:codeql-suites/python-security-extended.qls"
  [cpp]="codeql/cpp-queries:codeql-suites/cpp-security-extended.qls"
)

# 多语言时取第一个（分析主语言）
PRIMARY_LANG="${LANG%%,*}"
SUITE="${SUITES[$PRIMARY_LANG]}"

# ── 5. 运行分析 → SARIF ──────────────────────────────────────────
echo "⏳ 运行安全查询套件..."
codeql database analyze "$DB_PATH" \
  --format=sarifv2.1.0 \
  --output="$OUTPUT" \
  "$SUITE"

echo "✅ 扫描完成 → $OUTPUT"
echo "   下一步: python3 scripts/audit.py $OUTPUT"


path: references/debugging.md
content: # CodeQL Debugging Guide

## Common Compilation Errors

### 1. "Type mismatch"
```
ERROR: Expected type X, but got Y
```
**Fix**: Check that you're not mixing `Expr` with `DataFlow::Node`. Convert with:
- `DataFlow::exprNode(expr)` → `DataFlow::Node`
- `node.asExpr()` → `Expr`

### 2. "No such predicate"
```
ERROR: No such predicate: Foo::bar/1
```
**Fix**: Check your import. For example, `RemoteFlowSource` requires:
- Java: `import semmle.code.java.dataflow.FlowSources`
- Python: `import semmle.python.dataflow.new.RemoteFlowSources`
- JS: automatically available with `import javascript`

### 3. "Ambiguous name"
```
ERROR: Ambiguous type name: Configuration
```
**Fix**: Qualify the name:
```ql
class MyConf extends TaintTracking::Configuration { ... }
// NOT: class MyConf extends Configuration { ... }
```

### 4. "Predicate body has no result"
```
WARNING: Predicate always returns empty result
```
**Fix**: Your `isSource` or `isSink` predicate is never satisfied. See "No Results" below.

---

## No Results

When a taint tracking query returns nothing:

### Step 1 — Check sources independently
```ql
// Temporarily: does this find ANY sources?
from DataFlow::Node source
where source instanceof RemoteFlowSource
select source, "Found source"
```
If this returns nothing → your source definition is wrong.

### Step 2 — Check sinks independently  
```ql
// Temporarily: does this find ANY sinks?
from DataFlow::Node sink
where /* your isSink condition */ ...
select sink, "Found sink"
```
If this returns nothing → your sink definition is wrong.

### Step 3 — Check if flow exists between simple pairs
Use `DataFlow::localFlow` first (easier than full taint tracking):
```ql
from DataFlow::Node a, DataFlow::Node b
where DataFlow::localFlow(a, b)
  and /* your source condition on a */
  and /* your sink condition on b */
select a, b
```

### Step 4 — Add partial flow debugging
```ql
// Find what nodes ARE reachable from your source
from MyConfig cfg, DataFlow::Node source, DataFlow::Node reached
where 
  cfg.isSource(source) and
  cfg.hasFlow(source, reached)
select reached, "Reachable from source"
```

---

## Too Many Results (False Positives)

### Add sanitizer conditions
```ql
override predicate isSanitizer(DataFlow::Node node) {
  // Data has been validated/escaped
  exists(MethodAccess ma |
    ma.getMethod().getName() in ["escapeHtml", "sanitize", "encode"] and
    node.asExpr() = ma
  )
}
```

### Restrict source scope
```ql
// Too broad:
override predicate isSource(DataFlow::Node source) {
  source instanceof RemoteFlowSource
}

// More specific (only certain endpoints):
override predicate isSource(DataFlow::Node source) {
  source instanceof RemoteFlowSource and
  exists(Method m | 
    source.getEnclosingCallable() = m and
    m.hasAnnotation("org.springframework.web.bind.annotation", "PostMapping")
  )
}
```

### Filter by location or class
```ql
// Exclude test files
where not source.getFile().getRelativePath().matches("%test%")
```

---

## Query Performance Issues

If a query is timing out or very slow:

1. **Avoid `any()`** in hot paths — it forces full enumeration
2. **Use `exists()` instead of joins** for existence checks
3. **Bind early**: put the most selective predicates first in `where`
4. **Use `pragma[inline]`** for predicates called many times:
   ```ql
   pragma[inline]
   predicate isInteresting(Expr e) { ... }
   ```
5. **Avoid `+` and `*` closure on large relations** (very expensive)

---

## Checking CodeQL Version Compatibility

Some APIs differ between CodeQL versions:

| Feature | Old API | New API (preferred) |
|---------|---------|---------------------|
| Python taint | `semmle.python.security.*` | `semmle.python.dataflow.new.*` |
| JS taint source | `HTTP::RequestInputAccess` | `RemoteFlowSource` |
| C++ dataflow | `DataFlow::Configuration` | `DataFlow::Configuration` (same) |

Always check: https://codeql.github.com/docs/codeql-language-guides/

---

## Useful Debug Predicates

```ql
// Print location of any node
select node, node.getLocation().toString()

// Count results per file
select count(DataFlow::Node n | /* your condition */ | n), "nodes found"

// Find what type something actually is
select e, e.getType().toString(), e.getType().getQualifiedName()
```


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)