Step 3

Exercise 2: Detection Guardrails

On this page

Exercise 2: Detection Guardrails (~12 min)

Goal: Experience 3 distinct GHAS detection mechanisms operating at different points in the workflow.

📝 Open docs/detection-worksheet.md — record your observations for each detection mechanism as you complete the steps below.

Push time          PR review time         Merge time
    │                    │                     │
    ▼                    ▼                     ▼
┌──────────┐    ┌───────────────┐    ┌──────────────────┐
│   Push   │    │   CodeQL +    │    │ Required status  │
│Protection│    │  Dependency   │    │  checks block    │
│  blocks  │    │    Review     │    │  merge           │
│  secrets │    │  find issues  │    │                  │
└──────────┘    └───────────────┘    └──────────────────┘

2a. Push Protection — Blocks Secrets at Push Time

  1. Open vulnerable-samples/secret-leak.js and review the hardcoded API key
  2. Attempt to push the file:
git add vulnerable-samples/secret-leak.js
git commit -m "Add API integration module"
git push origin workshop/secure-by-design
  1. Observe: The push is BLOCKED by push protection
remote: error: GH013: Repository rule violations found for refs/heads/workshop/secure-by-design.
remote: 
remote: - GITHUB PUSH PROTECTION
remote:   —————————————————————————————————————————
remote:     Resolve the following violations before pushing again
remote: 
remote:     — Push cannot contain secrets
  1. Note: This happened BEFORE the code entered the repository. The secret never reached GitHub.

To proceed with the remaining exercises, remove the secret from the file or use git reset to undo the commit. In a real workflow, you would rotate the credential immediately.


2b. Code Scanning — CodeQL Finds Insecure Patterns in PR

  1. Push vulnerable-samples/sql-injection.js to the feature branch (this push will succeed — no secrets):
git add vulnerable-samples/sql-injection.js
git commit -m "Add database query module"
git push origin workshop/secure-by-design
  1. Open a Pull Request from workshop/secure-by-designmain
  2. Wait for the CodeQL analysis to complete (1–3 minutes)
  3. Observe: CodeQL findings appear on the PR:

    • Alert: SQL injection vulnerability
    • Severity: High / Error
    • Location: sql-injection.js, line with string concatenation in SQL query
  4. Trace the Data Flow Path — Click the CodeQL alert → click “Show paths”:

    SOURCE:  req.query.username           ← Untrusted user input from HTTP request
      ↓
    FLOW:    const query = "..." + username + "..."  ← Tainted data concatenated into SQL string
      ↓
    SINK:    pool.query(query)            ← SQL execution with tainted data
    

    CodeQL didn’t just find “string concatenation near SQL” — it tracked the data flow from the HTTP request parameter all the way to the database query execution. This is semantic analysis, not pattern matching.

  5. Why did CodeQL catch ALL 3 patterns? Our sql-injection.js contains 3 different SQL injection techniques:

    # Pattern Code Grep Catches? CodeQL Catches?
    1 String concatenation "..." + username + "..." ✅ (regex for +)
    2 Template literal `...${category}...` ❌ (different syntax)
    3 DELETE with param "DELETE ... " + userId ⚠️ (maybe)

    A simple grep or regex scanner would miss pattern 2 (template literals use ${}, not +). CodeQL catches ALL 3 because it uses semantic data flow analysis — it understands that req.query.* and req.params.* are sources of untrusted input, regardless of the string construction method.

  6. Note: These vulnerabilities were caught AFTER push but BEFORE merge. The vulnerable code is in the repository but cannot reach main because the required status check (CodeQL) blocks the merge.

[!NOTE] CodeQL Query Suites and Threat Coverage

Suite Coverage Threat Rows False Positives
Default High-precision security queries Rows 1-2 Few
Extended Default + lower-confidence security Rows 1-2 + edge cases Moderate
Security-and-Quality Extended + code quality/maintainability Rows 1-2, 8, 9 More

For this workshop, we use the Default suite. For regulated production environments, consider Extended to broaden threat coverage.


2c. Dependency Review — Flags Vulnerable Dependencies in PR

  1. Push vulnerable-samples/package.json to the feature branch:
git add vulnerable-samples/package.json
git commit -m "Add project dependencies"
git push origin workshop/secure-by-design
  1. Open (or update) the Pull Request
  2. Observe: The dependency review surfaces:
    • Vulnerable package: lodash@3.10.1
    • CVEs: Multiple known vulnerabilities (e.g., Prototype Pollution)
    • Fix version: Upgrade to lodash@4.17.21
  3. Note: This was caught in the PR review, with CVE details and the specific fix version.

Update the Threat Model

After completing all three detection scenarios, update THREAT-MODEL.md — fill in the first 3 rows of the attack vectors table:

# Attack Vector Current Control Gap?
1 Secret leaked in commit ✅ Push protection Covered
2 Insecure code pattern (e.g., SQL injection) ✅ CodeQL code scanning Covered
3 Vulnerable dependency in supply chain ✅ Dependency review Covered

Key Insight

Push protection stops secrets BEFORE they enter the repository. Code scanning and dependency review stop vulnerabilities BEFORE they reach main. Each operates at a different point in the workflow — together, they form a layered defense.

[!TIP] Run scripts/verify-exercise2.sh to validate your Exercise 2 completion.

to navigate between steps