Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .codeql-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v2.24.0
v2.24.1
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
applyTo: '**/*.prompt.md'
applyTo: '.github/prompts/*.prompt.md'
description: 'Instructions for managing .prompt.md files throughout the advanced-security/codeql-development-mcp-server repository.'
---

# Instructions for managing `*.prompt.md` files
# Instructions for managing `.github/prompts/*.prompt.md` files

## PURPOSE

Expand All @@ -27,13 +27,11 @@ That higher-level component should link (i.e. point) to at least one `.github/pr
- ALWAYS follow best practices for writing markdown files, including proper use of headings, lists, links, and code blocks. This explicitly includes inserting a newline before and after code blocks, lists, and headings.
- ALWAYS check formatting with `npm run lint && npm run format:check` from the repo root directory to ensure consistent formatting after making changes.
- ALWAYS fix linting and formatting errors by running `npm run lint:fix && npm run format` from the repo root directory before committing changes.
- ALWAYS start each `*.prompt.md` file with the following YAML block of frontmatter:

```yaml
---
mode: agent
---
```
- ALWAYS start each `*.prompt.md` file with a YAML front-matter block containing, at minimum, values for fields such as:
- `agent` -> pointing to the name of the agent this prompt is intended for (e.g. `agent: ql-mcp-tool-tester`)
- `name` -> a unique name for the prompt (e.g. `name: validate-ql-mcp-tools-via-workshop`)
- `description` -> a concise description of the prompt's purpose and functionality
- `argument-hint` -> a brief hint about the expected arguments for the prompt

## PREFERENCES

Expand Down
44 changes: 44 additions & 0 deletions .github/instructions/server_src_prompts_md.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
applyTo: 'server/src/prompts/*.prompt.md'
description: 'Instructions for MCP-server-hosted workflow prompt files.'
---

# Copilot Instructions for `server/src/prompts/*.prompt.md` prompt files

## PURPOSE

This file contains instructions for working with workflow prompt files in the `server/src/prompts/` directory. These prompts are registered by the MCP server (via `workflow-prompts.ts`) and exposed as slash commands in VS Code Copilot Chat.

## REQUIREMENTS

- ALWAYS start each prompt file with the following YAML frontmatter block:

```yaml
---
agent: agent
---
```

Note: VS Code has deprecated `mode: agent` in favor of `agent: agent`.

- ALWAYS ensure each prompt has a corresponding registration in `server/src/prompts/workflow-prompts.ts`, including a Zod parameter schema and a `server.prompt()` call.
- ALWAYS ensure the prompt name in `WORKFLOW_PROMPT_NAMES` matches the registration.
- ALWAYS write prompts that work in **any environment** where `codeql` and the MCP server tools are available, including terminal-only environments without an IDE.
- ALWAYS use explicit, numeric tool parameters (e.g., `file_path`, `line`, `character`) instead of IDE-centric language like "position the cursor" or "click on".
- ALWAYS document when MCP tools use **0-based** positions (all `codeql_lsp_*` tools) versus **1-based** positions (`find_predicate_position`, `find_class_position`, `read_file`).
- ALWAYS note the `workspace_uri` requirement for LSP tools: it must be a **plain directory path** to the pack root containing `codeql-pack.yml`, not a `file://` URI.
- **ALWAYS run `npm run tidy` from the repo root directory to apply (markdown) linting for all prompt files.**

## PREFERENCES

- PREFER referencing other prompts by their registered MCP name (e.g., `codeql://prompts/ql_lsp_iterative_development`) to enable cross-prompt navigation.
- PREFER including a "Worked Example" section showing concrete tool invocations with realistic parameter values.
- PREFER a validation tools comparison table when multiple tools can validate queries at different fidelity levels (e.g., `validate_codeql_query` vs `codeql_lsp_diagnostics` vs `codeql_query_compile`).
- PREFER documenting tool limitations discovered through actual usage (e.g., `codeql_lsp_diagnostics` cannot resolve imports; `find_class_position` finds `class` only, not `module`).

## CONSTRAINTS

- NEVER leave any trailing whitespace on any line.
- NEVER use four backticks to nest one code block inside another.
- NEVER assume the calling LLM has access to an IDE, cursor, or editor UI.
- NEVER reference `file://` URIs for the `workspace_uri` parameter — use plain directory paths.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ agent: mcp-enabled-ql-workshop-developer
name: validate-ql-mcp-server-tools-via-workshop
description: 'A prompt for validating the real-world functionality of the CodeQL Development MCP Server tools by creating a CodeQL query development workshop from scratch using an existing, production-grade CodeQL query as the workshop "solution".'
argument-hint: 'Provide the absolute or relative path to a local ".ql" or ".qlref" file associated with a production-grade CodeQL query to be used as the "solution" for the last stage of the to-be-created workshop.'
model: Claude Opus 4.5 (copilot)
model: Claude Opus 4.6 (copilot)
---

# `validate-ql-mcp-server-tools-via-workshop` Prompt
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
# JavaScript XSS Taint-Tracking Workshop

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.

## Background

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**: `RemoteFlowSource` and 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.

## Prerequisites

- CodeQL CLI v2.23.9 or later
- `codeql/javascript-all` pack (installed automatically via `codeql pack install`)

## Setup

```bash
# 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.sh
```

## Workshop Structure

```text
exercises/ — 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
```

## Test Code Overview

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) |

## Exercises

Each exercise builds on the previous one, progressively teaching more advanced CodeQL concepts.

### Exercise 1: Find XSS Sinks

**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 `innerHTML` and `outerHTML`
- Calls to `document.write()`
- Calls to `eval()`

**Validate**:

```bash
codeql test run exercises-tests/Exercise1
```

### Exercise 2: Find Remote Flow Sources

**Goal**: 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**:

```bash
codeql test run exercises-tests/Exercise2
```

### Exercise 3: Basic Taint-Tracking Configuration

**Goal**: Connect sources to sinks using CodeQL's taint-tracking framework.

**Concepts**: `DataFlow::ConfigSig`, `TaintTracking::Global`, `isSource`, `isSink`, `flow()`

**Key pattern**:

```ql
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**:

```bash
codeql test run exercises-tests/Exercise3
```

### Exercise 4: Add Sanitizer Barriers

**Goal**: 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**:

```bash
codeql test run exercises-tests/Exercise4
```

### Exercise 5: Full Path-Problem Query

**Goal**: 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 `PathNode` instead of `Node` for source/sink
- Imports `PathGraph` for visualization
- Shows complete taint propagation paths in results

**Validate**:

```bash
codeql test run exercises-tests/Exercise5
```

## Validating Solutions

```bash
# 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 --learn
```

## Learning Path

```
Exercise 1 (Sinks)
Exercise 2 (Sources)
Exercise 3 (Taint Tracking) ← Core concept
Exercise 4 (Barriers) ← Reducing false positives
Exercise 5 (Path Problem) ← Production-ready query
```

## Relation to Production 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 |

## Tips

- Start with Exercise 1 and work sequentially — each builds on the previous
- Use `codeql test run --learn` to accept current output as expected results
- If stuck, look at the solution for the PREVIOUS exercise as a starting point
- The `tests-common/test.js` file has comments explaining each test case
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/bin/bash
set -e

WORKSHOP_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

echo "Building test databases for SAP UI5 XSS workshop..."

# Build solution test databases
for exercise_dir in "${WORKSHOP_ROOT}"/solutions-tests/Exercise*/; do
exercise_name="$(basename "$exercise_dir")"
DB_PATH="${exercise_dir}/${exercise_name}.testproj"

echo " Creating database: ${exercise_name} (solutions)"
rm -rf "${DB_PATH}"

codeql test extract --search-path="${WORKSHOP_ROOT}/solutions" "$exercise_dir"
done

# Build exercise test databases
for exercise_dir in "${WORKSHOP_ROOT}"/exercises-tests/Exercise*/; do
exercise_name="$(basename "$exercise_dir")"
DB_PATH="${exercise_dir}/${exercise_name}.testproj"

echo " Creating database: ${exercise_name} (exercises)"
rm -rf "${DB_PATH}"

codeql test extract --search-path="${WORKSHOP_ROOT}/exercises" "$exercise_dir"
done

echo "Database creation complete!"
echo ""
echo "To run tests:"
echo " codeql test run solutions-tests # Validate solutions"
echo " codeql test run exercises-tests # Test your exercises"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
provide:
- '*/codeql-pack.yml'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Exercise1.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Test cases for XSS taint-tracking workshop
// Inspired by SAP UI5 XSS production query patterns

// ============================================================
// POSITIVE CASE 1: Direct XSS via document.location.search → innerHTML
// ============================================================
function positiveDirectInnerHTML() {
var userInput = document.location.search; // source: URL query string
var container = document.getElementById("output");
container.innerHTML = userInput; // sink: innerHTML assignment — XSS!
}

// ============================================================
// POSITIVE CASE 2: XSS via window.location.hash → document.write()
// ============================================================
function positiveDocumentWrite() {
var hashValue = window.location.hash; // source: URL hash fragment
document.write(hashValue); // sink: document.write — XSS!
}

// ============================================================
// POSITIVE CASE 3: XSS via URL param parsed → outerHTML
// ============================================================
function positiveOuterHTML() {
var params = new URLSearchParams(window.location.search);
var name = params.get("name"); // source: user-controlled URL parameter
var el = document.getElementById("profile");
el.outerHTML = "<div>" + name + "</div>"; // sink: outerHTML assignment — XSS!
}

// ============================================================
// NEGATIVE CASE 1: Sanitized value via DOMPurify.sanitize() → innerHTML
// Should NOT be flagged because the input is sanitized
// ============================================================
function negativeSanitized() {
var userInput = document.location.search; // source
var clean = DOMPurify.sanitize(userInput); // barrier: sanitization
var container = document.getElementById("safe-output");
container.innerHTML = clean; // safe: sanitized input
}

// ============================================================
// NEGATIVE CASE 2: Hardcoded string → innerHTML
// Should NOT be flagged because there is no user-controlled source
// ============================================================
function negativeHardcoded() {
var container = document.getElementById("static-content");
container.innerHTML = "<p>Welcome to the application</p>"; // safe: hardcoded
}

// ============================================================
// EDGE CASE: XSS via eval() with URL input
// ============================================================
function edgeCaseEval() {
var code = window.location.hash.substring(1); // source: URL hash
eval(code); // sink: eval — code injection / XSS!
}

// ============================================================
// NEGATIVE CASE 3: encodeURIComponent barrier
// Should NOT be flagged because input is encoded
// ============================================================
function negativeEncoded() {
var userInput = document.location.search;
var encoded = encodeURIComponent(userInput); // barrier: encoding
var container = document.getElementById("encoded-output");
container.innerHTML = encoded; // safe: encoded input
}
Loading
Loading