A CodeQL query development workshop that teaches you to find client-side cross-site scripting (XSS) vulnerabilities using taint-tracking analysis. Inspired by a production SAP UI5 XSS query pattern.
This workshop is modeled after a real-world production query (UI5Xss.ql) that detects XSS vulnerabilities in SAP UI5 applications. The production query uses the DataFlow::ConfigSig pattern with:
- Sources:
RemoteFlowSourceand DOM-based XSS sources - Sinks: HTML injection sinks (
innerHTML,document.write, etc.) - Barriers: Sanitizer functions (
encodeHTML,encodeJS, etc.) - Path-problem: Full taint path visualization
Since the production query depends on custom SAP libraries, this workshop uses only codeql/javascript-all (version 2.6.19) to teach the same core concepts with standard JavaScript APIs.
- CodeQL CLI v2.23.9 or later
codeql/javascript-allpack (installed automatically viacodeql pack install)
# Navigate to the workshop directory
cd .github/skills/create-codeql-query-development-workshop/examples/codeql-sap-js-ui5-xss
# Install dependencies for all packs
codeql pack install solutions
codeql pack install solutions-tests
codeql pack install exercises
codeql pack install exercises-tests
# Build test databases (optional — tests do this automatically)
chmod +x build-databases.sh
./build-databases.shexercises/ — Your workspace (incomplete queries to complete)
exercises-tests/ — Tests that validate your solutions
solutions/ — Reference solutions (don't peek!)
solutions-tests/ — Tests for the solutions
tests-common/ — Shared test source code
The test file (tests-common/test.js) contains carefully crafted cases:
| Case | Function | Source | Sink | Expected |
|---|---|---|---|---|
| POSITIVE 1 | positiveDirectInnerHTML |
document.location.search |
innerHTML |
Detected |
| POSITIVE 2 | positiveDocumentWrite |
window.location.hash |
document.write() |
Detected |
| POSITIVE 3 | positiveOuterHTML |
URL param via URLSearchParams |
outerHTML |
Detected |
| NEGATIVE 1 | negativeSanitized |
document.location.search |
innerHTML (via DOMPurify.sanitize) |
NOT detected (barrier) |
| NEGATIVE 2 | negativeHardcoded |
hardcoded string | innerHTML |
NOT detected (no source) |
| EDGE CASE | edgeCaseEval |
window.location.hash |
eval() |
Detected |
| NEGATIVE 3 | negativeEncoded |
document.location.search |
innerHTML (via encodeURIComponent) |
NOT detected (barrier) |
Each exercise builds on the previous one, progressively teaching more advanced CodeQL concepts.
Goal: Write a query that identifies dangerous DOM operations that could introduce XSS.
Concepts: DataFlow::Node, DataFlow::PropWrite, DataFlow::CallNode, DataFlow::globalVarRef
What you'll find:
- Property writes to
innerHTMLandouterHTML - Calls to
document.write() - Calls to
eval()
Validate:
codeql test run exercises-tests/Exercise1Goal: Identify all user-controlled inputs that could be XSS sources.
Concepts: RemoteFlowSource, getSourceType()
What you'll find:
document.location.search,window.location.hash- URL parameters via
URLSearchParams - Any other browser API that returns user-controlled data
Validate:
codeql test run exercises-tests/Exercise2Goal: Connect sources to sinks using CodeQL's taint-tracking framework.
Concepts: DataFlow::ConfigSig, TaintTracking::Global, isSource, isSink, flow()
Key pattern:
module MyConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node source) { ... }
predicate isSink(DataFlow::Node sink) { ... }
}
module MyFlow = TaintTracking::Global<MyConfig>;This exercise will find ALL XSS flows including the sanitized ones (no barriers yet).
Validate:
codeql test run exercises-tests/Exercise3Goal: Reduce false positives by adding barriers for sanitizer functions.
Concepts: isBarrier predicate, DataFlow::CallNode, getCalleeName()
What changes: Flows through DOMPurify.sanitize() and encodeURIComponent() are blocked.
Validate:
codeql test run exercises-tests/Exercise4Goal: Convert the query to a production-style path-problem query with full taint paths.
Concepts: @kind path-problem, PathGraph, PathNode, flowPath()
What changes:
- Query metadata includes
@kind path-problem,@security-severity, CWE tags - Uses
PathNodeinstead ofNodefor source/sink - Imports
PathGraphfor visualization - Shows complete taint propagation paths in results
Validate:
codeql test run exercises-tests/Exercise5# Run all solution tests (should pass after accepting results)
codeql test run solutions-tests
# Run a specific exercise test
codeql test run solutions-tests/Exercise3
# Accept current results as expected baseline
codeql test run solutions-tests --learnExercise 1 (Sinks)
↓
Exercise 2 (Sources)
↓
Exercise 3 (Taint Tracking) ← Core concept
↓
Exercise 4 (Barriers) ← Reducing false positives
↓
Exercise 5 (Path Problem) ← Production-ready query
| Workshop Concept | Production Query (UI5Xss.ql) |
|---|---|
RemoteFlowSource |
RemoteFlowSource + SAP-specific sources |
innerHTML/document.write sinks |
SAP UI5 HTML injection sinks |
sanitize/encodeURIComponent barriers |
encodeHTML/encodeJS/encodeCSS barriers |
DataFlow::ConfigSig |
Same pattern |
path-problem |
Same query kind |
- Start with Exercise 1 and work sequentially — each builds on the previous
- Use
codeql test run --learnto accept current output as expected results - If stuck, look at the solution for the PREVIOUS exercise as a starting point
- The
tests-common/test.jsfile has comments explaining each test case