Compare commits

..

4 Commits

Author SHA1 Message Date
Harry Maclean
150a0e569f Ruby: Add basic modeling for ViewComponent 2024-01-17 10:51:32 +00:00
Harry Maclean
6afeb1d2f9 Update expected file 2024-01-16 11:39:43 +00:00
Harry Maclean
173e1def4f Add WIP query for erb flow 2024-01-16 11:36:39 +00:00
Harry Maclean
2af00a0be9 Add test for erb flow 2024-01-04 12:31:13 +00:00
20230 changed files with 683965 additions and 1533221 deletions

View File

@@ -1,35 +1,9 @@
common --enable_platform_specific_config
# because we use --override_module with `%workspace%`, the lock file is not stable
common --lockfile_mode=off
# when building from this repository in isolation, the internal repository will not be found at ..
# where `MODULE.bazel` looks for it. The following will get us past the module loading phase, so
# that we can build things that do not rely on that
common --override_module=semmle_code=%workspace%/misc/bazel/semmle_code_stub
build --repo_env=CC=clang --repo_env=CXX=clang++
# we use transitions that break builds of `...`, so for `test` to work with that we need the following
test --build_tests_only
# this requires developer mode, but is required to have pack installer functioning
startup --windows_enable_symlinks
common --enable_runfiles
# with the above, we can avoid building python zips which is the default on windows as that's expensive
build --nobuild_python_zip
common --registry=file:///%workspace%/misc/bazel/registry
common --registry=https://bcr.bazel.build
common --@rules_dotnet//dotnet/settings:strict_deps=false
# Reduce this eventually to empty, once we've fixed all our usages of java, and https://github.com/bazel-contrib/rules_go/issues/4193 is fixed
common --incompatible_autoload_externally="+@rules_java,+@rules_shell"
build --java_language_version=17
build --tool_java_language_version=17
build --tool_java_runtime_version=remotejdk_17
build --java_runtime_version=remotejdk_17
build:linux --cxxopt=-std=c++20
build:macos --cxxopt=-std=c++20 --cpu=darwin_x86_64
build:windows --cxxopt=/std:c++20 --cxxopt=/Zc:preprocessor
try-import %workspace%/local.bazelrc

View File

@@ -1,10 +0,0 @@
# this file should contain bazel settings required to build things from `semmle-code`
common --registry=file:///%workspace%/ql/misc/bazel/registry
common --registry=https://bcr.bazel.build
# See bazelbuild/rules_dotnet#413: strict_deps in C# also appliy to 3rd-party deps, and when we pull
# in (for example) the xunit package, there's no code in this at all, it just depends transitively on
# its implementation packages without providing any code itself.
# We either can depend on internal implementation details, or turn of strict deps.
common --@rules_dotnet//dotnet/settings:strict_deps=false

View File

@@ -1 +1 @@
8.0.0
6.3.1

View File

@@ -1 +0,0 @@
DisableFormat: true

View File

@@ -1,5 +1,4 @@
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
"extensions": [
"rust-lang.rust-analyzer",
"bungcip.better-toml",

14
.devcontainer/swift/root.sh Executable file → Normal file
View File

@@ -3,16 +3,6 @@ set -xe
BAZELISK_VERSION=v1.12.0
BAZELISK_DOWNLOAD_SHA=6b0bcb2ea15bca16fffabe6fda75803440375354c085480fe361d2cbf32501db
# install git lfs apt source
curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | bash
# install gh apt source
(type -p wget >/dev/null || (sudo apt update && sudo apt-get install wget -y)) \
&& sudo mkdir -p -m 755 /etc/apt/keyrings \
&& wget -qO- https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
&& sudo chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
apt-get update
export DEBIAN_FRONTEND=noninteractive
apt-get -y install --no-install-recommends \
@@ -20,9 +10,7 @@ apt-get -y install --no-install-recommends \
uuid-dev \
python3-distutils \
python3-pip \
bash-completion \
git-lfs \
gh
bash-completion
# Install Bazel
curl -fSsL -o /usr/local/bin/bazelisk https://github.com/bazelbuild/bazelisk/releases/download/${BAZELISK_VERSION}/bazelisk-linux-amd64

View File

@@ -1,7 +1,5 @@
set -xe
git lfs install
# add the workspace to the codeql search path
mkdir -p /home/vscode/.config/codeql
echo "--search-path /workspaces/codeql" > /home/vscode/.config/codeql/config

39
.gitattributes vendored
View File

@@ -50,41 +50,24 @@
*.dll -text
*.pdb -text
/java/ql/test/stubs/**/*.java linguist-generated=true
/java/ql/test/experimental/stubs/**/*.java linguist-generated=true
/java/kotlin-extractor/deps/*.jar filter=lfs diff=lfs merge=lfs -text
java/ql/test/stubs/**/*.java linguist-generated=true
java/ql/test/experimental/stubs/**/*.java linguist-generated=true
# Force git not to modify line endings for go or html files under the go/ql directory
/go/ql/**/*.go -text
/go/ql/**/*.html -text
go/ql/**/*.go -text
go/ql/**/*.html -text
# Force git not to modify line endings for go dbschemes
/go/*.dbscheme -text
go/*.dbscheme -text
# Preserve unusual line ending from codeql-go merge
/go/extractor/opencsv/CSVReader.java -text
go/extractor/opencsv/CSVReader.java -text
# For some languages, upgrade script testing references really old dbscheme
# files from legacy upgrades that have CRLF line endings. Since upgrade
# resolution relies on object hashes, we must suppress line ending conversion
# for those testing dbscheme files.
/*/ql/lib/upgrades/initial/*.dbscheme -text
*/ql/lib/upgrades/initial/*.dbscheme -text
# Auto-generated modeling for Python
/python/ql/lib/semmle/python/frameworks/data/internal/subclass-capture/*.yml linguist-generated=true
# auto-generated bazel lock file
/ruby/extractor/cargo-bazel-lock.json linguist-generated=true
/ruby/extractor/cargo-bazel-lock.json -merge
# auto-generated files for the C# build
/csharp/paket.lock linguist-generated=true
# needs eol=crlf, as `paket` touches this file and saves it as crlf
/csharp/.paket/Paket.Restore.targets linguist-generated=true eol=crlf
/csharp/paket.main.bzl linguist-generated=true
/csharp/paket.main_extension.bzl linguist-generated=true
# ripunzip tool
/misc/ripunzip/ripunzip-* filter=lfs diff=lfs merge=lfs -text
# swift prebuilt resources
/swift/third_party/resources/*.zip filter=lfs diff=lfs merge=lfs -text
/swift/third_party/resources/*.tar.zst filter=lfs diff=lfs merge=lfs -text
# Generated test files - these are synced from the standard JavaScript libraries using
# `javascript/ql/experimental/adaptivethreatmodeling/test/update_endpoint_test_files.py`.
javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/autogenerated/**/*.js linguist-generated=true -merge
javascript/ql/experimental/adaptivethreatmodeling/test/endpoint_large_scale/autogenerated/**/*.ts linguist-generated=true -merge

View File

@@ -9,4 +9,3 @@ paths-ignore:
- '/python/'
- '/javascript/ql/test'
- '/javascript/extractor/tests'
- '/rust/ql'

15
.github/labeler.yml vendored
View File

@@ -15,12 +15,12 @@ Java:
- change-notes/**/*java.*
JS:
- any: [ 'javascript/**/*' ]
- any: [ 'javascript/**/*', '!javascript/ql/experimental/adaptivethreatmodeling/**/*' ]
- change-notes/**/*javascript*
Kotlin:
- java/kotlin-extractor/**/*
- java/ql/test-kotlin*/**/*
- java/ql/test/kotlin/**/*
Python:
- python/**/*
@@ -30,18 +30,10 @@ Ruby:
- ruby/**/*
- change-notes/**/*ruby*
Rust:
- rust/**/*
- change-notes/**/*rust*
Swift:
- swift/**/*
- change-notes/**/*swift*
Actions:
- actions/**/*
- change-notes/**/*actions*
documentation:
- "**/*.qhelp"
- "**/*.md"
@@ -54,3 +46,6 @@ documentation:
# Since these are all shared files that need to be synced, just pick _one_ copy of each.
"DataFlow Library":
- "shared/dataflow/**/*"
"ATM":
- javascript/ql/experimental/adaptivethreatmodeling/**/*

View File

@@ -1,14 +0,0 @@
### Pull Request checklist
#### All query authors
- [ ] A change note is added if necessary. See [the documentation](https://github.com/github/codeql/blob/main/docs/change-notes.md) in this repository.
- [ ] All new queries have appropriate `.qhelp`. See [the documentation](https://github.com/github/codeql/blob/main/docs/query-help-style-guide.md) in this repository.
- [ ] QL tests are added if necessary. See [Testing custom queries](https://docs.github.com/en/code-security/codeql-cli/using-the-advanced-functionality-of-the-codeql-cli/testing-custom-queries) in the GitHub documentation.
- [ ] New and changed queries have correct query metadata. See [the documentation](https://github.com/github/codeql/blob/main/docs/query-metadata-style-guide.md) in this repository.
#### Internal query authors only
- [ ] Autofixes generated based on these changes are valid, only needed if this PR makes significant changes to `.ql`, `.qll`, or `.qhelp` files. See [the documentation](https://github.com/github/codeql-team/blob/main/docs/best-practices/validating-autofix-for-query-changes.md) (internal access required).
- [ ] Changes are validated [at scale](https://github.com/github/codeql-dca/) (internal access required).
- [ ] Adding a new query? Consider also [adding the query to autofix](https://github.com/github/codeml-autofix/blob/main/docs/updating-query-support.md#adding-a-new-query-to-the-query-suite).

View File

@@ -1,74 +0,0 @@
name: Build runzip
on:
workflow_dispatch:
inputs:
ripunzip-version:
description: "what reference to checktout from google/runzip"
required: false
default: v1.2.1
openssl-version:
description: "what reference to checkout from openssl/openssl for Linux"
required: false
default: openssl-3.3.0
jobs:
build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-20.04, macos-13, windows-2019]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
with:
repository: google/ripunzip
ref: ${{ inputs.ripunzip-version }}
# we need to avoid ripunzip dynamically linking into libssl
# see https://github.com/sfackler/rust-openssl/issues/183
- if: runner.os == 'Linux'
name: checkout openssl
uses: actions/checkout@v4
with:
repository: openssl/openssl
path: openssl
ref: ${{ inputs.openssl-version }}
- if: runner.os == 'Linux'
name: build and install openssl with fPIC
shell: bash
working-directory: openssl
run: |
./config -fPIC --prefix=$HOME/.local --openssldir=$HOME/.local/ssl
make -j $(nproc)
make install_sw -j $(nproc)
- if: runner.os == 'Linux'
name: build (linux)
shell: bash
run: |
env OPENSSL_LIB_DIR=$HOME/.local/lib64 OPENSSL_INCLUDE_DIR=$HOME/.local/include OPENSSL_STATIC=yes cargo build --release
mv target/release/ripunzip ripunzip-linux
- if: runner.os == 'Windows'
name: build (windows)
shell: bash
run: |
cargo build --release
mv target/release/ripunzip ripunzip-windows
- name: build (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
rustup target install x86_64-apple-darwin
rustup target install aarch64-apple-darwin
cargo build --target x86_64-apple-darwin --release
cargo build --target aarch64-apple-darwin --release
lipo -create -output ripunzip-macos \
-arch x86_64 target/x86_64-apple-darwin/release/ripunzip \
-arch arm64 target/aarch64-apple-darwin/release/ripunzip
- uses: actions/upload-artifact@v4
with:
name: ripunzip-${{ runner.os }}
path: ripunzip-*
- name: Check built binary
shell: bash
run: |
./ripunzip-* --version

View File

@@ -1,28 +0,0 @@
name: Check bazel formatting
on:
pull_request:
paths:
- "**.bazel"
- "**.bzl"
branches:
- main
- "rc/*"
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Check bazel formatting
uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507
with:
extra_args: >
buildifier --all-files 2>&1 ||
(
echo -e "In order to format all bazel files, please run:\n bazel run //misc/bazel/buildifier"; exit 1
)

View File

@@ -1,8 +1,5 @@
name: Check change note
permissions:
pull-requests: read
on:
pull_request_target:
types: [labeled, unlabeled, opened, synchronize, reopened, ready_for_review]
@@ -16,12 +13,11 @@ on:
- "shared/**/*.qll"
- "!**/experimental/**"
- "!ql/**"
- "!rust/**"
- ".github/workflows/check-change-note.yml"
jobs:
check-change-note:
env:
env:
REPO: ${{ github.repository }}
PULL_REQUEST_NUMBER: ${{ github.event.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -34,7 +30,7 @@ jobs:
!contains(github.event.pull_request.labels.*.name, 'no-change-note-required')
run: |
change_note_files=$(gh api "repos/$REPO/pulls/$PULL_REQUEST_NUMBER/files" --paginate --jq '.[].filename | select(test("/change-notes/.*[.]md$"))')
if [ -z "$change_note_files" ]; then
echo "No change note found. Either add one, or add the 'no-change-note-required' label."
exit 1

View File

@@ -9,9 +9,6 @@ on:
- main
- "rc/*"
permissions:
contents: read
jobs:
check:
runs-on: ubuntu-latest

View File

@@ -10,9 +10,6 @@ on:
- main
- "rc/*"
permissions:
contents: read
jobs:
qldoc:
runs-on: ubuntu-latest
@@ -30,8 +27,7 @@ jobs:
run: |
EXIT_CODE=0
# TODO: remove the shared exception from the regex when coverage of qlpacks without dbschemes is supported
# TODO: remove the actions exception once https://github.com/github/codeql-team/issues/3656 is fixed
changed_lib_packs="$(git diff --name-only --diff-filter=ACMRT HEAD^ HEAD | { grep -Po '^(?!(shared|actions))[a-z]*/ql/lib' || true; } | sort -u)"
changed_lib_packs="$(git diff --name-only --diff-filter=ACMRT HEAD^ HEAD | { grep -Po '^(?!(shared))[a-z]*/ql/lib' || true; } | sort -u)"
for pack_dir in ${changed_lib_packs}; do
lang="${pack_dir%/ql/lib}"
codeql generate library-doc-coverage --output="${RUNNER_TEMP}/${lang}-current.txt" --dir="${pack_dir}"

View File

@@ -11,9 +11,6 @@ on:
- "rc/*"
workflow_dispatch:
permissions:
contents: read
jobs:
check:
name: Check query IDs

View File

@@ -5,9 +5,6 @@ on:
schedule:
- cron: "30 1 * * *"
permissions:
issues: write
jobs:
stale:
if: github.repository == 'github/codeql'

View File

@@ -30,7 +30,7 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.100
dotnet-version: 8.0.100
- name: Checkout repository
uses: actions/checkout@v4
@@ -56,9 +56,7 @@ jobs:
# uses a compiled language
- run: |
cd csharp
dotnet tool restore
dotnet build .
dotnet build csharp
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@main

View File

@@ -7,18 +7,9 @@ on:
- "rc/*"
- "codeql-cli-*"
pull_request:
paths:
- '**.ql'
- '**.qll'
- '**/qlpack.yml'
- '**.dbscheme'
permissions:
contents: read
jobs:
compile-queries:
if: github.repository_owner == 'github'
runs-on: ubuntu-latest-xl
steps:
@@ -33,14 +24,14 @@ jobs:
with:
key: all-queries
- name: check formatting
run: find shared */ql -type f \( -name "*.qll" -o -name "*.ql" \) -print0 | xargs -0 -n 3000 -P 10 codeql query format -q --check-only
run: find */ql -type f \( -name "*.qll" -o -name "*.ql" \) -print0 | xargs -0 -n 3000 -P 10 codeql query format -q --check-only
- name: compile queries - check-only
# run with --check-only if running in a PR (github.sha != main)
if : ${{ github.event_name == 'pull_request' }}
shell: bash
run: codeql query compile -q -j0 */ql/{src,examples} --keep-going --warnings=error --check-only --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" --compilation-cache-size=500 --ram=56000
run: codeql query compile -q -j0 */ql/{src,examples} --keep-going --warnings=error --check-only --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" --compilation-cache-size=500
- name: compile queries - full
# do full compile if running on main - this populates the cache
if : ${{ github.event_name != 'pull_request' }}
shell: bash
run: codeql query compile -q -j0 */ql/{src,examples} --keep-going --warnings=error --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" --compilation-cache-size=500 --ram=56000
run: codeql query compile -q -j0 */ql/{src,examples} --keep-going --warnings=error --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" --compilation-cache-size=500

View File

@@ -1,53 +0,0 @@
name: "Code scanning - C++"
on:
push:
branches:
- main
- 'rc/*'
pull_request:
branches:
- main
- 'rc/*'
paths:
- 'swift/**'
- '.github/codeql/**'
- '.github/workflows/cpp-swift-analysis.yml'
schedule:
- cron: '0 9 * * 1'
jobs:
CodeQL-Build:
runs-on: ubuntu-24.04
permissions:
contents: read
security-events: write
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@main
# Override language selection by uncommenting this and choosing your languages
with:
languages: cpp
config-file: ./.github/codeql/codeql-config.yml
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y uuid-dev
- name: "Build Swift extractor using Bazel"
run: |
bazel clean --expunge
bazel run //swift:install --nouse_action_cache --noremote_accept_cached --noremote_upload_local_results --spawn_strategy=local
bazel shutdown
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@main

View File

@@ -5,10 +5,8 @@ on:
paths:
- "csharp/**"
- "shared/**"
- "misc/bazel/**"
- .github/actions/fetch-codeql/action.yml
- codeql-workspace.yml
- "MODULE.bazel"
branches:
- main
- "rc/*"
@@ -16,11 +14,9 @@ on:
paths:
- "csharp/**"
- "shared/**"
- "misc/bazel/**"
- .github/workflows/csharp-qltest.yml
- .github/actions/fetch-codeql/action.yml
- codeql-workspace.yml
- "MODULE.bazel"
branches:
- main
- "rc/*"
@@ -29,10 +25,45 @@ defaults:
run:
working-directory: csharp
permissions:
contents: read
jobs:
qlupgrade:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/fetch-codeql
- name: Check DB upgrade scripts
run: |
echo >empty.trap
codeql dataset import -S ql/lib/upgrades/initial/semmlecode.csharp.dbscheme testdb empty.trap
codeql dataset upgrade testdb --additional-packs ql/lib
diff -q testdb/semmlecode.csharp.dbscheme ql/lib/semmlecode.csharp.dbscheme
- name: Check DB downgrade scripts
run: |
echo >empty.trap
rm -rf testdb; codeql dataset import -S ql/lib/semmlecode.csharp.dbscheme testdb empty.trap
codeql resolve upgrades --format=lines --allow-downgrades --additional-packs downgrades \
--dbscheme=ql/lib/semmlecode.csharp.dbscheme --target-dbscheme=downgrades/initial/semmlecode.csharp.dbscheme |
xargs codeql execute upgrades testdb
diff -q testdb/semmlecode.csharp.dbscheme downgrades/initial/semmlecode.csharp.dbscheme
qltest:
runs-on: ubuntu-latest-xl
strategy:
fail-fast: false
matrix:
slice: ["1/2", "2/2"]
steps:
- uses: actions/checkout@v4
- uses: ./csharp/actions/create-extractor-pack
- name: Cache compilation cache
id: query-cache
uses: ./.github/actions/cache-query-compilation
with:
key: csharp-qltest-${{ matrix.slice }}
- name: Run QL tests
run: |
codeql test run --threads=0 --ram 50000 --slice ${{ matrix.slice }} --search-path extractor-pack --check-databases --check-undefined-labels --check-repeated-labels --check-redefined-labels --consistency-queries ql/consistency-queries ql/test --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
env:
GITHUB_TOKEN: ${{ github.token }}
unit-tests:
strategy:
matrix:
@@ -43,14 +74,13 @@ jobs:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.100
dotnet-version: 8.0.100
- name: Extractor unit tests
run: |
dotnet tool restore
dotnet test -p:RuntimeFrameworkVersion=9.0.0 extractor/Semmle.Util.Tests
dotnet test -p:RuntimeFrameworkVersion=9.0.0 extractor/Semmle.Extraction.Tests
dotnet test -p:RuntimeFrameworkVersion=9.0.0 autobuilder/Semmle.Autobuild.CSharp.Tests
dotnet test -p:RuntimeFrameworkVersion=9.0.0 autobuilder/Semmle.Autobuild.Cpp.Tests
dotnet test -p:RuntimeFrameworkVersion=8.0.0 extractor/Semmle.Util.Tests
dotnet test -p:RuntimeFrameworkVersion=8.0.0 extractor/Semmle.Extraction.Tests
dotnet test -p:RuntimeFrameworkVersion=8.0.0 autobuilder/Semmle.Autobuild.CSharp.Tests
dotnet test -p:RuntimeFrameworkVersion=8.0.0 "${{ github.workspace }}/cpp/autobuilder/Semmle.Autobuild.Cpp.Tests"
shell: bash
stubgentest:
runs-on: ubuntu-latest
@@ -66,6 +96,6 @@ jobs:
# Update existing stubs in the repo with the freshly generated ones
mv "$STUBS_PATH/output/stubs/_frameworks" ql/test/resources/stubs/
git status
codeql test run --threads=0 --search-path "${{ github.workspace }}" --check-databases --check-undefined-labels --check-repeated-labels --check-redefined-labels --consistency-queries ql/consistency-queries -- ql/test/library-tests/dataflow/flowsources/aspremote
codeql test run --threads=0 --search-path extractor-pack --check-databases --check-undefined-labels --check-repeated-labels --check-redefined-labels --consistency-queries ql/consistency-queries -- ql/test/library-tests/dataflow/flowsources/aspremote
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@@ -14,10 +14,6 @@ on:
- ".github/workflows/csv-coverage-metrics.yml"
- ".github/actions/fetch-codeql/action.yml"
permissions:
contents: read
security-events: write
jobs:
publish-java:
runs-on: ubuntu-latest
@@ -37,7 +33,7 @@ jobs:
run: |
DATABASE="${{ runner.temp }}/java-database"
codeql database analyze --format=sarif-latest --output=metrics-java.sarif -- "$DATABASE" ./java/ql/src/Metrics/Summaries/FrameworkCoverage.ql
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: metrics-java.sarif
path: metrics-java.sarif
@@ -64,7 +60,7 @@ jobs:
run: |
DATABASE="${{ runner.temp }}/csharp-database"
codeql database analyze --format=sarif-latest --output=metrics-csharp.sarif -- "$DATABASE" ./csharp/ql/src/Metrics/Summaries/FrameworkCoverage.ql
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: metrics-csharp.sarif
path: metrics-csharp.sarif

View File

@@ -19,10 +19,6 @@ on:
- main
- "rc/*"
permissions:
contents: read
pull-requests: read
jobs:
generate:
name: Generate framework coverage artifacts
@@ -71,21 +67,21 @@ jobs:
run: |
python base/misc/scripts/library-coverage/compare-folders.py out_base out_merge comparison.md
- name: Upload CSV package list
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: csv-framework-coverage-merge
path: |
out_merge/framework-coverage-*.csv
out_merge/framework-coverage-*.rst
- name: Upload CSV package list
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: csv-framework-coverage-base
path: |
out_base/framework-coverage-*.csv
out_base/framework-coverage-*.rst
- name: Upload comparison results
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: comparison
path: |
@@ -97,7 +93,7 @@ jobs:
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
- name: Upload PR number
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: pr
path: pr/
@@ -117,7 +113,7 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
- name: Upload comment ID (if it exists)
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: comment
path: comment/

View File

@@ -6,10 +6,6 @@ on:
types:
- completed
permissions:
contents: read
pull-requests: write
jobs:
check:
name: Check framework coverage differences and comment

View File

@@ -3,9 +3,6 @@ name: Build framework coverage timeseries reports
on:
workflow_dispatch:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
@@ -30,7 +27,7 @@ jobs:
run: |
python script/misc/scripts/library-coverage/generate-timeseries.py codeqlModels
- name: Upload timeseries CSV
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: framework-coverage-timeseries
path: framework-coverage-timeseries-*.csv

View File

@@ -5,10 +5,6 @@ on:
schedule:
- cron: "0 0 * * *"
permissions:
contents: write
pull-requests: write
jobs:
update:
name: Update framework coverage report

View File

@@ -7,9 +7,6 @@ on:
description: "github/codeql repo SHA used for looking up the CSV models"
required: false
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
@@ -34,12 +31,12 @@ jobs:
run: |
python script/misc/scripts/library-coverage/generate-report.py ci codeqlModels script
- name: Upload CSV package list
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: framework-coverage-csv
path: framework-coverage-*.csv
- name: Upload RST package list
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: framework-coverage-rst
path: framework-coverage-*.rst

View File

@@ -7,14 +7,13 @@ name: Fast-forward tracking branch for selected CodeQL version
on:
workflow_dispatch:
permissions:
contents: write
jobs:
fast-forward:
name: Fast-forward tracking branch for selected CodeQL version
runs-on: ubuntu-latest
if: github.repository == 'github/codeql'
permissions:
contents: write
env:
BRANCH_NAME: 'lgtm.com'
steps:

View File

@@ -7,29 +7,76 @@ on:
- .github/workflows/go-tests-other-os.yml
- .github/actions/**
- codeql-workspace.yml
- MODULE.bazel
- .bazelrc
- misc/bazel/**
permissions:
contents: read
env:
GO_VERSION: '~1.21.0'
jobs:
test-mac:
name: Test MacOS
runs-on: macos-latest
steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Check out code
uses: actions/checkout@v4
- name: Run tests
uses: ./go/actions/test
- name: Set up CodeQL CLI
uses: ./.github/actions/fetch-codeql
- name: Enable problem matchers in repository
shell: bash
run: 'find .github/problem-matchers -name \*.json -exec echo "::add-matcher::{}" \;'
- name: Build
run: |
cd go
make
- name: Cache compilation cache
id: query-cache
uses: ./.github/actions/cache-query-compilation
with:
key: go-qltest
- name: Test
run: |
cd go
make test cache="${{ steps.query-cache.outputs.cache-dir }}"
test-win:
if: github.repository_owner == 'github'
name: Test Windows
runs-on: windows-latest-xl
steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Check out code
uses: actions/checkout@v4
- name: Run tests
uses: ./go/actions/test
- name: Set up CodeQL CLI
uses: ./.github/actions/fetch-codeql
- name: Enable problem matchers in repository
shell: bash
run: 'find .github/problem-matchers -name \*.json -exec echo "::add-matcher::{}" \;'
- name: Build
run: |
cd go
make
- name: Cache compilation cache
id: query-cache
uses: ./.github/actions/cache-query-compilation
with:
key: go-qltest
- name: Test
run: |
cd go
make test cache="${{ steps.query-cache.outputs.cache-dir }}"

View File

@@ -3,7 +3,6 @@ on:
push:
paths:
- "go/**"
- "shared/**"
- .github/workflows/go-tests.yml
- .github/actions/**
- codeql-workspace.yml
@@ -13,26 +12,60 @@ on:
pull_request:
paths:
- "go/**"
- "shared/**"
- .github/workflows/go-tests.yml
- .github/actions/**
- codeql-workspace.yml
- MODULE.bazel
- .bazelrc
- misc/bazel/**
permissions:
contents: read
env:
GO_VERSION: '~1.21.0'
jobs:
test-linux:
if: github.repository_owner == 'github'
name: Test Linux (Ubuntu)
runs-on: ubuntu-latest-xl
steps:
- name: Set up Go ${{ env.GO_VERSION }}
uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
id: go
- name: Check out code
uses: actions/checkout@v4
- name: Run tests
uses: ./go/actions/test
- name: Set up CodeQL CLI
uses: ./.github/actions/fetch-codeql
- name: Enable problem matchers in repository
shell: bash
run: 'find .github/problem-matchers -name \*.json -exec echo "::add-matcher::{}" \;'
- name: Build
run: |
cd go
make
- name: Check that all Go code is autoformatted
run: |
cd go
make check-formatting
- name: Compile qhelp files to markdown
run: |
cd go
env QHELP_OUT_DIR=qhelp-out make qhelp-to-markdown
- name: Upload qhelp markdown
uses: actions/upload-artifact@v3
with:
run-code-checks: true
name: qhelp-markdown
path: go/qhelp-out/**/*.md
- name: Cache compilation cache
id: query-cache
uses: ./.github/actions/cache-query-compilation
with:
key: go-qltest
- name: Test
run: |
cd go
make test cache="${{ steps.query-cache.outputs.cache-dir }}"

View File

@@ -1,28 +0,0 @@
name: "Kotlin Build"
on:
pull_request:
paths:
- "java/kotlin-extractor/**"
- "misc/bazel/**"
- "misc/codegen/**"
- "*.bazel*"
- .github/workflows/kotlin-build.yml
branches:
- main
- rc/*
- codeql-cli-*
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
bazel query //java/kotlin-extractor/...
# only build the default version as a quick check that we can build from `codeql`
# the full official build will be checked by QLucie
bazel build //java/kotlin-extractor

View File

@@ -2,12 +2,11 @@ name: "Pull Request Labeler"
on:
- pull_request_target
permissions:
contents: read
pull-requests: write
jobs:
triage:
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4

View File

@@ -12,7 +12,6 @@ on:
- main
paths:
- "java/ql/src/utils/modelgenerator/**/*.*"
- "misc/scripts/models-as-data/*.*"
- ".github/workflows/mad_modelDiff.yml"
permissions:
@@ -38,20 +37,14 @@ jobs:
path: codeql-main
ref: main
- uses: ./codeql-main/.github/actions/fetch-codeql
# compute the shortname of the project that does not contain any special (disk) characters
- run: |
echo "SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}" >> $GITHUB_OUTPUT
env:
SLUG: ${{ matrix.slug }}
id: shortname
- name: Download database
env:
SLUG: ${{ matrix.slug }}
GH_TOKEN: ${{ github.token }}
SHORTNAME: ${{ steps.shortname.outputs.SHORTNAME }}
run: |
set -x
mkdir lib-dbs
SHORTNAME=${SLUG//[^a-zA-Z0-9_]/}
gh api -H "Accept: application/zip" "/repos/${SLUG}/code-scanning/codeql/databases/java" > "$SHORTNAME.zip"
unzip -q -d "${SHORTNAME}-db" "${SHORTNAME}.zip"
mkdir "lib-dbs/$SHORTNAME/"
@@ -68,9 +61,8 @@ jobs:
DATABASE=$2
cd codeql-$QL_VARIANT
SHORTNAME=`basename $DATABASE`
python java/ql/src/utils/modelgenerator/GenerateFlowModel.py --with-summaries --with-sinks $DATABASE $SHORTNAME/$QL_VARIANT
mkdir -p $MODELS/$SHORTNAME
mv java/ql/lib/ext/generated/$SHORTNAME/$QL_VARIANT $MODELS/$SHORTNAME
python java/ql/src/utils/modelgenerator/GenerateFlowModel.py --with-summaries --with-sinks $DATABASE ${SHORTNAME}.temp.model.yml
mv java/ql/lib/ext/generated/${SHORTNAME}.temp.model.yml $MODELS/${SHORTNAME}Generated_${QL_VARIANT}.model.yml
cd ..
}
@@ -93,20 +85,20 @@ jobs:
set -x
MODELS=`pwd`/tmp-models
ls -1 tmp-models/
for m in $MODELS/*/main/*.model.yml ; do
for m in $MODELS/*_main.model.yml ; do
t="${m/main/"pr"}"
basename=`basename $m`
name="diff_${basename/.model.yml/""}"
name="diff_${basename/_main.model.yml/""}"
(diff -w -u $m $t | diff2html -i stdin -F $MODELS/$name.html) || true
done
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: models-${{ steps.shortname.outputs.SHORTNAME }}
path: tmp-models/**/**/*.model.yml
name: models
path: tmp-models/*.model.yml
retention-days: 20
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: diffs-${{ steps.shortname.outputs.SHORTNAME }}
name: diffs
path: tmp-models/*.html
# An html file is only produced if the generated models differ.
if-no-files-found: ignore

View File

@@ -11,9 +11,6 @@ on:
- ".github/workflows/mad_regenerate-models.yml"
- ".github/actions/fetch-codeql/action.yml"
permissions:
contents: read
jobs:
regenerate-models:
runs-on: ubuntu-latest
@@ -59,7 +56,7 @@ jobs:
find java -name "*.model.yml" -print0 | xargs -0 git add
git status
git diff --cached > models.patch
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: patch
path: models.patch

View File

@@ -17,11 +17,8 @@ jobs:
post_comment:
runs-on: ubuntu-latest
steps:
- name: Download artifacts
run: |
gh run download "${WORKFLOW_RUN_ID}" --repo "${GITHUB_REPOSITORY}" --name "comment-pr-number"
gh run download "${WORKFLOW_RUN_ID}" --repo "${GITHUB_REPOSITORY}" --name "comment-body"
gh run download "${WORKFLOW_RUN_ID}" --repo "${GITHUB_REPOSITORY}" --name "comment-id"
- name: Download artifact
run: gh run download "${WORKFLOW_RUN_ID}" --repo "${GITHUB_REPOSITORY}" --name "comment"
env:
GITHUB_TOKEN: ${{ github.token }}
WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }}

View File

@@ -36,9 +36,9 @@ jobs:
- run: echo "${PR_NUMBER}" > pr_number.txt
env:
PR_NUMBER: ${{ github.event.number }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: comment-pr-number
name: comment
path: pr_number.txt
if-no-files-found: error
retention-days: 1
@@ -77,10 +77,10 @@ jobs:
done < "${RUNNER_TEMP}/paths.txt" >> comment_body.txt
exit "${EXIT_CODE}"
- if: ${{ !cancelled() }}
uses: actions/upload-artifact@v4
- if: always()
uses: actions/upload-artifact@v3
with:
name: comment-body
name: comment
path: comment_body.txt
if-no-files-found: error
retention-days: 1
@@ -94,9 +94,9 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.number }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: comment-id
name: comment
path: comment_id.txt
if-no-files-found: error
retention-days: 1

View File

@@ -9,13 +9,8 @@ on:
env:
CARGO_TERM_COLOR: always
permissions:
contents: read
security-events: write
jobs:
analyze:
if: github.repository_owner == 'github'
runs-on: ubuntu-latest-xl
steps:
### Build the queries ###
@@ -24,7 +19,7 @@ jobs:
fetch-depth: 0
- name: Find codeql
id: find-codeql
uses: github/codeql-action/init@main
uses: github/codeql-action/init@v2
with:
languages: javascript # does not matter
- uses: ./.github/actions/os-version
@@ -49,20 +44,20 @@ jobs:
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-rust-cargo-${{ hashFiles('ql/**/Cargo.lock') }}
- name: Release build
if: steps.cache-extractor.outputs.cache-hit != 'true'
run: cd ql; ./scripts/create-extractor-pack.sh
run: cd ql; ./scripts/create-extractor-pack.sh
env:
GH_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ github.token }}
- name: Cache compilation cache
id: query-cache
uses: ./.github/actions/cache-query-compilation
with:
with:
key: run-ql-for-ql
- name: Make database and analyze
run: |
./ql/target/release/buramu | tee deprecated.blame # Add a blame file for the extractor to parse.
${CODEQL} database create -l=ql ${DB} --search-path "${{ github.workspace }}"
${CODEQL} database create -l=ql --search-path ql/extractor-pack ${DB}
${CODEQL} database analyze -j0 --format=sarif-latest --output=ql-for-ql.sarif ${DB} ql/ql/src/codeql-suites/ql-code-scanning.qls --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
env:
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
DB: ${{ runner.temp }}/DB
LGTM_INDEX_FILTERS: |
@@ -70,12 +65,12 @@ jobs:
exclude:*/ql/lib/upgrades/
exclude:java/ql/integration-tests
- name: Upload sarif to code-scanning
uses: github/codeql-action/upload-sarif@main
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: ql-for-ql.sarif
category: ql-for-ql
- name: Sarif as artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: ql-for-ql.sarif
path: ql-for-ql.sarif
@@ -84,7 +79,7 @@ jobs:
mkdir split-sarif
node ./ql/scripts/split-sarif.js ql-for-ql.sarif split-sarif
- name: Upload langs as artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: ql-for-ql-langs
path: split-sarif

View File

@@ -11,10 +11,6 @@ on:
- ql/ql/src/ql.dbscheme
workflow_dispatch:
permissions:
contents: read
security-events: read
jobs:
measure:
env:
@@ -29,7 +25,7 @@ jobs:
- name: Find codeql
id: find-codeql
uses: github/codeql-action/init@main
uses: github/codeql-action/init@v2
with:
languages: javascript # does not matter
- uses: ./.github/actions/os-version
@@ -53,8 +49,8 @@ jobs:
- name: Create database
run: |
"${CODEQL}" database create \
--search-path "${{ github.workspace }}"
--threads 4 \
--search-path "ql/extractor-pack" \
--threads 4 \
--language ql --source-root "${{ github.workspace }}/repo" \
"${{ runner.temp }}/database"
env:
@@ -65,7 +61,7 @@ jobs:
"${CODEQL}" dataset measure --threads 4 --output "stats/${{ matrix.repo }}/stats.xml" "${{ runner.temp }}/database/db-ql"
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: measurements
path: stats
@@ -76,14 +72,14 @@ jobs:
needs: measure
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: measurements
path: stats
- run: |
python -m pip install --user lxml
find stats -name 'stats.xml' -print0 | sort -z | xargs -0 python ruby/scripts/merge_stats.py --output ql/ql/src/ql.dbscheme.stats --normalise ql_tokeninfo
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: ql.dbscheme.stats
path: ql/ql/src/ql.dbscheme.stats

View File

@@ -17,9 +17,6 @@ on:
env:
CARGO_TERM_COLOR: always
permissions:
contents: read
jobs:
qltest:
runs-on: ubuntu-latest
@@ -27,7 +24,7 @@ jobs:
- uses: actions/checkout@v4
- name: Find codeql
id: find-codeql
uses: github/codeql-action/init@main
uses: github/codeql-action/init@v2
with:
languages: javascript # does not matter
- uses: ./.github/actions/os-version
@@ -40,7 +37,7 @@ jobs:
ql/target
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-qltest-cargo-${{ hashFiles('ql/rust-toolchain.toml', 'ql/**/Cargo.lock') }}
- name: Check formatting
run: cd ql; cargo fmt -- --check
run: cd ql; cargo fmt --all -- --check
- name: Build extractor
run: |
cd ql;
@@ -49,15 +46,15 @@ jobs:
- name: Cache compilation cache
id: query-cache
uses: ./.github/actions/cache-query-compilation
with:
with:
key: ql-for-ql-tests
- name: Run QL tests
run: |
"${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}" --consistency-queries ql/ql/consistency-queries --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" ql/ql/test
"${CODEQL}" test run --check-databases --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --search-path "${{ github.workspace }}/ql/extractor-pack" --consistency-queries ql/ql/consistency-queries --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}" ql/ql/test
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
other-os:
other-os:
strategy:
matrix:
os: [macos-latest, windows-latest]
@@ -65,14 +62,14 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Install GNU tar
- name: Install GNU tar
if: runner.os == 'macOS'
run: |
brew install gnu-tar
echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
- name: Find codeql
id: find-codeql
uses: github/codeql-action/init@main
uses: github/codeql-action/init@v2
with:
languages: javascript # does not matter
- uses: ./.github/actions/os-version
@@ -100,7 +97,7 @@ jobs:
- name: Run a single QL tests - Unix
if: runner.os != 'Windows'
run: |
"${CODEQL}" test run --check-databases --search-path "${{ github.workspace }}" ql/ql/test/queries/style/DeadCode/DeadCode.qlref
"${CODEQL}" test run --check-databases --search-path "${{ github.workspace }}/ql/extractor-pack" ql/ql/test/queries/style/DeadCode/DeadCode.qlref
env:
CODEQL: ${{ steps.find-codeql.outputs.codeql-path }}
- name: Run a single QL tests - Windows
@@ -108,4 +105,5 @@ jobs:
shell: pwsh
run: |
$Env:PATH += ";$(dirname ${{ steps.find-codeql.outputs.codeql-path }})"
codeql test run --check-databases --search-path "${{ github.workspace }}" ql/ql/test/queries/style/DeadCode/DeadCode.qlref
codeql test run --check-databases --search-path "${{ github.workspace }}/ql/extractor-pack" ql/ql/test/queries/style/DeadCode/DeadCode.qlref

View File

@@ -13,9 +13,6 @@ on:
- '.github/actions/fetch-codeql/action.yml'
- 'misc/scripts/generate-code-scanning-query-list.py'
permissions:
contents: read
jobs:
build:
@@ -37,7 +34,7 @@ jobs:
run: |
python codeql/misc/scripts/generate-code-scanning-query-list.py > code-scanning-query-list.csv
- name: Upload code scanning query list
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: code-scanning-query-list
path: code-scanning-query-list.csv

View File

@@ -7,7 +7,6 @@ on:
- .github/workflows/ruby-build.yml
- .github/actions/fetch-codeql/action.yml
- codeql-workspace.yml
- "shared/tree-sitter-extractor/**"
branches:
- main
- "rc/*"
@@ -17,7 +16,6 @@ on:
- .github/workflows/ruby-build.yml
- .github/actions/fetch-codeql/action.yml
- codeql-workspace.yml
- "shared/tree-sitter-extractor/**"
branches:
- main
- "rc/*"
@@ -34,9 +32,6 @@ defaults:
run:
working-directory: ruby
permissions:
contents: read
jobs:
build:
strategy:
@@ -53,11 +48,9 @@ jobs:
run: |
brew install gnu-tar
echo "/usr/local/opt/gnu-tar/libexec/gnubin" >> $GITHUB_PATH
- name: Prepare Windows
if: runner.os == 'Windows'
shell: powershell
run: |
git config --global core.longpaths true
- name: Install cargo-cross
if: runner.os == 'Linux'
run: cargo install cross --version 0.2.5
- uses: ./.github/actions/os-version
id: os_version
- name: Cache entire extractor
@@ -65,8 +58,8 @@ jobs:
id: cache-extractor
with:
path: |
target/release/codeql-extractor-ruby
target/release/codeql-extractor-ruby.exe
ruby/extractor/target/release/codeql-extractor-ruby
ruby/extractor/target/release/codeql-extractor-ruby.exe
ruby/extractor/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-ruby-extractor-${{ hashFiles('ruby/extractor/rust-toolchain.toml', 'ruby/extractor/Cargo.lock') }}-${{ hashFiles('shared/tree-sitter-extractor') }}-${{ hashFiles('ruby/extractor/**/*.rs') }}
- uses: actions/cache@v3
@@ -75,42 +68,49 @@ jobs:
path: |
~/.cargo/registry
~/.cargo/git
target
ruby/target
key: ${{ runner.os }}-${{ steps.os_version.outputs.version }}-ruby-rust-cargo-${{ hashFiles('ruby/extractor/rust-toolchain.toml', 'ruby/extractor/**/Cargo.lock') }}
- name: Check formatting
if: steps.cache-extractor.outputs.cache-hit != 'true'
run: cd extractor && cargo fmt -- --check
run: cd extractor && cargo fmt --all -- --check
- name: Build
if: steps.cache-extractor.outputs.cache-hit != 'true'
run: cd extractor && cargo build --verbose
- name: Run tests
if: steps.cache-extractor.outputs.cache-hit != 'true'
run: cd extractor && cargo test --verbose
- name: Release build
if: steps.cache-extractor.outputs.cache-hit != 'true'
# On linux, build the extractor via cross in a centos7 container.
# This ensures we don't depend on glibc > 2.17.
- name: Release build (linux)
if: steps.cache-extractor.outputs.cache-hit != 'true' && runner.os == 'Linux'
run: |
cd extractor
cross build --release
mv target/x86_64-unknown-linux-gnu/release/codeql-extractor-ruby target/release/
- name: Release build (windows and macos)
if: steps.cache-extractor.outputs.cache-hit != 'true' && runner.os != 'Linux'
run: cd extractor && cargo build --release
- name: Generate dbscheme
if: ${{ matrix.os == 'ubuntu-latest' && steps.cache-extractor.outputs.cache-hit != 'true'}}
run: ../target/release/codeql-extractor-ruby generate --dbscheme ql/lib/ruby.dbscheme --library ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
- uses: actions/upload-artifact@v4
run: extractor/target/release/codeql-extractor-ruby generate --dbscheme ql/lib/ruby.dbscheme --library ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
- uses: actions/upload-artifact@v3
if: ${{ matrix.os == 'ubuntu-latest' }}
with:
name: ruby.dbscheme
path: ruby/ql/lib/ruby.dbscheme
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
if: ${{ matrix.os == 'ubuntu-latest' }}
with:
name: TreeSitter.qll
path: ruby/ql/lib/codeql/ruby/ast/internal/TreeSitter.qll
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: extractor-${{ matrix.os }}
path: |
target/release/codeql-extractor-ruby
target/release/codeql-extractor-ruby.exe
ruby/extractor/target/release/codeql-extractor-ruby
ruby/extractor/target/release/codeql-extractor-ruby.exe
retention-days: 1
compile-queries:
if: github.repository_owner == 'github'
runs-on: ubuntu-latest-xl
steps:
- uses: actions/checkout@v4
@@ -119,7 +119,7 @@ jobs:
- name: Cache compilation cache
id: query-cache
uses: ./.github/actions/cache-query-compilation
with:
with:
key: ruby-build
- name: Build Query Pack
run: |
@@ -134,32 +134,31 @@ jobs:
PACK_FOLDER=$(readlink -f "$PACKS"/codeql/ruby-queries/*)
codeql generate query-help --format=sarifv2.1.0 --output="${PACK_FOLDER}/rules.sarif" ql/src
(cd ql/src; find queries \( -name '*.qhelp' -o -name '*.rb' -o -name '*.erb' \) -exec bash -c 'mkdir -p "'"${PACK_FOLDER}"'/$(dirname "{}")"' \; -exec cp "{}" "${PACK_FOLDER}/{}" \;)
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: codeql-ruby-queries
path: |
${{ runner.temp }}/query-packs/*
retention-days: 1
include-hidden-files: true
package:
runs-on: ubuntu-latest
needs: [build, compile-queries]
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: ruby.dbscheme
path: ruby/ruby
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: extractor-ubuntu-latest
path: ruby/linux64
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: extractor-windows-latest
path: ruby/win64
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: extractor-macos-latest
path: ruby/osx64
@@ -172,13 +171,12 @@ jobs:
cp win64/codeql-extractor-ruby.exe ruby/tools/win64/extractor.exe
chmod +x ruby/tools/{linux64,osx64}/extractor
zip -rq codeql-ruby.zip ruby
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: codeql-ruby-pack
path: ruby/codeql-ruby.zip
retention-days: 1
include-hidden-files: true
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: codeql-ruby-queries
path: ruby/qlpacks
@@ -190,12 +188,11 @@ jobs:
]
}' > .codeqlmanifest.json
zip -rq codeql-ruby-bundle.zip .codeqlmanifest.json ruby qlpacks
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: codeql-ruby-bundle
path: ruby/codeql-ruby-bundle.zip
retention-days: 1
include-hidden-files: true
test:
defaults:
@@ -214,7 +211,7 @@ jobs:
uses: ./.github/actions/fetch-codeql
- name: Download Ruby bundle
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: codeql-ruby-bundle
path: ${{ runner.temp }}
@@ -234,3 +231,54 @@ jobs:
shell: bash
run: |
codeql database analyze --search-path "${{ runner.temp }}/ruby-bundle" --format=sarifv2.1.0 --output=out.sarif ../database ruby-code-scanning.qls
# This is a copy of the 'test' job that runs in a centos7 container.
# This tests that the extractor works correctly on systems with an old glibc.
test-centos7:
defaults:
run:
working-directory: ${{ github.workspace }}
strategy:
fail-fast: false
runs-on: ubuntu-latest
container:
image: centos:centos7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
needs: [package]
steps:
- name: Install gh cli
run: |
yum-config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
# fetch-codeql requires unzip and jq
# jq is available in epel-release (https://docs.fedoraproject.org/en-US/epel/)
yum install -y gh unzip epel-release
yum install -y jq
- uses: actions/checkout@v3
- name: Fetch CodeQL
uses: ./.github/actions/fetch-codeql
# Due to a bug in Actions, we can't use runner.temp in the run blocks here.
# https://github.com/actions/runner/issues/2185
- name: Download Ruby bundle
uses: actions/download-artifact@v3
with:
name: codeql-ruby-bundle
path: ${{ runner.temp }}
- name: Unzip Ruby bundle
shell: bash
run: unzip -q -d "$RUNNER_TEMP"/ruby-bundle "$RUNNER_TEMP"/codeql-ruby-bundle.zip
- name: Run QL test
shell: bash
run: |
codeql test run --search-path "$RUNNER_TEMP"/ruby-bundle --additional-packs "$RUNNER_TEMP"/ruby-bundle ruby/ql/test/library-tests/ast/constants/
- name: Create database
shell: bash
run: |
codeql database create --search-path "$RUNNER_TEMP"/ruby-bundle --language ruby --source-root ruby/ql/test/library-tests/ast/constants/ ../database
- name: Analyze database
shell: bash
run: |
codeql database analyze --search-path "$RUNNER_TEMP"/ruby-bundle --format=sarifv2.1.0 --output=out.sarif ../database ruby-code-scanning.qls

View File

@@ -17,9 +17,6 @@ on:
- .github/workflows/ruby-dataset-measure.yml
workflow_dispatch:
permissions:
contents: read
jobs:
measure:
env:
@@ -44,7 +41,7 @@ jobs:
- name: Create database
run: |
codeql database create \
--search-path "${{ github.workspace }}" \
--search-path "${{ github.workspace }}/ruby/extractor-pack" \
--threads 4 \
--language ruby --source-root "${{ github.workspace }}/repo" \
"${{ runner.temp }}/database"
@@ -52,9 +49,9 @@ jobs:
run: |
mkdir -p "stats/${{ matrix.repo }}"
codeql dataset measure --threads 4 --output "stats/${{ matrix.repo }}/stats.xml" "${{ runner.temp }}/database/db-ruby"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: measurements-${{ hashFiles('stats/**') }}
name: measurements
path: stats
retention-days: 1
@@ -63,13 +60,14 @@ jobs:
needs: measure
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@v3
with:
name: measurements
path: stats
- run: |
python -m pip install --user lxml
find stats -name 'stats.xml' | sort | xargs python ruby/scripts/merge_stats.py --output ruby/ql/lib/ruby.dbscheme.stats --normalise ruby_tokeninfo
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: ruby.dbscheme.stats
path: ruby/ql/lib/ruby.dbscheme.stats

View File

@@ -29,9 +29,6 @@ defaults:
run:
working-directory: ruby
permissions:
contents: read
jobs:
qlupgrade:
runs-on: ubuntu-latest
@@ -53,7 +50,6 @@ jobs:
xargs codeql execute upgrades testdb
diff -q testdb/ruby.dbscheme downgrades/initial/ruby.dbscheme
qltest:
if: github.repository_owner == 'github'
runs-on: ubuntu-latest-xl
strategy:
fail-fast: false
@@ -64,10 +60,10 @@ jobs:
- name: Cache compilation cache
id: query-cache
uses: ./.github/actions/cache-query-compilation
with:
with:
key: ruby-qltest
- name: Run QL tests
run: |
codeql test run --threads=0 --ram 50000 --search-path "${{ github.workspace }}" --check-databases --check-undefined-labels --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
codeql test run --threads=0 --ram 50000 --search-path "${{ github.workspace }}/ruby/extractor-pack" --check-databases --check-undefined-labels --check-unused-labels --check-repeated-labels --check-redefined-labels --check-use-before-definition --consistency-queries ql/consistency-queries ql/test --compilation-cache "${{ steps.query-cache.outputs.cache-dir }}"
env:
GITHUB_TOKEN: ${{ github.token }}

View File

@@ -1,64 +0,0 @@
name: "Code scanning - Rust"
on:
push:
branches:
- main
- 'rc/*'
pull_request:
branches:
- main
- 'rc/*'
paths:
- '**/*.rs'
- '**/Cargo.toml'
- '.github/codeql/codeql-config.yml'
- '.github/workflows/rust-analysis.yml'
schedule:
- cron: '0 9 * * 1'
env:
CODEQL_ENABLE_EXPERIMENTAL_FEATURES: "true"
jobs:
analyze:
strategy:
matrix:
language: [ 'rust' ]
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
pull-requests: read
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Query latest nightly CodeQL bundle
shell: bash
id: codeql
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
REPO=dsp-testing/codeql-cli-nightlies
TAG=$(
gh release list -R $REPO -L1 --exclude-drafts --json tagName -q ".[] | .tagName"
)
echo "nightly_bundle=https://github.com/$REPO/releases/download/$TAG/codeql-bundle-linux64.tar.zst" \
| tee -a "$GITHUB_OUTPUT"
- name: Initialize CodeQL
uses: github/codeql-action/init@main
with:
tools: ${{ steps.codeql.outputs.nightly_bundle }}
languages: ${{ matrix.language }}
config-file: ./.github/codeql/codeql-config.yml
- name: Autobuild
uses: github/codeql-action/autobuild@main
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@main

View File

@@ -1,80 +0,0 @@
name: "Rust"
on:
pull_request:
paths:
- "rust/**"
- "misc/bazel/**"
- "misc/codegen/**"
- "shared/**"
- "MODULE.bazel"
- .github/workflows/rust.yml
- .github/actions/**
- codeql-workspace.yml
- "!**/*.md"
- "!**/*.qhelp"
branches:
- rust-experiment
- main
- rc/*
- codeql-cli-*
permissions:
contents: read
jobs:
rust-ast-generator:
runs-on: ubuntu-latest
defaults:
run:
working-directory: rust/ast-generator
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Inject sources
shell: bash
run: |
bazel run //rust/ast-generator:inject-sources
- name: Format
shell: bash
run: |
cargo fmt --check
- name: Compilation
shell: bash
run: cargo check
- name: Clippy
shell: bash
run: |
cargo clippy --no-deps -- -D warnings
rust-code:
runs-on: ubuntu-latest
defaults:
run:
working-directory: rust/extractor
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Format
shell: bash
run: |
cargo fmt --check
- name: Compilation
shell: bash
run: cargo check
- name: Clippy
shell: bash
run: |
cargo clippy --no-deps -- -D warnings
rust-codegen:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install CodeQL
uses: ./.github/actions/fetch-codeql
- name: Code generation
shell: bash
run: |
bazel run //rust/codegen
git add .
git diff --exit-code HEAD

View File

@@ -6,7 +6,6 @@ on:
- "swift/**"
- "misc/bazel/**"
- "misc/codegen/**"
- "shared/**"
- "*.bazel*"
- .github/workflows/swift.yml
- .github/actions/**
@@ -23,12 +22,10 @@ on:
- "swift/**"
- "misc/bazel/**"
- "misc/codegen/**"
- "shared/**"
- "*.bazel*"
- .github/workflows/swift.yml
- .github/actions/**
- codeql-workspace.yml
- .pre-commit-config.yaml
- "!**/*.md"
- "!**/*.qhelp"
branches:
@@ -36,40 +33,46 @@ on:
- rc/*
- codeql-cli-*
permissions:
contents: read
jobs:
# not using a matrix as you cannot depend on a specific job in a matrix, and we want to start linux checks
# without waiting for the macOS build
build-and-test-macos:
if: github.repository_owner == 'github'
runs-on: macos-13-xlarge
runs-on: macos-12-xl
steps:
- uses: actions/checkout@v4
- uses: ./swift/actions/build-and-test
build-and-test-linux:
if: github.repository_owner == 'github'
runs-on: ubuntu-22.04
runs-on: ubuntu-latest-xl
steps:
- uses: actions/checkout@v4
- uses: ./swift/actions/build-and-test
qltests-macos:
if: ${{ github.repository_owner == 'github' && github.event_name == 'pull_request' }}
needs: build-and-test-macos
runs-on: macos-13-xlarge
qltests-linux:
needs: build-and-test-linux
runs-on: ubuntu-latest-xl
steps:
- uses: actions/checkout@v4
- uses: ./swift/actions/run-ql-tests
clang-format:
qltests-macos:
if : ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
needs: build-and-test-macos
runs-on: macos-12-xl
steps:
- uses: actions/checkout@v4
- uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507
name: Check that python code is properly formatted
with:
extra_args: clang-format --all-files
- uses: ./swift/actions/run-ql-tests
integration-tests-linux:
needs: build-and-test-linux
runs-on: ubuntu-latest-xl
steps:
- uses: actions/checkout@v4
- uses: ./swift/actions/run-integration-tests
integration-tests-macos:
if : ${{ github.event_name == 'pull_request' }}
needs: build-and-test-macos
runs-on: macos-12-xl
timeout-minutes: 60
steps:
- uses: actions/checkout@v4
- uses: ./swift/actions/run-integration-tests
codegen:
if : ${{ github.event_name == 'pull_request' }}
runs-on: ubuntu-latest
@@ -79,19 +82,19 @@ jobs:
- uses: actions/setup-python@v4
with:
python-version-file: 'swift/.python-version'
- uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507
- uses: pre-commit/action@v3.0.0
name: Check that python code is properly formatted
with:
extra_args: autopep8 --all-files
- uses: ./.github/actions/fetch-codeql
- uses: pre-commit/action@646c83fcd040023954eafda54b4db0192ce70507
- uses: pre-commit/action@v3.0.0
name: Check that QL generated code was checked in
with:
extra_args: swift-codegen --all-files
- name: Generate C++ files
run: |
bazel run //swift/codegen:codegen -- --generate=trap,cpp --cpp-output=$PWD/generated-cpp-files
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v3
with:
name: swift-generated-cpp-files
path: generated-cpp-files/**
@@ -102,10 +105,3 @@ jobs:
- uses: actions/checkout@v4
- uses: ./.github/actions/fetch-codeql
- uses: ./swift/actions/database-upgrade-scripts
check-no-override:
if : github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- shell: bash
run: bazel test //swift/... --test_tag_filters=override --test_output=errors

View File

@@ -10,9 +10,6 @@ on:
- main
- 'rc/*'
permissions:
contents: read
jobs:
sync:
runs-on: ubuntu-latest

View File

@@ -23,26 +23,23 @@ defaults:
run:
working-directory: shared/tree-sitter-extractor
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check formatting
run: cargo fmt -- --check
run: cargo fmt --all -- --check
- name: Run tests
run: cargo test --verbose
fmt:
runs-on: ubuntu-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check formatting
run: cargo fmt --check
clippy:
runs-on: ubuntu-latest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run clippy

View File

@@ -15,9 +15,6 @@ on:
- ".github/workflows/validate-change-notes.yml"
- ".github/actions/fetch-codeql/action.yml"
permissions:
contents: read
jobs:
check-change-note:
runs-on: ubuntu-latest

View File

@@ -1,23 +0,0 @@
name: "Test zipmerge code"
on:
pull_request:
paths:
- "misc/bazel/internal/zipmerge/**"
- "MODULE.bazel"
- ".bazelrc*"
branches:
- main
- "rc/*"
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: |
bazel test //misc/bazel/internal/zipmerge:test --test_output=all

16
.gitignore vendored
View File

@@ -7,8 +7,8 @@
.cache
# qltest projects and artifacts
*.actual
*/ql/test*/**/*.testproj
*/ql/test/**/*.testproj
*/ql/test/**/*.actual
*/ql/test/**/go.sum
# Visual studio temporaries, except a file used by QL4VS
@@ -39,9 +39,6 @@
# local bazel options
/local.bazelrc
# generated cmake directory
/.bazel-cmake
# CLion project files
/.clwb
@@ -62,12 +59,3 @@ node_modules/
# Temporary folders for working with generated models
.model-temp
# bazel-built in-tree extractor packs
/*/extractor-pack
# Jetbrains IDE files
.idea
# cargo build directory
/target

View File

@@ -1,7 +0,0 @@
[lfs]
# codeql is publicly forked by many users, and we don't want any LFS file polluting their working
# copies. We therefore exclude everything by default.
# For files required by bazel builds, use rules in `misc/bazel/lfs.bzl` to download them on demand.
# we go for `fetchinclude` to something not exsiting rather than `fetchexclude = *` because the
# former is easier to override (with `git -c` or a local git config) to fetch something specific
fetchinclude = /nothing

View File

@@ -5,38 +5,29 @@ repos:
rev: v3.2.0
hooks:
- id: trailing-whitespace
exclude: /test/.*$(?<!\.qlref)|.*\.patch$|.*\.qll?$
exclude: /test/.*$(?<!\.ql)(?<!\.qll)(?<!\.qlref)|.*\.patch
- id: end-of-file-fixer
exclude: /test/.*$(?<!\.qlref)|.*\.patch$|.*\.qll?$
exclude: /test/.*$(?<!\.ql)(?<!\.qll)(?<!\.qlref)|.*\.patch
- repo: https://github.com/pre-commit/mirrors-clang-format
rev: v17.0.6
rev: v13.0.1
hooks:
- id: clang-format
files: ^swift/.*\.(h|c|cpp)$
- repo: https://github.com/pre-commit/mirrors-autopep8
rev: v2.0.4
rev: v1.6.0
hooks:
- id: autopep8
files: ^misc/codegen/.*\.py
- repo: local
- repo: https://github.com/warchant/pre-commit-buildifier
rev: 0.0.2
hooks:
- id: buildifier
name: Format bazel files
files: \.(bazel|bzl)
language: system
entry: bazel run //misc/bazel/buildifier
pass_filenames: false
# DISABLED: can be enabled by copying this config and installing `pre-commit` with `--config` on the copy
# - id: go-gen
# name: Check checked in generated files in go
# files: ^go/.*
# language: system
# entry: bazel run //go:gen
# pass_filenames: false
- repo: local
hooks:
- id: codeql-format
name: Fix QL file formatting
files: \.qll?$
@@ -45,7 +36,7 @@ repos:
- id: sync-files
name: Fix files required to be identical
files: \.(qll?|qhelp|swift|toml)$|^config/identical-files\.json$
files: \.(qll?|qhelp|swift)$|^config/identical-files\.json$
language: system
entry: python3 config/sync-files.py --latest
pass_filenames: false
@@ -58,7 +49,7 @@ repos:
- id: swift-codegen
name: Run Swift checked in code generation
files: ^misc/codegen/|^swift/(schema.py$|codegen/|.*/generated/|ql/lib/(swift\.dbscheme$|codeql/swift/elements)|ql/\.generated.list)
files: ^swift/(schema.py$|codegen/|.*/generated/|ql/lib/(swift\.dbscheme$|codeql/swift/elements)|ql/\.generated.list)
language: system
entry: bazel run //swift/codegen -- --quiet
pass_filenames: false
@@ -69,17 +60,3 @@ repos:
language: system
entry: bazel test //misc/codegen/test
pass_filenames: false
- id: rust-codegen
name: Run Rust checked in code generation
files: ^misc/codegen/|^rust/(prefix\.dbscheme|schema/|codegen/|.*/generated/|ql/lib/(rust\.dbscheme$|codeql/rust/elements)|\.generated.list)
language: system
entry: bazel run //rust/codegen -- --quiet
pass_filenames: false
- id: rust-lint
name: Run fmt and clippy on Rust code
files: ^rust/extractor/(.*rs|Cargo.toml)$
language: system
entry: python3 rust/lint.py
pass_filenames: false

View File

@@ -1,6 +1,5 @@
{
"omnisharp.autoStart": false,
"cmake.sourceDirectory": "${workspaceFolder}/swift",
"cmake.buildDirectory": "${workspaceFolder}/bazel-cmake-build",
"editor.suggest.matchOnWordStartOnly": false
"cmake.buildDirectory": "${workspaceFolder}/bazel-cmake-build"
}

88
.vscode/tasks.json vendored
View File

@@ -38,94 +38,6 @@
"command": "${config:python.pythonPath}",
},
"problemMatcher": []
},
{
"label": "Create query change note",
"type": "process",
"command": "python3",
"args": [
"misc/scripts/create-change-note.py",
"${input:language}",
"src",
"${input:name}",
"${input:categoryQuery}"
],
"presentation": {
"reveal": "never",
"close": true
},
"problemMatcher": []
},
{
"label": "Create library change note",
"type": "process",
"command": "python3",
"args": [
"misc/scripts/create-change-note.py",
"${input:language}",
"lib",
"${input:name}",
"${input:categoryLibrary}"
],
"presentation": {
"reveal": "never",
"close": true
},
"problemMatcher": []
}
],
"inputs": [
{
"type": "pickString",
"id": "language",
"description": "Language",
"options":
[
"actions",
"go",
"java",
"javascript",
"cpp",
"csharp",
"python",
"ruby",
"rust",
"swift",
]
},
{
"type": "promptString",
"id": "name",
"description": "Short name (kebab-case)"
},
{
"type": "pickString",
"id": "categoryQuery",
"description": "Category (query change)",
"options":
[
"breaking",
"deprecated",
"newQuery",
"queryMetadata",
"majorAnalysis",
"minorAnalysis",
"fix",
]
},
{
"type": "pickString",
"id": "categoryLibrary",
"description": "Category (library change)",
"options":
[
"breaking",
"deprecated",
"feature",
"majorAnalysis",
"minorAnalysis",
"fix",
]
}
]
}

View File

@@ -1,5 +0,0 @@
exports_files([
"LICENSE",
"Cargo.lock",
"Cargo.toml",
])

View File

@@ -1,8 +1,5 @@
/actions/ @github/codeql-dynamic
/cpp/ @github/codeql-c-analysis
/csharp/ @github/codeql-csharp
/csharp/autobuilder/Semmle.Autobuild.Cpp @github/codeql-c-extractor
/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests @github/codeql-c-extractor
/go/ @github/codeql-go
/java/ @github/codeql-java
/javascript/ @github/codeql-javascript
@@ -14,6 +11,9 @@
/java/ql/test-kotlin1/ @github/codeql-kotlin
/java/ql/test-kotlin2/ @github/codeql-kotlin
# ML-powered queries
/javascript/ql/experimental/adaptivethreatmodeling/ @github/codeql-ml-powered-queries-reviewers
# CodeQL tools and associated docs
/docs/codeql/codeql-cli/ @github/codeql-cli-reviewers
/docs/codeql/codeql-for-visual-studio-code/ @github/codeql-vscode-reviewers
@@ -24,7 +24,7 @@
/ql/ @github/codeql-ql-for-ql-reviewers
# Bazel (excluding BUILD.bazel files)
MODULE.bazel @github/codeql-ci-reviewers
WORKSPACE.bazel @github/codeql-ci-reviewers
.bazelversion @github/codeql-ci-reviewers
.bazelrc @github/codeql-ci-reviewers
**/*.bzl @github/codeql-ci-reviewers
@@ -35,7 +35,9 @@ MODULE.bazel @github/codeql-ci-reviewers
# Workflows
/.github/workflows/ @github/codeql-ci-reviewers
/.github/workflows/atm-* @github/codeql-ml-powered-queries-reviewers
/.github/workflows/go-* @github/codeql-go
/.github/workflows/js-ml-tests.yml @github/codeql-ml-powered-queries-reviewers
/.github/workflows/ql-for-ql-* @github/codeql-ql-for-ql-reviewers
/.github/workflows/ruby-* @github/codeql-ruby
/.github/workflows/swift.yml @github/codeql-swift
@@ -43,6 +45,3 @@ MODULE.bazel @github/codeql-ci-reviewers
# Misc
/misc/scripts/accept-expected-changes-from-ci.py @RasmusWL
/misc/scripts/generate-code-scanning-query-list.py @RasmusWL
# .devcontainer
/.devcontainer/ @github/codeql-ci-reviewers

View File

@@ -4,8 +4,6 @@ We welcome contributions to our CodeQL libraries and queries. Got an idea for a
There is lots of useful documentation to help you write queries, ranging from information about query file structure to tutorials for specific target languages. For more information on the documentation available, see [CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/codeql-queries) on [codeql.github.com](https://codeql.github.com).
Note that the CodeQL for Visual Studio Code documentation has been migrated to https://docs.github.com/en/code-security/codeql-for-vs-code/, but you can still contribute to it via a different repository. For more information, see [Contributing to GitHub Docs documentation](https://docs.github.com/en/contributing)."
## Change notes
Any nontrivial user-visible change to a query pack or library pack should have a change note. For details on how to add a change note for your change, see [this guide](docs/change-notes.md).
@@ -45,7 +43,7 @@ If you have an idea for a query that you would like to share with other CodeQL u
3. **Formatting**
- The queries and libraries must be autoformatted, for example using the "Format Document" command in [CodeQL for Visual Studio Code](https://docs.github.com/en/code-security/codeql-for-vs-code/).
- The queries and libraries must be autoformatted, for example using the "Format Document" command in [CodeQL for Visual Studio Code](https://codeql.github.com/docs/codeql-for-visual-studio-code/about-codeql-for-visual-studio-code).
If you prefer, you can either:
1. install the [pre-commit framework](https://pre-commit.com/) and install the configured hooks on this repo via `pre-commit install`, or

2835
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +0,0 @@
# This is the shared workspace file for extractor using shared/tree-sitter/extractor
[workspace]
resolver = "2"
members = [
"shared/tree-sitter-extractor",
"ruby/extractor",
"rust/extractor",
"rust/extractor/macros",
"rust/ast-generator",
"rust/autobuild",
]
[patch.crates-io]
# patch for build script bug preventing bazel build
# see https://github.com/rust-lang/rustc_apfloat/pull/17
rustc_apfloat = { git = "https://github.com/redsun82/rustc_apfloat.git", rev = "096d585100636bc2e9f09d7eefec38c5b334d47b" }

View File

@@ -1,280 +0,0 @@
module(
name = "ql",
version = "0.0",
repo_name = "codeql",
)
# this points to our internal repository when `codeql` is checked out as a submodule thereof
# when building things from `codeql` independently this is stubbed out in `.bazelrc`
bazel_dep(name = "semmle_code", version = "0.0")
local_path_override(
module_name = "semmle_code",
path = "..",
)
# see https://registry.bazel.build/ for a list of available packages
bazel_dep(name = "platforms", version = "0.0.10")
bazel_dep(name = "rules_go", version = "0.50.1")
bazel_dep(name = "rules_pkg", version = "1.0.1")
bazel_dep(name = "rules_nodejs", version = "6.2.0-codeql.1")
bazel_dep(name = "rules_python", version = "0.40.0")
bazel_dep(name = "rules_shell", version = "0.3.0")
bazel_dep(name = "bazel_skylib", version = "1.7.1")
bazel_dep(name = "abseil-cpp", version = "20240116.1", repo_name = "absl")
bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json")
bazel_dep(name = "fmt", version = "10.0.0")
bazel_dep(name = "rules_kotlin", version = "2.0.0-codeql.1")
bazel_dep(name = "gazelle", version = "0.40.0")
bazel_dep(name = "rules_dotnet", version = "0.17.4")
bazel_dep(name = "googletest", version = "1.14.0.bcr.1")
bazel_dep(name = "rules_rust", version = "0.52.2")
bazel_dep(name = "zstd", version = "1.5.5.bcr.1")
bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True)
# Keep edition and version approximately in sync with internal repo.
# the versions there are canonical, the versions here are used for CI in github/codeql, as well as for the vendoring of dependencies.
RUST_EDITION = "2021"
RUST_VERSION = "1.82.0"
rust = use_extension("@rules_rust//rust:extensions.bzl", "rust")
rust.toolchain(
edition = RUST_EDITION,
# We need those extra target triples so that we can build universal binaries on macos
extra_target_triples = [
"x86_64-apple-darwin",
"aarch64-apple-darwin",
],
versions = [RUST_VERSION],
)
use_repo(rust, "rust_toolchains")
register_toolchains("@rust_toolchains//:all")
rust_host_tools = use_extension("@rules_rust//rust:extensions.bzl", "rust_host_tools")
# Don't download a second toolchain as host toolchain, make sure this is the same version as above
# The host toolchain is used for vendoring dependencies.
rust_host_tools.host_tools(
edition = RUST_EDITION,
version = RUST_VERSION,
)
# deps for python extractor
# keep in sync by running `misc/bazel/3rdparty/update_cargo_deps.sh`
py_deps = use_extension("//misc/bazel/3rdparty:py_deps_extension.bzl", "p")
use_repo(
py_deps,
"vendor__anyhow-1.0.44",
"vendor__cc-1.0.70",
"vendor__clap-2.33.3",
"vendor__regex-1.5.5",
"vendor__smallvec-1.6.1",
"vendor__string-interner-0.12.2",
"vendor__thiserror-1.0.29",
"vendor__tree-sitter-0.20.4",
"vendor__tree-sitter-graph-0.7.0",
)
# deps for ruby+rust
# keep in sync by running `misc/bazel/3rdparty/update_cargo_deps.sh`
tree_sitter_extractors_deps = use_extension("//misc/bazel/3rdparty:tree_sitter_extractors_extension.bzl", "r")
use_repo(
tree_sitter_extractors_deps,
"vendor__anyhow-1.0.95",
"vendor__argfile-0.2.1",
"vendor__chrono-0.4.39",
"vendor__clap-4.5.26",
"vendor__dunce-1.0.5",
"vendor__either-1.13.0",
"vendor__encoding-0.2.33",
"vendor__figment-0.10.19",
"vendor__flate2-1.0.35",
"vendor__glob-0.3.2",
"vendor__globset-0.4.15",
"vendor__itertools-0.14.0",
"vendor__lazy_static-1.5.0",
"vendor__log-0.4.22",
"vendor__mustache-0.9.0",
"vendor__num-traits-0.2.19",
"vendor__num_cpus-1.16.0",
"vendor__proc-macro2-1.0.93",
"vendor__quote-1.0.38",
"vendor__ra_ap_base_db-0.0.258",
"vendor__ra_ap_cfg-0.0.258",
"vendor__ra_ap_hir-0.0.258",
"vendor__ra_ap_hir_def-0.0.258",
"vendor__ra_ap_hir_expand-0.0.258",
"vendor__ra_ap_ide_db-0.0.258",
"vendor__ra_ap_intern-0.0.258",
"vendor__ra_ap_load-cargo-0.0.258",
"vendor__ra_ap_parser-0.0.258",
"vendor__ra_ap_paths-0.0.258",
"vendor__ra_ap_project_model-0.0.258",
"vendor__ra_ap_span-0.0.258",
"vendor__ra_ap_stdx-0.0.258",
"vendor__ra_ap_syntax-0.0.258",
"vendor__ra_ap_vfs-0.0.258",
"vendor__rand-0.8.5",
"vendor__rayon-1.10.0",
"vendor__regex-1.11.1",
"vendor__serde-1.0.217",
"vendor__serde_json-1.0.135",
"vendor__serde_with-3.12.0",
"vendor__stderrlog-0.6.0",
"vendor__syn-2.0.96",
"vendor__toml-0.8.19",
"vendor__tracing-0.1.41",
"vendor__tracing-subscriber-0.3.19",
"vendor__tree-sitter-0.24.6",
"vendor__tree-sitter-embedded-template-0.23.2",
"vendor__tree-sitter-json-0.24.8",
"vendor__tree-sitter-ql-0.23.1",
"vendor__tree-sitter-ruby-0.23.1",
"vendor__triomphe-0.1.14",
"vendor__ungrammar-1.16.1",
)
http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
# rust-analyzer sources needed by the rust ast-generator (see `rust/ast-generator/README.md`)
RUST_ANALYZER_SRC_TAG = "2025-01-07"
http_archive(
name = "rust-analyzer-src",
build_file = "//rust/ast-generator:BUILD.rust-analyzer-src.bazel",
integrity = "sha256-eo8mIaUafZL8LOM65bDIIIXw1rNQ/P/x5RK/XUtgo5g=",
patch_args = ["-p1"],
patches = [
"//rust/ast-generator:patches/rust-analyzer.patch",
],
strip_prefix = "rust-analyzer-%s" % RUST_ANALYZER_SRC_TAG,
url = "https://github.com/rust-lang/rust-analyzer/archive/refs/tags/%s.tar.gz" % RUST_ANALYZER_SRC_TAG,
)
dotnet = use_extension("@rules_dotnet//dotnet:extensions.bzl", "dotnet")
dotnet.toolchain(dotnet_version = "9.0.100")
use_repo(dotnet, "dotnet_toolchains")
register_toolchains("@dotnet_toolchains//:all")
csharp_main_extension = use_extension("//csharp:paket.main_extension.bzl", "main_extension")
use_repo(csharp_main_extension, "paket.main")
pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
pip.parse(
hub_name = "codegen_deps",
python_version = "3.11",
requirements_lock = "//misc/codegen:requirements_lock.txt",
)
use_repo(pip, "codegen_deps")
swift_deps = use_extension("//swift/third_party:load.bzl", "swift_deps")
# following list can be kept in sync with `bazel mod tidy`
use_repo(
swift_deps,
"binlog",
"picosha2",
"swift-prebuilt-linux",
"swift-prebuilt-linux-download-only",
"swift-prebuilt-macos",
"swift-prebuilt-macos-download-only",
"swift-resource-dir-linux",
"swift-resource-dir-macos",
)
node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node")
node.toolchain(
name = "nodejs",
node_urls = [
"https://nodejs.org/dist/v{version}/{filename}",
"https://mirrors.dotsrc.org/nodejs/release/v{version}/{filename}",
],
node_version = "18.15.0",
)
use_repo(node, "nodejs", "nodejs_toolchains")
kotlin_extractor_deps = use_extension("//java/kotlin-extractor:deps.bzl", "kotlin_extractor_deps")
# following list can be kept in sync by running `bazel mod tidy` in `codeql`
use_repo(
kotlin_extractor_deps,
"codeql_kotlin_defaults",
"codeql_kotlin_embeddable",
"kotlin-compiler-1.5.0",
"kotlin-compiler-1.5.10",
"kotlin-compiler-1.5.20",
"kotlin-compiler-1.5.30",
"kotlin-compiler-1.6.0",
"kotlin-compiler-1.6.20",
"kotlin-compiler-1.7.0",
"kotlin-compiler-1.7.20",
"kotlin-compiler-1.8.0",
"kotlin-compiler-1.9.0-Beta",
"kotlin-compiler-1.9.20-Beta",
"kotlin-compiler-2.0.0-RC1",
"kotlin-compiler-2.0.20-Beta2",
"kotlin-compiler-2.1.0-Beta1",
"kotlin-compiler-embeddable-1.5.0",
"kotlin-compiler-embeddable-1.5.10",
"kotlin-compiler-embeddable-1.5.20",
"kotlin-compiler-embeddable-1.5.30",
"kotlin-compiler-embeddable-1.6.0",
"kotlin-compiler-embeddable-1.6.20",
"kotlin-compiler-embeddable-1.7.0",
"kotlin-compiler-embeddable-1.7.20",
"kotlin-compiler-embeddable-1.8.0",
"kotlin-compiler-embeddable-1.9.0-Beta",
"kotlin-compiler-embeddable-1.9.20-Beta",
"kotlin-compiler-embeddable-2.0.0-RC1",
"kotlin-compiler-embeddable-2.0.20-Beta2",
"kotlin-compiler-embeddable-2.1.0-Beta1",
"kotlin-stdlib-1.5.0",
"kotlin-stdlib-1.5.10",
"kotlin-stdlib-1.5.20",
"kotlin-stdlib-1.5.30",
"kotlin-stdlib-1.6.0",
"kotlin-stdlib-1.6.20",
"kotlin-stdlib-1.7.0",
"kotlin-stdlib-1.7.20",
"kotlin-stdlib-1.8.0",
"kotlin-stdlib-1.9.0-Beta",
"kotlin-stdlib-1.9.20-Beta",
"kotlin-stdlib-2.0.0-RC1",
"kotlin-stdlib-2.0.20-Beta2",
"kotlin-stdlib-2.1.0-Beta1",
)
go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk")
go_sdk.download(version = "1.23.1")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//go/extractor:go.mod")
use_repo(go_deps, "org_golang_x_mod", "org_golang_x_tools")
lfs_files = use_repo_rule("//misc/bazel:lfs.bzl", "lfs_files")
lfs_files(
name = "ripunzip-linux",
srcs = ["//misc/ripunzip:ripunzip-linux"],
executable = True,
)
lfs_files(
name = "ripunzip-windows",
srcs = ["//misc/ripunzip:ripunzip-windows.exe"],
executable = True,
)
lfs_files(
name = "ripunzip-macos",
srcs = ["//misc/ripunzip:ripunzip-macos"],
executable = True,
)
register_toolchains(
"@nodejs_toolchains//:all",
)

View File

@@ -4,7 +4,7 @@ This open source repository contains the standard CodeQL libraries and queries t
## How do I learn CodeQL and run queries?
There is extensive documentation about the [CodeQL language](https://codeql.github.com/docs/), writing CodeQL using the [CodeQL extension for Visual Studio Code](https://docs.github.com/en/code-security/codeql-for-vs-code/) and using the [CodeQL CLI](https://docs.github.com/en/code-security/codeql-cli).
There is [extensive documentation](https://codeql.github.com/docs/) on getting started with writing CodeQL using the [CodeQL extension for Visual Studio Code](https://codeql.github.com/docs/codeql-for-visual-studio-code/) and the [CodeQL CLI](https://codeql.github.com/docs/codeql-cli/).
## Contributing

12
WORKSPACE.bazel Normal file
View File

@@ -0,0 +1,12 @@
# Please notice that any bazel targets and definitions in this repository are currently experimental
# and for internal use only.
workspace(name = "codeql")
load("//misc/bazel:workspace.bzl", "codeql_workspace")
codeql_workspace()
load("//misc/bazel:workspace_deps.bzl", "codeql_workspace_deps")
codeql_workspace_deps()

View File

@@ -1,9 +0,0 @@
load("//misc/bazel:pkg.bzl", "codeql_pack")
package(default_visibility = ["//visibility:public"])
codeql_pack(
name = "actions",
srcs = ["//actions/extractor"],
experimental = True,
)

View File

@@ -1,10 +0,0 @@
load("//misc/bazel:pkg.bzl", "codeql_pkg_files", "strip_prefix")
codeql_pkg_files(
name = "extractor",
srcs = [
"codeql-extractor.yml",
] + glob(["tools/**"]),
strip_prefix = strip_prefix.from_pkg(),
visibility = ["//actions:__pkg__"],
)

View File

@@ -1,44 +0,0 @@
name: "actions"
aliases: []
display_name: "GitHub Actions"
version: 0.0.1
column_kind: "utf16"
unicode_newlines: true
build_modes:
- none
file_coverage_languages: []
github_api_languages: []
scc_languages: []
file_types:
- name: workflow
display_name: GitHub Actions workflow files
extensions:
- .yml
- .yaml
forwarded_extractor_name: javascript
options:
trap:
title: TRAP options
description: Options about how the extractor handles TRAP files
type: object
visibility: 3
properties:
cache:
title: TRAP cache options
description: Options about how the extractor handles its TRAP cache
type: object
properties:
dir:
title: TRAP cache directory
description: The directory of the TRAP cache to use
type: string
bound:
title: TRAP cache bound
description: A soft limit (in MB) on the size of the TRAP cache
type: string
pattern: "[0-9]+"
write:
title: TRAP cache writeable
description: Whether to write to the TRAP cache as well as reading it
type: string
pattern: "(true|TRUE|false|FALSE)"

View File

@@ -1,40 +0,0 @@
if (($null -ne $env:LGTM_INDEX_INCLUDE) -or ($null -ne $env:LGTM_INDEX_EXCLUDE) -or ($null -ne $env:LGTM_INDEX_FILTERS)) {
Write-Output 'Path filters set. Passing them through to the JavaScript extractor.'
} else {
Write-Output 'No path filters set. Using the default filters.'
$DefaultPathFilters = @(
'exclude:**/*',
'include:.github/workflows/**/*.yml',
'include:.github/workflows/**/*.yaml',
'include:**/action.yml',
'include:**/action.yaml'
)
$env:LGTM_INDEX_FILTERS = $DefaultPathFilters -join "`n"
}
# Find the JavaScript extractor directory via `codeql resolve extractor`.
$CodeQL = Join-Path $env:CODEQL_DIST 'codeql.exe'
$env:CODEQL_EXTRACTOR_JAVASCRIPT_ROOT = &$CodeQL resolve extractor --language javascript
if ($LASTEXITCODE -ne 0) {
throw 'Failed to resolve JavaScript extractor.'
}
Write-Output "Found JavaScript extractor at '${env:CODEQL_EXTRACTOR_JAVASCRIPT_ROOT}'."
# Run the JavaScript autobuilder.
$JavaScriptAutoBuild = Join-Path $env:CODEQL_EXTRACTOR_JAVASCRIPT_ROOT 'tools\autobuild.cmd'
Write-Output "Running JavaScript autobuilder at '${JavaScriptAutoBuild}'."
# Copy the values of the Actions extractor environment variables to the JavaScript extractor environment variables.
$env:CODEQL_EXTRACTOR_JAVASCRIPT_DIAGNOSTIC_DIR = $env:CODEQL_EXTRACTOR_ACTIONS_DIAGNOSTIC_DIR
$env:CODEQL_EXTRACTOR_JAVASCRIPT_LOG_DIR = $env:CODEQL_EXTRACTOR_ACTIONS_LOG_DIR
$env:CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR = $env:CODEQL_EXTRACTOR_ACTIONS_SCRATCH_DIR
$env:CODEQL_EXTRACTOR_JAVASCRIPT_SOURCE_ARCHIVE_DIR = $env:CODEQL_EXTRACTOR_ACTIONS_SOURCE_ARCHIVE_DIR
$env:CODEQL_EXTRACTOR_JAVASCRIPT_TRAP_DIR = $env:CODEQL_EXTRACTOR_ACTIONS_TRAP_DIR
$env:CODEQL_EXTRACTOR_JAVASCRIPT_WIP_DATABASE = $env:CODEQL_EXTRACTOR_ACTIONS_WIP_DATABASE
&$JavaScriptAutoBuild
if ($LASTEXITCODE -ne 0) {
throw "JavaScript autobuilder failed."
}

View File

@@ -1,3 +0,0 @@
@echo off
rem All of the work is done in the PowerShell script
powershell.exe %~dp0autobuild-impl.ps1

View File

@@ -1,39 +0,0 @@
#!/bin/sh
set -eu
DEFAULT_PATH_FILTERS=$(cat << END
exclude:**/*
include:.github/workflows/**/*.yml
include:.github/workflows/**/*.yaml
include:**/action.yml
include:**/action.yaml
END
)
if [ -n "${LGTM_INDEX_INCLUDE:-}" ] || [ -n "${LGTM_INDEX_EXCLUDE:-}" ] || [ -n "${LGTM_INDEX_FILTERS:-}" ] ; then
echo "Path filters set. Passing them through to the JavaScript extractor."
else
echo "No path filters set. Using the default filters."
LGTM_INDEX_FILTERS="${DEFAULT_PATH_FILTERS}"
export LGTM_INDEX_FILTERS
fi
# Find the JavaScript extractor directory via `codeql resolve extractor`.
CODEQL_EXTRACTOR_JAVASCRIPT_ROOT="$($CODEQL_DIST/codeql resolve extractor --language javascript)"
export CODEQL_EXTRACTOR_JAVASCRIPT_ROOT
echo "Found JavaScript extractor at '${CODEQL_EXTRACTOR_JAVASCRIPT_ROOT}'."
# Run the JavaScript autobuilder
JAVASCRIPT_AUTO_BUILD="${CODEQL_EXTRACTOR_JAVASCRIPT_ROOT}/tools/autobuild.sh"
echo "Running JavaScript autobuilder at '${JAVASCRIPT_AUTO_BUILD}'."
# Copy the values of the Actions extractor environment variables to the JavaScript extractor environment variables.
env CODEQL_EXTRACTOR_JAVASCRIPT_DIAGNOSTIC_DIR="${CODEQL_EXTRACTOR_ACTIONS_DIAGNOSTIC_DIR}" \
CODEQL_EXTRACTOR_JAVASCRIPT_LOG_DIR="${CODEQL_EXTRACTOR_ACTIONS_LOG_DIR}" \
CODEQL_EXTRACTOR_JAVASCRIPT_SCRATCH_DIR="${CODEQL_EXTRACTOR_ACTIONS_SCRATCH_DIR}" \
CODEQL_EXTRACTOR_JAVASCRIPT_SOURCE_ARCHIVE_DIR="${CODEQL_EXTRACTOR_ACTIONS_SOURCE_ARCHIVE_DIR}" \
CODEQL_EXTRACTOR_JAVASCRIPT_TRAP_DIR="${CODEQL_EXTRACTOR_ACTIONS_TRAP_DIR}" \
CODEQL_EXTRACTOR_JAVASCRIPT_WIP_DATABASE="${CODEQL_EXTRACTOR_ACTIONS_WIP_DATABASE}" \
${JAVASCRIPT_AUTO_BUILD}

View File

@@ -1,9 +0,0 @@
## 0.4.1
No user-facing changes.
## 0.4.0
### New Features
* Initial public preview release

View File

@@ -1 +0,0 @@
import codeql.actions.Ast

View File

@@ -1,5 +0,0 @@
## 0.4.0
### New Features
* Initial public preview release

View File

@@ -1,3 +0,0 @@
## 0.4.1
No user-facing changes.

View File

@@ -1,4 +0,0 @@
---
lockVersion: 1.0.0
dependencies: {}
compiled: false

View File

@@ -1,2 +0,0 @@
---
lastReleaseVersion: 0.4.1

View File

@@ -1,98 +0,0 @@
/** Provides classes for working with locations. */
import files.FileSystem
import codeql.actions.ast.internal.Ast
bindingset[loc]
pragma[inline_late]
private string locationToString(Location loc) {
exists(string filepath, int startline, int startcolumn, int endline, int endcolumn |
loc.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn) and
result = filepath + "@" + startline + ":" + startcolumn + ":" + endline + ":" + endcolumn
)
}
newtype TLocation =
TBaseLocation(string filepath, int startline, int startcolumn, int endline, int endcolumn) {
exists(File file |
file.getAbsolutePath() = filepath and
locations_default(_, file, startline, startcolumn, endline, endcolumn)
)
or
exists(ExpressionImpl e |
e.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
)
or
filepath = "" and startline = 0 and startcolumn = 0 and endline = 0 and endcolumn = 0
}
/**
* A location as given by a file, a start line, a start column,
* an end line, and an end column.
*
* For more information about locations see [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
class Location extends TLocation, TBaseLocation {
string filepath;
int startline;
int startcolumn;
int endline;
int endcolumn;
Location() { this = TBaseLocation(filepath, startline, startcolumn, endline, endcolumn) }
/** Gets the file for this location. */
File getFile() {
exists(File file |
file.getAbsolutePath() = filepath and
result = file
)
}
/** Gets the 1-based line number (inclusive) where this location starts. */
int getStartLine() { result = startline }
/** Gets the 1-based column number (inclusive) where this location starts. */
int getStartColumn() { result = startcolumn }
/** Gets the 1-based line number (inclusive) where this.getLocationDefault() location ends. */
int getEndLine() { result = endline }
/** Gets the 1-based column number (inclusive) where this.getLocationDefault() location ends. */
int getEndColumn() { result = endcolumn }
/** Gets the number of lines covered by this location. */
int getNumLines() { result = endline - startline + 1 }
/** Gets a textual representation of this element. */
pragma[inline]
string toString() { result = locationToString(this) }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Providing locations in CodeQL queries](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(string p, int sl, int sc, int el, int ec) {
p = filepath and
sl = startline and
sc = startcolumn and
el = endline and
ec = endcolumn
}
/** Holds if this location starts strictly before the specified location. */
pragma[inline]
predicate strictlyBefore(Location other) {
this.getStartLine() < other.getStartLine()
or
this.getStartLine() = other.getStartLine() and this.getStartColumn() < other.getStartColumn()
}
}
/** An entity representing an empty location. */
class EmptyLocation extends Location {
EmptyLocation() { this.hasLocationInfo("", 0, 0, 0, 0) }
}

View File

@@ -1,400 +0,0 @@
private import codeql.actions.ast.internal.Ast
private import codeql.Locations
import codeql.actions.Helper
class AstNode instanceof AstNodeImpl {
AstNode getAChildNode() { result = super.getAChildNode() }
AstNode getParentNode() { result = super.getParentNode() }
string getAPrimaryQlClass() { result = super.getAPrimaryQlClass() }
Location getLocation() { result = super.getLocation() }
string toString() { result = super.toString() }
Step getEnclosingStep() { result = super.getEnclosingStep() }
Job getEnclosingJob() { result = super.getEnclosingJob() }
Event getATriggerEvent() { result = super.getATriggerEvent() }
Workflow getEnclosingWorkflow() { result = super.getEnclosingWorkflow() }
CompositeAction getEnclosingCompositeAction() { result = super.getEnclosingCompositeAction() }
Expression getInScopeEnvVarExpr(string name) { result = super.getInScopeEnvVarExpr(name) }
ScalarValue getInScopeDefaultValue(string name, string prop) {
result = super.getInScopeDefaultValue(name, prop)
}
}
class ScalarValue extends AstNode instanceof ScalarValueImpl {
string getValue() { result = super.getValue() }
}
class Expression extends AstNode instanceof ExpressionImpl {
string expression;
string rawExpression;
Expression() {
expression = this.getExpression() and
rawExpression = this.getRawExpression()
}
string getExpression() { result = expression }
string getRawExpression() { result = rawExpression }
string getNormalizedExpression() { result = normalizeExpr(expression) }
}
/** A common class for `env` in workflow, job or step. */
abstract class Env extends AstNode instanceof EnvImpl {
/** Gets an environment variable value given its name. */
ScalarValueImpl getEnvVarValue(string name) { result = super.getEnvVarValue(name) }
/** Gets an environment variable value. */
ScalarValueImpl getAnEnvVarValue() { result = super.getAnEnvVarValue() }
/** Gets an environment variable expressin given its name. */
ExpressionImpl getEnvVarExpr(string name) { result = super.getEnvVarExpr(name) }
/** Gets an environment variable expression. */
ExpressionImpl getAnEnvVarExpr() { result = super.getAnEnvVarExpr() }
}
/**
* A custom composite action. This is a mapping at the top level of an Actions YAML action file.
* See https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions.
*/
class CompositeAction extends AstNode instanceof CompositeActionImpl {
Runs getRuns() { result = super.getRuns() }
Outputs getOutputs() { result = super.getOutputs() }
Expression getAnOutputExpr() { result = super.getAnOutputExpr() }
Expression getOutputExpr(string outputName) { result = super.getOutputExpr(outputName) }
Input getAnInput() { result = super.getAnInput() }
Input getInput(string inputName) { result = super.getInput(inputName) }
LocalJob getACallerJob() { result = super.getACallerJob() }
UsesStep getACallerStep() { result = super.getACallerStep() }
predicate isPrivileged() { super.isPrivileged() }
}
/**
* An Actions workflow. This is a mapping at the top level of an Actions YAML workflow file.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions.
*/
class Workflow extends AstNode instanceof WorkflowImpl {
Env getEnv() { result = super.getEnv() }
string getName() { result = super.getName() }
Job getAJob() { result = super.getAJob() }
Job getJob(string jobId) { result = super.getJob(jobId) }
Permissions getPermissions() { result = super.getPermissions() }
Strategy getStrategy() { result = super.getStrategy() }
On getOn() { result = super.getOn() }
}
class ReusableWorkflow extends Workflow instanceof ReusableWorkflowImpl {
Outputs getOutputs() { result = super.getOutputs() }
Expression getAnOutputExpr() { result = super.getAnOutputExpr() }
Expression getOutputExpr(string outputName) { result = super.getOutputExpr(outputName) }
Input getAnInput() { result = super.getAnInput() }
Input getInput(string inputName) { result = super.getInput(inputName) }
ExternalJob getACaller() { result = super.getACaller() }
}
class Input extends AstNode instanceof InputImpl { }
class Default extends AstNode instanceof DefaultsImpl {
ScalarValue getValue(string name, string prop) { result = super.getValue(name, prop) }
}
class Outputs extends AstNode instanceof OutputsImpl {
Expression getAnOutputExpr() { result = super.getAnOutputExpr() }
Expression getOutputExpr(string outputName) { result = super.getOutputExpr(outputName) }
override string toString() { result = "Job outputs node" }
}
class Permissions extends AstNode instanceof PermissionsImpl {
bindingset[perm]
string getPermission(string perm) { result = super.getPermission(perm) }
string getAPermission() { result = super.getAPermission() }
}
class Strategy extends AstNode instanceof StrategyImpl {
Expression getMatrixVarExpr(string varName) { result = super.getMatrixVarExpr(varName) }
Expression getAMatrixVarExpr() { result = super.getAMatrixVarExpr() }
}
/**
* https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds
*/
class Needs extends AstNode instanceof NeedsImpl {
Job getANeededJob() { result = super.getANeededJob() }
}
class On extends AstNode instanceof OnImpl {
Event getAnEvent() { result = super.getAnEvent() }
}
class Event extends AstNode instanceof EventImpl {
string getName() { result = super.getName() }
string getAnActivityType() { result = super.getAnActivityType() }
string getAPropertyValue(string prop) { result = super.getAPropertyValue(prop) }
predicate hasProperty(string prop) { super.hasProperty(prop) }
predicate isExternallyTriggerable() { super.isExternallyTriggerable() }
predicate isPrivileged() { super.isPrivileged() }
}
/**
* An Actions job within a workflow.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
*/
abstract class Job extends AstNode instanceof JobImpl {
string getId() { result = super.getId() }
Workflow getWorkflow() { result = super.getWorkflow() }
Job getANeededJob() { result = super.getANeededJob() }
Outputs getOutputs() { result = super.getOutputs() }
Expression getAnOutputExpr() { result = super.getAnOutputExpr() }
Expression getOutputExpr(string outputName) { result = super.getOutputExpr(outputName) }
Env getEnv() { result = super.getEnv() }
If getIf() { result = super.getIf() }
Environment getEnvironment() { result = super.getEnvironment() }
Permissions getPermissions() { result = super.getPermissions() }
Strategy getStrategy() { result = super.getStrategy() }
string getARunsOnLabel() { result = super.getARunsOnLabel() }
predicate isPrivileged() { super.isPrivileged() }
predicate isPrivilegedExternallyTriggerable(Event event) {
super.isPrivilegedExternallyTriggerable(event)
}
}
abstract class StepsContainer extends AstNode instanceof StepsContainerImpl {
Step getAStep() { result = super.getAStep() }
Step getStep(int i) { result = super.getStep(i) }
}
/**
* An `runs` mapping in a custom composite action YAML.
* See https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs
*/
class Runs extends StepsContainer instanceof RunsImpl {
CompositeAction getAction() { result = super.getAction() }
}
/**
* An Actions job within a workflow which is composed of steps.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobs.
*/
class LocalJob extends Job, StepsContainer instanceof LocalJobImpl { }
/**
* A step within an Actions job.
* See https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idsteps.
*/
class Step extends AstNode instanceof StepImpl {
string getId() { result = super.getId() }
Env getEnv() { result = super.getEnv() }
If getIf() { result = super.getIf() }
StepsContainer getContainer() { result = super.getContainer() }
Step getNextStep() { result = super.getNextStep() }
Step getAFollowingStep() { result = super.getAFollowingStep() }
}
/**
* An If node representing a conditional statement.
*/
class If extends AstNode instanceof IfImpl {
string getCondition() { result = super.getCondition() }
Expression getConditionExpr() { result = super.getConditionExpr() }
string getConditionStyle() { result = super.getConditionStyle() }
}
/**
* An Environemnt node representing a deployment environment.
*/
class Environment extends AstNode instanceof EnvironmentImpl {
string getName() { result = super.getName() }
Expression getNameExpr() { result = super.getNameExpr() }
}
abstract class Uses extends AstNode instanceof UsesImpl {
string getCallee() { result = super.getCallee() }
ScalarValue getCalleeNode() { result = super.getCalleeNode() }
string getVersion() { result = super.getVersion() }
int getMajorVersion() { result = super.getMajorVersion() }
string getArgument(string argName) { result = super.getArgument(argName) }
Expression getArgumentExpr(string argName) { result = super.getArgumentExpr(argName) }
}
class UsesStep extends Step, Uses instanceof UsesStepImpl { }
class ExternalJob extends Job, Uses instanceof ExternalJobImpl { }
/**
* A `run` field within an Actions job step, which runs command-line programs using an operating system shell.
* See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun.
*/
class Run extends Step instanceof RunImpl {
ShellScript getScript() { result = super.getScript() }
Expression getAnScriptExpr() { result = super.getAnScriptExpr() }
string getWorkingDirectory() { result = super.getWorkingDirectory() }
string getShell() { result = super.getShell() }
}
class ShellScript extends ScalarValueImpl instanceof ShellScriptImpl {
string getRawScript() { result = super.getRawScript() }
string getStmt(int i) { result = super.getStmt(i) }
string getAStmt() { result = super.getAStmt() }
string getCommand(int i) { result = super.getCommand(i) }
string getACommand() { result = super.getACommand() }
string getFileReadCommand(int i) { result = super.getFileReadCommand(i) }
string getAFileReadCommand() { result = super.getAFileReadCommand() }
predicate getAssignment(int i, string name, string data) { super.getAssignment(i, name, data) }
predicate getAnAssignment(string name, string data) { super.getAnAssignment(name, data) }
predicate getAWriteToGitHubEnv(string name, string data) {
super.getAWriteToGitHubEnv(name, data)
}
predicate getAWriteToGitHubOutput(string name, string data) {
super.getAWriteToGitHubOutput(name, data)
}
predicate getAWriteToGitHubPath(string data) { super.getAWriteToGitHubPath(data) }
predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) {
super.getAnEnvReachingGitHubOutputWrite(var, output_field)
}
predicate getACmdReachingGitHubOutputWrite(string cmd, string output_field) {
super.getACmdReachingGitHubOutputWrite(cmd, output_field)
}
predicate getAnEnvReachingGitHubEnvWrite(string var, string output_field) {
super.getAnEnvReachingGitHubEnvWrite(var, output_field)
}
predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field) {
super.getACmdReachingGitHubEnvWrite(cmd, output_field)
}
predicate getAnEnvReachingGitHubPathWrite(string var) {
super.getAnEnvReachingGitHubPathWrite(var)
}
predicate getACmdReachingGitHubPathWrite(string cmd) { super.getACmdReachingGitHubPathWrite(cmd) }
predicate getAnEnvReachingArgumentInjectionSink(string var, string command, string argument) {
super.getAnEnvReachingArgumentInjectionSink(var, command, argument)
}
predicate getACmdReachingArgumentInjectionSink(string cmd, string command, string argument) {
super.getACmdReachingArgumentInjectionSink(cmd, command, argument)
}
predicate fileToGitHubEnv(string path) { super.fileToGitHubEnv(path) }
predicate fileToGitHubOutput(string path) { super.fileToGitHubOutput(path) }
predicate fileToGitHubPath(string path) { super.fileToGitHubPath(path) }
}
abstract class SimpleReferenceExpression extends AstNode instanceof SimpleReferenceExpressionImpl {
string getFieldName() { result = super.getFieldName() }
AstNode getTarget() { result = super.getTarget() }
}
class JsonReferenceExpression extends AstNode instanceof JsonReferenceExpressionImpl {
string getAccessPath() { result = super.getAccessPath() }
string getInnerExpression() { result = super.getInnerExpression() }
}
class GitHubExpression extends SimpleReferenceExpression instanceof GitHubExpressionImpl { }
class SecretsExpression extends SimpleReferenceExpression instanceof SecretsExpressionImpl { }
class StepsExpression extends SimpleReferenceExpression instanceof StepsExpressionImpl {
string getStepId() { result = super.getStepId() }
}
class NeedsExpression extends SimpleReferenceExpression instanceof NeedsExpressionImpl {
string getNeededJobId() { result = super.getNeededJobId() }
}
class JobsExpression extends SimpleReferenceExpression instanceof JobsExpressionImpl { }
class InputsExpression extends SimpleReferenceExpression instanceof InputsExpressionImpl { }
class EnvExpression extends SimpleReferenceExpression instanceof EnvExpressionImpl { }
class MatrixExpression extends SimpleReferenceExpression instanceof MatrixExpressionImpl { }

View File

@@ -1,722 +0,0 @@
private import codeql.actions.Ast
class BashShellScript extends ShellScript {
BashShellScript() {
exists(Run run |
this = run.getScript() and
run.getShell().matches(["bash%", "sh"])
)
}
private string lineProducer(int i) {
result = this.getRawScript().regexpReplaceAll("\\\\\\s*\n", "").splitAt("\n", i)
}
private predicate cmdSubstitutionReplacement(string cmdSubs, string id, int k) {
exists(string line | line = this.lineProducer(k) |
exists(int i, int j |
cmdSubs =
// $() cmd substitution
line.regexpFind("\\$\\((?:[^()]+|\\((?:[^()]+|\\([^()]*\\))*\\))*\\)", i, j)
.regexpReplaceAll("^\\$\\(", "")
.regexpReplaceAll("\\)$", "") and
id = "cmdsubs:" + k + ":" + i + ":" + j
)
or
exists(int i, int j |
// `...` cmd substitution
cmdSubs =
line.regexpFind("\\`[^\\`]+\\`", i, j)
.regexpReplaceAll("^\\`", "")
.regexpReplaceAll("\\`$", "") and
id = "cmd:" + k + ":" + i + ":" + j
)
)
}
private predicate rankedCmdSubstitutionReplacements(int i, string old, string new) {
old = rank[i](string old2 | this.cmdSubstitutionReplacement(old2, _, _) | old2) and
this.cmdSubstitutionReplacement(old, new, _)
}
private predicate doReplaceCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.lineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doReplaceCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(target, replacement)
)
}
private string cmdSubstitutedLineProducer(int i) {
// script lines where any command substitution has been replaced with a unique placeholder
result =
max(int round, string new |
this.doReplaceCmdSubstitutions(i, round, _, new)
|
new order by round
)
or
this.cmdSubstitutionReplacement(result, _, i)
}
private predicate quotedStringReplacement(string quotedStr, string id) {
exists(string line, int k | line = this.cmdSubstitutedLineProducer(k) |
exists(int i, int j |
// double quoted string
quotedStr = line.regexpFind("\"((?:[^\"\\\\]|\\\\.)*)\"", i, j) and
id =
"qstr:" + k + ":" + i + ":" + j + ":" + quotedStr.length() + ":" +
quotedStr.regexpReplaceAll("[^a-zA-Z0-9]", "")
)
or
exists(int i, int j |
// single quoted string
quotedStr = line.regexpFind("'((?:\\\\.|[^'\\\\])*)'", i, j) and
id =
"qstr:" + k + ":" + i + ":" + j + ":" + quotedStr.length() + ":" +
quotedStr.regexpReplaceAll("[^a-zA-Z0-9]", "")
)
)
}
private predicate rankedQuotedStringReplacements(int i, string old, string new) {
old = rank[i](string old2 | this.quotedStringReplacement(old2, _) | old2) and
this.quotedStringReplacement(old, new)
}
private predicate doReplaceQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.cmdSubstitutedLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doReplaceQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(target, replacement)
)
}
private string quotedStringLineProducer(int i) {
result =
max(int round, string new | this.doReplaceQuotedStrings(i, round, _, new) | new order by round)
}
private string stmtProducer(int i) {
result = this.quotedStringLineProducer(i).splitAt(Bash::splitSeparator()).trim() and
// when splitting the line with a separator that is not present, the result is the original line which may contain other separators
// we only one the split parts that do not contain any of the separators
not result.indexOf(Bash::splitSeparator()) > -1
}
private predicate doStmtRestoreQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.stmtProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doStmtRestoreQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
private string restoredStmtQuotedStringLineProducer(int i) {
result =
max(int round, string new |
this.doStmtRestoreQuotedStrings(i, round, _, new)
|
new order by round
) and
not result.indexOf("qstr:") > -1
}
private predicate doStmtRestoreCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.restoredStmtQuotedStringLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doStmtRestoreCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
override string getStmt(int i) {
result =
max(int round, string new |
this.doStmtRestoreCmdSubstitutions(i, round, _, new)
|
new order by round
) and
not result.indexOf("cmdsubs:") > -1
}
override string getAStmt() { result = this.getStmt(_) }
private string cmdProducer(int i) {
result = this.quotedStringLineProducer(i).splitAt(Bash::separator()).trim() and
// when splitting the line with a separator that is not present, the result is the original line which may contain other separators
// we only one the split parts that do not contain any of the separators
not result.indexOf(Bash::separator()) > -1
}
private predicate doCmdRestoreQuotedStrings(int line, int round, string old, string new) {
round = 0 and
old = this.cmdProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doCmdRestoreQuotedStrings(line, round - 1, old, middle) and
this.rankedQuotedStringReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
private string restoredCmdQuotedStringLineProducer(int i) {
result =
max(int round, string new |
this.doCmdRestoreQuotedStrings(i, round, _, new)
|
new order by round
) and
not result.indexOf("qstr:") > -1
}
private predicate doCmdRestoreCmdSubstitutions(int line, int round, string old, string new) {
round = 0 and
old = this.restoredCmdQuotedStringLineProducer(line) and
new = old
or
round > 0 and
exists(string middle, string target, string replacement |
this.doCmdRestoreCmdSubstitutions(line, round - 1, old, middle) and
this.rankedCmdSubstitutionReplacements(round, target, replacement) and
new = middle.replaceAll(replacement, target)
)
}
string getCmd(int i) {
result =
max(int round, string new |
this.doCmdRestoreCmdSubstitutions(i, round, _, new)
|
new order by round
) and
not result.indexOf("cmdsubs:") > -1
}
string getACmd() { result = this.getCmd(_) }
override string getCommand(int i) {
// remove redirection
result =
this.getCmd(i)
.regexpReplaceAll("(>|>>|2>|2>>|<|<<<)\\s*[\\{\\}\\$\"'_\\-0-9a-zA-Z]+$", "")
.trim() and
// exclude variable declarations
not result.regexpMatch("^[a-zA-Z0-9\\-_]+=") and
// exclude comments
not result.trim().indexOf("#") = 0 and
// exclude the following keywords
not result =
[
"", "for", "in", "do", "done", "if", "then", "else", "elif", "fi", "while", "until", "case",
"esac", "{", "}"
]
}
override string getACommand() { result = this.getCommand(_) }
override string getFileReadCommand(int i) {
result = this.getStmt(i) and
result.matches(Bash::fileReadCommand() + "%")
}
override string getAFileReadCommand() { result = this.getFileReadCommand(_) }
override predicate getAssignment(int i, string name, string data) {
exists(string stmt |
stmt = this.getStmt(i) and
name = stmt.regexpCapture("^([a-zA-Z0-9\\-_]+)=.*", 1) and
data = stmt.regexpCapture("^[a-zA-Z0-9\\-_]+=(.*)", 1)
)
}
override predicate getAnAssignment(string name, string data) { this.getAssignment(_, name, data) }
override predicate getAWriteToGitHubEnv(string name, string data) {
exists(string raw |
Bash::extractFileWrite(this, "GITHUB_ENV", raw) and
Bash::extractVariableAndValue(raw, name, data)
)
}
override predicate getAWriteToGitHubOutput(string name, string data) {
exists(string raw |
Bash::extractFileWrite(this, "GITHUB_OUTPUT", raw) and
Bash::extractVariableAndValue(raw, name, data)
)
}
override predicate getAWriteToGitHubPath(string data) {
Bash::extractFileWrite(this, "GITHUB_PATH", data)
}
override predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) {
Bash::envReachingGitHubFileWrite(this, var, "GITHUB_OUTPUT", output_field)
}
override predicate getACmdReachingGitHubOutputWrite(string cmd, string output_field) {
Bash::cmdReachingGitHubFileWrite(this, cmd, "GITHUB_OUTPUT", output_field)
}
override predicate getAnEnvReachingGitHubEnvWrite(string var, string output_field) {
Bash::envReachingGitHubFileWrite(this, var, "GITHUB_ENV", output_field)
}
override predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field) {
Bash::cmdReachingGitHubFileWrite(this, cmd, "GITHUB_ENV", output_field)
}
override predicate getAnEnvReachingGitHubPathWrite(string var) {
Bash::envReachingGitHubFileWrite(this, var, "GITHUB_PATH", _)
}
override predicate getACmdReachingGitHubPathWrite(string cmd) {
Bash::cmdReachingGitHubFileWrite(this, cmd, "GITHUB_PATH", _)
}
override predicate getAnEnvReachingArgumentInjectionSink(
string var, string command, string argument
) {
Bash::envReachingArgumentInjectionSink(this, var, command, argument)
}
override predicate getACmdReachingArgumentInjectionSink(
string cmd, string command, string argument
) {
Bash::cmdReachingArgumentInjectionSink(this, cmd, command, argument)
}
override predicate fileToGitHubEnv(string path) {
Bash::fileToFileWrite(this, "GITHUB_ENV", path)
}
override predicate fileToGitHubOutput(string path) {
Bash::fileToFileWrite(this, "GITHUB_OUTPUT", path)
}
override predicate fileToGitHubPath(string path) {
Bash::fileToFileWrite(this, "GITHUB_PATH", path)
}
}
module Bash {
string stmtSeparator() { result = ";" }
string commandSeparator() { result = ["&&", "||"] }
string splitSeparator() {
result = stmtSeparator() or
result = commandSeparator()
}
string redirectionSeparator() { result = [">", ">>", "2>", "2>>", ">&", "2>&", "<", "<<<"] }
string pipeSeparator() { result = "|" }
string separator() {
result = stmtSeparator() or
result = commandSeparator() or
result = pipeSeparator()
}
string fileReadCommand() { result = ["<", "cat", "jq", "yq", "tail", "head"] }
/** Checks if expr is a bash command substitution */
bindingset[expr]
predicate isCmdSubstitution(string expr, string cmd) {
exists(string regexp |
// $(cmd)
regexp = "\\$\\(([^)]+)\\)" and
cmd = expr.regexpCapture(regexp, 1)
or
// `cmd`
regexp = "`([^`]+)`" and
cmd = expr.regexpCapture(regexp, 1)
)
}
/** Checks if expr is a bash command substitution */
bindingset[expr]
predicate containsCmdSubstitution(string expr, string cmd) {
exists(string regexp |
// $(cmd)
regexp = ".*\\$\\(([^)]+)\\).*" and
cmd = expr.regexpCapture(regexp, 1).trim()
or
// `cmd`
regexp = ".*`([^`]+)`.*" and
cmd = expr.regexpCapture(regexp, 1).trim()
)
}
/** Checks if expr is a bash parameter expansion */
bindingset[expr]
predicate isParameterExpansion(string expr, string parameter, string operator, string params) {
exists(string regexp |
// $VAR
regexp = "\\$([a-zA-Z_][a-zA-Z0-9_]+)\\b" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${VAR}
regexp = "\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${!VAR}
regexp = "\\$\\{([!#])([a-zA-Z_][a-zA-Z0-9_]*)\\}" and
parameter = expr.regexpCapture(regexp, 2) and
operator = expr.regexpCapture(regexp, 1) and
params = ""
or
// ${VAR<OP><PARAMS>}, ...
regexp = "\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)([#%/:^,\\-+]{1,2})?(.*?)\\}" and
parameter = expr.regexpCapture(regexp, 1) and
operator = expr.regexpCapture(regexp, 2) and
params = expr.regexpCapture(regexp, 3)
)
}
bindingset[expr]
predicate containsParameterExpansion(string expr, string parameter, string operator, string params) {
exists(string regexp |
// $VAR
regexp = ".*\\$([a-zA-Z_][a-zA-Z0-9_]+)\\b.*" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${VAR}
regexp = ".*\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)\\}.*" and
parameter = expr.regexpCapture(regexp, 1) and
operator = "" and
params = ""
or
// ${!VAR}
regexp = ".*\\$\\{([!#])([a-zA-Z_][a-zA-Z0-9_]*)\\}.*" and
parameter = expr.regexpCapture(regexp, 2) and
operator = expr.regexpCapture(regexp, 1) and
params = ""
or
// ${VAR<OP><PARAMS>}, ...
regexp = ".*\\$\\{([a-zA-Z_][a-zA-Z0-9_]*)([#%/:^,\\-+]{1,2})?(.*?)\\}.*" and
parameter = expr.regexpCapture(regexp, 1) and
operator = expr.regexpCapture(regexp, 2) and
params = expr.regexpCapture(regexp, 3)
)
}
bindingset[raw_content]
predicate extractVariableAndValue(string raw_content, string key, string value) {
exists(string regexp, string content | content = trimQuotes(raw_content) |
regexp = "(?msi).*^([a-zA-Z_][a-zA-Z0-9_]*)\\s*<<\\s*['\"]?(\\S+)['\"]?\\s*\n(.*?)\n\\2\\s*$" and
key = trimQuotes(content.regexpCapture(regexp, 1)) and
value = trimQuotes(content.regexpCapture(regexp, 3))
or
exists(string line |
line = content.splitAt("\n") and
regexp = "(?i)^([a-zA-Z_][a-zA-Z0-9_\\-]*)\\s*=\\s*(.*)$" and
key = trimQuotes(line.regexpCapture(regexp, 1)) and
value = trimQuotes(line.regexpCapture(regexp, 2))
)
)
}
bindingset[script]
predicate singleLineFileWrite(
string script, string cmd, string file, string content, string filters
) {
exists(string regexp |
regexp = "(?i)(echo|printf)\\s*(.*?)\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)" and
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
filters = "" and
content = script.regexpCapture(regexp, 2)
)
}
bindingset[script]
predicate singleLineWorkflowCmd(string script, string cmd, string key, string value) {
exists(string regexp |
regexp = "(?i)(echo|printf)\\s*(['|\"])?::(set-[a-z]+)\\s*name\\s*=\\s*(.*?)::(.*)" and
cmd = script.regexpCapture(regexp, 3) and
key = script.regexpCapture(regexp, 4) and
value = trimQuotes(script.regexpCapture(regexp, 5))
or
regexp = "(?i)(echo|printf)\\s*(['|\"])?::(add-[a-z]+)\\s*::(.*)" and
cmd = script.regexpCapture(regexp, 3) and
key = "" and
value = trimQuotes(script.regexpCapture(regexp, 4))
)
}
bindingset[script]
predicate heredocFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp |
regexp =
"(?msi).*^(cat)\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)\\s*<<\\s*['\"]?(\\S+)['\"]?\\s*\n(.*?)\n\\4\\s*$.*" and
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 4)) and
content = script.regexpCapture(regexp, 6) and
filters = ""
or
regexp =
"(?msi).*^(cat)\\s*(<<|<)\\s*[-]?['\"]?(\\S+)['\"]?\\s*([^>]*)(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+)\\s*\n(.*?)\n\\3\\s*$.*" and
cmd = script.regexpCapture(regexp, 1) and
file = trimQuotes(script.regexpCapture(regexp, 7)) and
filters = script.regexpCapture(regexp, 4) and
content = script.regexpCapture(regexp, 8)
)
}
bindingset[script]
predicate linesFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp, string var_name |
regexp =
"(?msi).*((echo|printf)\\s+['|\"]?(.*?<<(\\S+))['|\"]?\\s*>>\\s*(\\S+)\\s*[\r\n]+)" +
"(((.*?)\\s*>>\\s*\\S+\\s*[\r\n]+)+)" +
"((echo|printf)\\s+['|\"]?(EOF)['|\"]?\\s*>>\\s*\\S+\\s*[\r\n]*).*" and
var_name = trimQuotes(script.regexpCapture(regexp, 3)).regexpReplaceAll("<<\\s*(\\S+)", "") and
content =
var_name + "=$(" +
trimQuotes(script.regexpCapture(regexp, 6))
.regexpReplaceAll(">>.*GITHUB_(ENV|OUTPUT)(})?", "")
.trim() + ")" and
cmd = "echo" and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
filters = ""
)
}
bindingset[script]
predicate blockFileWrite(string script, string cmd, string file, string content, string filters) {
exists(string regexp, string first_line, string var_name |
regexp =
"(?msi).*^\\s*\\{\\s*[\r\n]" +
//
"(.*?)" +
//
"(\\s*\\}\\s*(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*(\\S+))\\s*$.*" and
first_line = script.regexpCapture(regexp, 1).splitAt("\n", 0).trim() and
var_name = first_line.regexpCapture("echo\\s+('|\\\")?(.*)<<.*", 2) and
content = var_name + "=$(" + script.regexpCapture(regexp, 1).splitAt("\n").trim() + ")" and
not content.indexOf("EOF") > 0 and
file = trimQuotes(script.regexpCapture(regexp, 5)) and
cmd = "echo" and
filters = ""
)
}
bindingset[script]
predicate multiLineFileWrite(
string script, string cmd, string file, string content, string filters
) {
heredocFileWrite(script, cmd, file, content, filters)
or
linesFileWrite(script, cmd, file, content, filters)
or
blockFileWrite(script, cmd, file, content, filters)
}
bindingset[file_var]
predicate extractFileWrite(BashShellScript script, string file_var, string content) {
// single line assignment
exists(string file_expr, string raw_content |
isParameterExpansion(file_expr, file_var, _, _) and
singleLineFileWrite(script.getAStmt(), _, file_expr, raw_content, _) and
content = trimQuotes(raw_content)
)
or
// workflow command assignment
exists(string key, string value, string cmd |
(
file_var = "GITHUB_ENV" and
cmd = "set-env" and
content = key + "=" + value
or
file_var = "GITHUB_OUTPUT" and
cmd = "set-output" and
content = key + "=" + value
or
file_var = "GITHUB_PATH" and
cmd = "add-path" and
content = value
) and
singleLineWorkflowCmd(script.getAStmt(), cmd, key, value)
)
or
// multiline assignment
exists(string file_expr, string raw_content |
multiLineFileWrite(script.getRawScript(), _, file_expr, raw_content, _) and
isParameterExpansion(file_expr, file_var, _, _) and
content = trimQuotes(raw_content)
)
}
/** Writes the content of the file specified by `path` into a file pointed to by `file_var` */
predicate fileToFileWrite(BashShellScript script, string file_var, string path) {
exists(string regexp, string stmt, string file_expr |
regexp =
"(?i)(cat)\\s*" + "((?:(?!<<|<<-)[^>\n])+)\\s*" +
"(>>|>|\\s*\\|\\s*tee\\s*(-a|--append)?)\\s*" + "(\\S+)" and
stmt = script.getAStmt() and
file_expr = trimQuotes(stmt.regexpCapture(regexp, 5)) and
path = stmt.regexpCapture(regexp, 2) and
containsParameterExpansion(file_expr, file_var, _, _)
)
}
/**
* Holds if the Run scripts contains an access to an environment variable called `var`
* which value may get appended to the GITHUB_XXX special file
*/
predicate envReachingGitHubFileWrite(
BashShellScript script, string var, string file_var, string field
) {
exists(string file_write_value |
(
file_var = "GITHUB_ENV" and
script.getAWriteToGitHubEnv(field, file_write_value)
or
file_var = "GITHUB_OUTPUT" and
script.getAWriteToGitHubOutput(field, file_write_value)
or
file_var = "GITHUB_PATH" and
field = "PATH" and
script.getAWriteToGitHubPath(file_write_value)
) and
envReachingRunExpr(script, var, file_write_value)
)
}
/**
* Holds if and environment variable is used, directly or indirectly, in a Run's step expression.
* Where the expression is a string captured from the Run's script.
*/
bindingset[expr]
predicate envReachingRunExpr(BashShellScript script, string var, string expr) {
exists(string var2, string value2 |
// VAR2=${VAR:-default} (var2=value2)
// echo "FIELD=${VAR2:-default}" >> $GITHUB_ENV (field, file_write_value)
script.getAnAssignment(var2, value2) and
containsParameterExpansion(value2, var, _, _) and
containsParameterExpansion(expr, var2, _, _)
)
or
// var reaches the file write directly
// echo "FIELD=${VAR:-default}" >> $GITHUB_ENV (field, file_write_value)
containsParameterExpansion(expr, var, _, _)
}
/**
* Holds if the Run scripts contains a command substitution (`cmd`)
* which output may get appended to the GITHUB_XXX special file
*/
predicate cmdReachingGitHubFileWrite(
BashShellScript script, string cmd, string file_var, string field
) {
exists(string file_write_value |
(
file_var = "GITHUB_ENV" and
script.getAWriteToGitHubEnv(field, file_write_value)
or
file_var = "GITHUB_OUTPUT" and
script.getAWriteToGitHubOutput(field, file_write_value)
or
file_var = "GITHUB_PATH" and
field = "PATH" and
script.getAWriteToGitHubPath(file_write_value)
) and
cmdReachingRunExpr(script, cmd, file_write_value)
)
}
predicate envReachingArgumentInjectionSink(
BashShellScript script, string source, string command, string argument
) {
exists(string cmd, string regex, int command_group, int argument_group |
cmd = script.getACommand() and
argumentInjectionSinksDataModel(regex, command_group, argument_group) and
argument = cmd.regexpCapture(regex, argument_group).trim() and
command = cmd.regexpCapture(regex, command_group).trim() and
envReachingRunExpr(script, source, argument)
)
}
predicate cmdReachingArgumentInjectionSink(
BashShellScript script, string source, string command, string argument
) {
exists(string cmd, string regex, int command_group, int argument_group |
cmd = script.getACommand() and
argumentInjectionSinksDataModel(regex, command_group, argument_group) and
argument = cmd.regexpCapture(regex, argument_group).trim() and
command = cmd.regexpCapture(regex, command_group).trim() and
cmdReachingRunExpr(script, source, argument)
)
}
/**
* Holds if a command output is used, directly or indirectly, in a Run's step expression.
* Where the expression is a string captured from the Run's script.
*/
bindingset[expr]
predicate cmdReachingRunExpr(BashShellScript script, string cmd, string expr) {
// cmd output is assigned to a second variable (var2) and var2 reaches the file write
exists(string var2, string value2 |
// VAR2=$(cmd)
// echo "FIELD=${VAR2:-default}" >> $GITHUB_ENV (field, file_write_value)
script.getAnAssignment(var2, value2) and
containsCmdSubstitution(value2, cmd) and
containsParameterExpansion(expr, var2, _, _) and
not varMatchesRegexTest(script, var2, alphaNumericRegex())
)
or
// var reaches the file write directly
// echo "FIELD=$(cmd)" >> $GITHUB_ENV (field, file_write_value)
containsCmdSubstitution(expr, cmd)
}
/**
* Holds if there test command that checks a variable against a regex
* eg: `[[ $VAR =~ ^[a-zA-Z0-9_]+$ ]]`
*/
bindingset[var, regex]
predicate varMatchesRegexTest(BashShellScript script, string var, string regex) {
exists(string lhs, string rhs |
lhs = script.getACommand().regexpCapture(".*\\[\\[\\s*(.*?)\\s*=~\\s*(.*?)\\s*\\]\\].*", 1) and
containsParameterExpansion(lhs, var, _, _) and
rhs = script.getACommand().regexpCapture(".*\\[\\[\\s*(.*?)\\s*=~\\s*(.*?)\\s*\\]\\].*", 2) and
trimQuotes(rhs).regexpMatch(regex)
)
}
/**
* Holds if the given regex is used to match an alphanumeric string
* eg: `^[0-9a-zA-Z]{40}$`, `^[0-9]+$` or `^[a-zA-Z0-9_]+$`
*/
string alphaNumericRegex() { result = "^\\^\\[([09azAZ_-]+)\\](\\+|\\{\\d+\\})\\$$" }
}

View File

@@ -1,6 +0,0 @@
/** Provides classes representing the control flow graph. */
private import codeql.actions.controlflow.internal.Cfg as CfgInternal
import CfgInternal::Completion
import CfgInternal::CfgScope
import CfgInternal::CfgImpl

View File

@@ -1 +0,0 @@
import DataFlow::DataFlow::Consistency

View File

@@ -1,22 +0,0 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) data flow analyses.
*/
import codeql.Locations
module DataFlow {
private import codeql.dataflow.DataFlow
private import codeql.actions.dataflow.internal.DataFlowImplSpecific
import DataFlowMake<Location, ActionsDataFlow>
import codeql.actions.dataflow.internal.DataFlowPublic
// debug
private import codeql.actions.dataflow.internal.TaintTrackingImplSpecific
import codeql.dataflow.internal.DataFlowImplConsistency as DFIC
module ActionsConsistency implements DFIC::InputSig<Location, ActionsDataFlow> { }
module Consistency {
import DFIC::MakeConsistency<Location, ActionsDataFlow, ActionsTaintTracking, ActionsConsistency>
}
}

View File

@@ -1,88 +0,0 @@
private import codeql.actions.Ast
private import codeql.Locations
private import codeql.actions.security.ControlChecks
import codeql.actions.config.Config
import codeql.actions.Bash
import codeql.actions.PowerShell
bindingset[expr]
string normalizeExpr(string expr) {
result =
expr.regexpReplaceAll("\\['([a-zA-Z0-9_\\*\\-]+)'\\]", ".$1")
.regexpReplaceAll("\\[\"([a-zA-Z0-9_\\*\\-]+)\"\\]", ".$1")
.regexpReplaceAll("\\s*\\.\\s*", ".")
}
bindingset[regex]
string wrapRegexp(string regex) { result = "\\b" + regex + "\\b" }
bindingset[regex]
string wrapJsonRegexp(string regex) {
result = ["fromJSON\\(\\s*" + regex + "\\s*\\)", "toJSON\\(\\s*" + regex + "\\s*\\)"]
}
bindingset[str]
string trimQuotes(string str) {
result = str.trim().regexpReplaceAll("^(\"|')", "").regexpReplaceAll("(\"|')$", "")
}
predicate inPrivilegedContext(AstNode node, Event event) {
node.getEnclosingJob().isPrivilegedExternallyTriggerable(event)
}
predicate inNonPrivilegedContext(AstNode node) {
not node.getEnclosingJob().isPrivilegedExternallyTriggerable(_)
}
string defaultBranchNames() {
repositoryDataModel(_, result)
or
not exists(string default_branch_name | repositoryDataModel(_, default_branch_name)) and
result = ["main", "master"]
}
string getRepoRoot() {
exists(Workflow w |
w.getLocation().getFile().getRelativePath().indexOf("/.github/workflows") > 0 and
result =
w.getLocation()
.getFile()
.getRelativePath()
.prefix(w.getLocation().getFile().getRelativePath().indexOf("/.github/workflows") + 1) and
// exclude workflow_enum reusable workflows directory root
not result.indexOf(".github/workflows/external/") > -1 and
not result.indexOf(".github/actions/external/") > -1
or
not w.getLocation().getFile().getRelativePath().indexOf("/.github/workflows") > 0 and
not w.getLocation().getFile().getRelativePath().indexOf(".github/workflows/external/") > -1 and
not w.getLocation().getFile().getRelativePath().indexOf(".github/actions/external/") > -1 and
result = ""
)
}
bindingset[path]
string normalizePath(string path) {
exists(string trimmed_path | trimmed_path = trimQuotes(path) |
// ./foo -> GITHUB_WORKSPACE/foo
if path.indexOf("./") = 0
then result = path.replaceAll("./", "GITHUB_WORKSPACE/")
else
// GITHUB_WORKSPACE/foo -> GITHUB_WORKSPACE/foo
if path.indexOf("GITHUB_WORKSPACE/") = 0
then result = path
else
// foo -> GITHUB_WORKSPACE/foo
if path.regexpMatch("^[^/~].*")
then result = "GITHUB_WORKSPACE/" + path.regexpReplaceAll("/$", "")
else
// ~/foo -> ~/foo
// /foo -> /foo
result = path
)
}
/**
* Holds if the path cache_path is a subpath of the path untrusted_path.
*/
bindingset[subpath, path]
predicate isSubpath(string subpath, string path) { subpath.substring(0, path.length()) = path }

View File

@@ -1,62 +0,0 @@
private import codeql.actions.Ast
class PowerShellScript extends ShellScript {
PowerShellScript() {
exists(Run run |
this = run.getScript() and
run.getShell().matches("pwsh%")
)
}
override string getStmt(int i) { none() }
override string getAStmt() { none() }
override string getCommand(int i) { none() }
override string getACommand() { none() }
override string getFileReadCommand(int i) { none() }
override string getAFileReadCommand() { none() }
override predicate getAssignment(int i, string name, string data) { none() }
override predicate getAnAssignment(string name, string data) { none() }
override predicate getAWriteToGitHubEnv(string name, string data) { none() }
override predicate getAWriteToGitHubOutput(string name, string data) { none() }
override predicate getAWriteToGitHubPath(string data) { none() }
override predicate getAnEnvReachingGitHubOutputWrite(string var, string output_field) { none() }
override predicate getACmdReachingGitHubOutputWrite(string cmd, string output_field) { none() }
override predicate getAnEnvReachingGitHubEnvWrite(string var, string output_field) { none() }
override predicate getACmdReachingGitHubEnvWrite(string cmd, string output_field) { none() }
override predicate getAnEnvReachingGitHubPathWrite(string var) { none() }
override predicate getACmdReachingGitHubPathWrite(string cmd) { none() }
override predicate getAnEnvReachingArgumentInjectionSink(
string var, string command, string argument
) {
none()
}
override predicate getACmdReachingArgumentInjectionSink(
string cmd, string command, string argument
) {
none()
}
override predicate fileToGitHubEnv(string path) { none() }
override predicate fileToGitHubOutput(string path) { none() }
override predicate fileToGitHubPath(string path) { none() }
}

View File

@@ -1,13 +0,0 @@
/**
* Provides classes for performing local (intra-procedural) and
* global (inter-procedural) taint-tracking analyses.
*/
import codeql.Locations
module TaintTracking {
private import codeql.actions.dataflow.internal.DataFlowImplSpecific
private import codeql.actions.dataflow.internal.TaintTrackingImplSpecific
private import codeql.dataflow.TaintTracking
import TaintFlowMake<Location, ActionsDataFlow, ActionsTaintTracking>
}

View File

@@ -1,32 +0,0 @@
private import actions
/**
* Holds if workflow step uses the github/codeql-action/init action with no customizations.
* e.g.
* - name: Initialize
* uses: github/codeql-action/init@v2
* with:
* languages: ruby, javascript
*/
class DefaultableCodeQLInitiatlizeActionQuery extends UsesStep {
DefaultableCodeQLInitiatlizeActionQuery() {
this.getCallee() = "github/codeql-action/init" and
not customizedWorkflowStep(this)
}
}
/**
* Holds if the with: part of the workflow step contains any arguments for with: other than "languages".
* e.g.
* - name: Initialize CodeQL
* uses: github/codeql-action/init@v3
* with:
* languages: ${{ matrix.language }}
* config-file: ./.github/codeql/${{ matrix.language }}/codeql-config.yml
*/
predicate customizedWorkflowStep(UsesStep codeQLInitStep) {
exists(string arg |
exists(codeQLInitStep.getArgument(arg)) and
arg != "languages"
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,57 +0,0 @@
/**
* Provides classes for working with YAML data.
*
* YAML documents are represented as abstract syntax trees whose nodes
* are either YAML values or alias nodes referring to another YAML value.
*/
private import codeql.yaml.Yaml as LibYaml
private module YamlSig implements LibYaml::InputSig {
import codeql.Locations
class LocatableBase extends @yaml_locatable {
Location getLocation() {
exists(@location_default loc, File f, string p, int sl, int sc, int el, int ec |
f.getAbsolutePath() = p and
locations_default(loc, f, sl, sc, el, ec) and
yaml_locations(this, loc) and
result = TBaseLocation(p, sl, sc, el, ec)
)
}
string toString() { none() }
}
class NodeBase extends LocatableBase, @yaml_node {
NodeBase getChildNode(int i) { yaml(result, _, this, i, _, _) }
string getTag() { yaml(this, _, _, _, result, _) }
string getAnchor() { yaml_anchors(this, result) }
override string toString() { yaml(this, _, _, _, _, result) }
}
class ScalarNodeBase extends NodeBase, @yaml_scalar_node {
int getStyle() { yaml_scalars(this, result, _) }
string getValue() { yaml_scalars(this, _, result) }
}
class CollectionNodeBase extends NodeBase, @yaml_collection_node { }
class MappingNodeBase extends CollectionNodeBase, @yaml_mapping_node { }
class SequenceNodeBase extends CollectionNodeBase, @yaml_sequence_node { }
class AliasNodeBase extends NodeBase, @yaml_alias_node {
string getTarget() { yaml_aliases(this, result) }
}
class ParseErrorBase extends LocatableBase, @yaml_error {
string getMessage() { yaml_errors(this, result) }
}
}
import LibYaml::Make<YamlSig>

View File

@@ -1,147 +0,0 @@
import ConfigExtensions as Extensions
/**
* MaD models for workflow details
* Fields:
* - path: Path to the workflow file
* - trigger: Trigger for the workflow
* - job: Job name
* - secrets_source: Source of secrets
* - permissions: Permissions for the workflow
* - runner: Runner info for the workflow
*/
predicate workflowDataModel(
string path, string trigger, string job, string secrets_source, string permissions, string runner
) {
Extensions::workflowDataModel(path, trigger, job, secrets_source, permissions, runner)
}
/**
* MaD models for repository details
* Fields:
* - visibility: Visibility of the repository
* - default_branch_name: Default branch name
*/
predicate repositoryDataModel(string visibility, string default_branch_name) {
Extensions::repositoryDataModel(visibility, default_branch_name)
}
/**
* MaD models for context/trigger mapping
* Fields:
* - trigger: Trigger for the workflow
* - context_prefix: Prefix for the context
*/
predicate contextTriggerDataModel(string trigger, string context_prefix) {
Extensions::contextTriggerDataModel(trigger, context_prefix)
}
/**
* MaD models for externally triggerable events
* Fields:
* - event: Event name
*/
predicate externallyTriggerableEventsDataModel(string event) {
Extensions::externallyTriggerableEventsDataModel(event)
}
private string commandLauncher() { result = ["", "sudo\\s+", "su\\s+", "xvfb-run\\s+"] }
/**
* MaD models for poisonable commands
* Fields:
* - regexp: Regular expression for matching poisonable commands
*/
predicate poisonableCommandsDataModel(string regexp) {
exists(string sub_regexp |
Extensions::poisonableCommandsDataModel(sub_regexp) and
regexp = commandLauncher() + sub_regexp + ".*"
)
}
/**
* MaD models for poisonable local scripts
* Fields:
* - regexp: Regular expression for matching poisonable local scripts
* - group: Script capture group number for the regular expression
*/
predicate poisonableLocalScriptsDataModel(string regexp, int command_group) {
exists(string sub_regexp |
Extensions::poisonableLocalScriptsDataModel(sub_regexp, command_group) and
regexp = commandLauncher() + sub_regexp + ".*"
)
}
/**
* MaD models for arguments to commands that execute the given argument.
* Fields:
* - regexp: Regular expression for matching argument injections.
* - command_group: capture group for the command.
* - argument_group: capture group for the argument.
*/
predicate argumentInjectionSinksDataModel(string regexp, int command_group, int argument_group) {
exists(string sub_regexp |
Extensions::argumentInjectionSinksDataModel(sub_regexp, command_group, argument_group) and
regexp = commandLauncher() + sub_regexp
)
}
/**
* MaD models for poisonable actions
* Fields:
* - action: action name
*/
predicate poisonableActionsDataModel(string action) {
Extensions::poisonableActionsDataModel(action)
}
/**
* MaD models for event properties that can be user-controlled.
* Fields:
* - property: event property
* - kind: property kind
*/
predicate untrustedEventPropertiesDataModel(string property, string kind) {
Extensions::untrustedEventPropertiesDataModel(property, kind)
}
/**
* MaD models for vulnerable actions
* Fields:
* - action: action name
* - vulnerable_version: vulnerable version
* - vulnerable_sha: vulnerable sha
* - fixed_version: fixed version
*/
predicate vulnerableActionsDataModel(
string action, string vulnerable_version, string vulnerable_sha, string fixed_version
) {
Extensions::vulnerableActionsDataModel(action, vulnerable_version, vulnerable_sha, fixed_version)
}
/**
* MaD models for immutable actions
* Fields:
* - action: action name
*/
predicate immutableActionsDataModel(string action) { Extensions::immutableActionsDataModel(action) }
/**
* MaD models for untrusted git commands
* Fields:
* - cmd_regex: Regular expression for matching untrusted git commands
* - flag: Flag for the command
*/
predicate untrustedGitCommandDataModel(string cmd_regex, string flag) {
Extensions::untrustedGitCommandDataModel(cmd_regex, flag)
}
/**
* MaD models for untrusted gh commands
* Fields:
* - cmd_regex: Regular expression for matching untrusted gh commands
* - flag: Flag for the command
*/
predicate untrustedGhCommandDataModel(string cmd_regex, string flag) {
Extensions::untrustedGhCommandDataModel(cmd_regex, flag)
}

View File

@@ -1,74 +0,0 @@
/**
* This module provides extensible predicates for defining MaD models.
*/
/**
* Holds if workflow data model exists for the given parameters.
*/
extensible predicate workflowDataModel(
string path, string trigger, string job, string secrets_source, string permissions, string runner
);
/**
* Holds if repository data model exists for the given parameters.
*/
extensible predicate repositoryDataModel(string visibility, string default_branch_name);
/**
* Holds if a context expression starting with context_prefix is available for a given trigger.
*/
extensible predicate contextTriggerDataModel(string trigger, string context_prefix);
/**
* Holds if a given trigger event can be fired by an external actor.
*/
extensible predicate externallyTriggerableEventsDataModel(string event);
/**
* Holds for strings that match poisonable commands.
*/
extensible predicate poisonableCommandsDataModel(string regexp);
/**
* Holds for strings that match poisonable local scripts.
*/
extensible predicate poisonableLocalScriptsDataModel(string regexp, int group);
/**
* Holds for actions that can be poisoned through local files.
*/
extensible predicate poisonableActionsDataModel(string action);
/**
* Holds for event properties that can be user-controlled.
*/
extensible predicate untrustedEventPropertiesDataModel(string property, string kind);
/**
* Holds for arguments to commands that execute the given argument
*/
extensible predicate argumentInjectionSinksDataModel(
string regexp, int command_group, int argument_group
);
/**
* Holds for actions that are known to be vulnerable.
*/
extensible predicate vulnerableActionsDataModel(
string action, string vulnerable_version, string vulnerable_sha, string fixed_version
);
/**
* Holds for actions that are known to be immutable.
*/
extensible predicate immutableActionsDataModel(string action);
/**
* Holds for git commands that may introduce untrusted data when called on an attacker controlled branch.
*/
extensible predicate untrustedGitCommandDataModel(string cmd_regex, string flag);
/**
* Holds for gh commands that may introduce untrusted data
*/
extensible predicate untrustedGhCommandDataModel(string cmd_regex, string flag);

View File

@@ -1,444 +0,0 @@
/** Provides classes representing basic blocks. */
private import codeql.actions.Cfg
private import codeql.actions.Ast
private import codeql.Locations
/**
* A basic block, that is, a maximal straight-line sequence of control flow nodes
* without branches or joins.
*/
class BasicBlock extends TBasicBlockStart {
/** Gets the scope of this basic block. */
final CfgScope getScope() { result = this.getFirstNode().getScope() }
/** Gets an immediate successor of this basic block, if any. */
BasicBlock getASuccessor() { result = this.getASuccessor(_) }
/** Gets an immediate successor of this basic block of a given type, if any. */
BasicBlock getASuccessor(SuccessorType t) {
result.getFirstNode() = this.getLastNode().getASuccessor(t)
}
/** Gets an immediate predecessor of this basic block, if any. */
BasicBlock getAPredecessor() { result.getASuccessor() = this }
/** Gets an immediate predecessor of this basic block of a given type, if any. */
BasicBlock getAPredecessor(SuccessorType t) { result.getASuccessor(t) = this }
/** Gets the control flow node at a specific (zero-indexed) position in this basic block. */
Node getNode(int pos) { bbIndex(this.getFirstNode(), result, pos) }
/** Gets a control flow node in this basic block. */
Node getANode() { result = this.getNode(_) }
/** Gets the first control flow node in this basic block. */
Node getFirstNode() { this = TBasicBlockStart(result) }
/** Gets the last control flow node in this basic block. */
Node getLastNode() { result = this.getNode(this.length() - 1) }
/** Gets the length of this basic block. */
int length() { result = strictcount(this.getANode()) }
/**
* Holds if this basic block immediately dominates basic block `bb`.
*
* That is, all paths reaching basic block `bb` from some entry point
* basic block must go through this basic block (which is an immediate
* predecessor of `bb`).
*
* Example:
*
* ```rb
* def m b
* if b
* return 0
* end
* return 1
* end
* ```
*
* The basic block starting on line 2 immediately dominates the
* basic block on line 5 (all paths from the entry point of `m`
* to `return 1` must go through the `if` block).
*/
predicate immediatelyDominates(BasicBlock bb) { bbIDominates(this, bb) }
/**
* Holds if this basic block strictly dominates basic block `bb`.
*
* That is, all paths reaching basic block `bb` from some entry point
* basic block must go through this basic block (which must be different
* from `bb`).
*
* Example:
*
* ```rb
* def m b
* if b
* return 0
* end
* return 1
* end
* ```
*
* The basic block starting on line 2 strictly dominates the
* basic block on line 5 (all paths from the entry point of `m`
* to `return 1` must go through the `if` block).
*/
predicate strictlyDominates(BasicBlock bb) { bbIDominates+(this, bb) }
/**
* Holds if this basic block dominates basic block `bb`.
*
* That is, all paths reaching basic block `bb` from some entry point
* basic block must go through this basic block.
*
* Example:
*
* ```rb
* def m b
* if b
* return 0
* end
* return 1
* end
* ```
*
* The basic block starting on line 2 dominates the basic
* basic block on line 5 (all paths from the entry point of `m`
* to `return 1` must go through the `if` block).
*/
predicate dominates(BasicBlock bb) {
bb = this or
this.strictlyDominates(bb)
}
/**
* Holds if `df` is in the dominance frontier of this basic block.
* That is, this basic block dominates a predecessor of `df`, but
* does not dominate `df` itself.
*
* Example:
*
* ```rb
* def m x
* if x < 0
* x = -x
* if x > 10
* x = x - 1
* end
* end
* puts x
* end
* ```
*
* The basic block on line 8 is in the dominance frontier
* of the basic block starting on line 3 because that block
* dominates the basic block on line 4, which is a predecessor of
* `puts x`. Also, the basic block starting on line 3 does not
* dominate the basic block on line 8.
*/
predicate inDominanceFrontier(BasicBlock df) {
this.dominatesPredecessor(df) and
not this.strictlyDominates(df)
}
/**
* Holds if this basic block dominates a predecessor of `df`.
*/
private predicate dominatesPredecessor(BasicBlock df) { this.dominates(df.getAPredecessor()) }
/**
* Gets the basic block that immediately dominates this basic block, if any.
*
* That is, all paths reaching this basic block from some entry point
* basic block must go through the result, which is an immediate basic block
* predecessor of this basic block.
*
* Example:
*
* ```rb
* def m b
* if b
* return 0
* end
* return 1
* end
* ```
*
* The basic block starting on line 2 is an immediate dominator of
* the basic block on line 5 (all paths from the entry point of `m`
* to `return 1` must go through the `if` block, and the `if` block
* is an immediate predecessor of `return 1`).
*/
BasicBlock getImmediateDominator() { bbIDominates(result, this) }
/**
* Holds if this basic block strictly post-dominates basic block `bb`.
*
* That is, all paths reaching a normal exit point basic block from basic
* block `bb` must go through this basic block (which must be different
* from `bb`).
*
* Example:
*
* ```rb
* def m b
* if b
* puts "b"
* end
* puts "m"
* end
* ```
*
* The basic block on line 5 strictly post-dominates the basic block on
* line 3 (all paths to the exit point of `m` from `puts "b"` must go
* through `puts "m"`).
*/
predicate strictlyPostDominates(BasicBlock bb) { bbIPostDominates+(this, bb) }
/**
* Holds if this basic block post-dominates basic block `bb`.
*
* That is, all paths reaching a normal exit point basic block from basic
* block `bb` must go through this basic block.
*
* Example:
*
* ```rb
* def m b
* if b
* puts "b"
* end
* puts "m"
* end
* ```
*
* The basic block on line 5 post-dominates the basic block on line 3
* (all paths to the exit point of `m` from `puts "b"` must go through
* `puts "m"`).
*/
predicate postDominates(BasicBlock bb) {
this.strictlyPostDominates(bb) or
this = bb
}
/** Holds if this basic block is in a loop in the control flow graph. */
predicate inLoop() { this.getASuccessor+() = this }
/** Gets a textual representation of this basic block. */
string toString() { result = this.getFirstNode().toString() }
/** Gets the location of this basic block. */
Location getLocation() { result = this.getFirstNode().getLocation() }
}
cached
private module Cached {
/** Internal representation of basic blocks. */
cached
newtype TBasicBlock = TBasicBlockStart(Node cfn) { startsBB(cfn) }
/** Holds if `cfn` starts a new basic block. */
private predicate startsBB(Node cfn) {
not exists(cfn.getAPredecessor()) and exists(cfn.getASuccessor())
or
cfn.isJoin()
or
cfn.getAPredecessor().isBranch()
or
/*
* In cases such as
*
* ```rb
* if x or y
* foo
* else
* bar
* ```
*
* we have a CFG that looks like
*
* x --false--> [false] x or y --false--> bar
* \ |
* --true--> y --false--
* \
* --true--> [true] x or y --true--> foo
*
* and we want to ensure that both `foo` and `bar` start a new basic block,
* in order to get a `ConditionalBlock` out of the disjunction.
*/
exists(cfn.getAPredecessor(any(BooleanSuccessor s)))
}
/**
* Holds if `succ` is a control flow successor of `pred` within
* the same basic block.
*/
private predicate intraBBSucc(Node pred, Node succ) {
succ = pred.getASuccessor() and
not startsBB(succ)
}
/**
* Holds if `cfn` is the `i`th node in basic block `bb`.
*
* In other words, `i` is the shortest distance from a node `bb`
* that starts a basic block to `cfn` along the `intraBBSucc` relation.
*/
cached
predicate bbIndex(Node bbStart, Node cfn, int i) =
shortestDistances(startsBB/1, intraBBSucc/2)(bbStart, cfn, i)
/**
* Holds if the first node of basic block `succ` is a control flow
* successor of the last node of basic block `pred`.
*/
private predicate succBB(BasicBlock pred, BasicBlock succ) { succ = pred.getASuccessor() }
/** Holds if `dom` is an immediate dominator of `bb`. */
cached
predicate bbIDominates(BasicBlock dom, BasicBlock bb) =
idominance(entryBB/1, succBB/2)(_, dom, bb)
/** Holds if `pred` is a basic block predecessor of `succ`. */
private predicate predBB(BasicBlock succ, BasicBlock pred) { succBB(pred, succ) }
/** Holds if `bb` is an exit basic block that represents normal exit. */
private predicate normalExitBB(BasicBlock bb) { bb.getANode().(AnnotatedExitNode).isNormal() }
/** Holds if `dom` is an immediate post-dominator of `bb`. */
cached
predicate bbIPostDominates(BasicBlock dom, BasicBlock bb) =
idominance(normalExitBB/1, predBB/2)(_, dom, bb)
/**
* Gets the `i`th predecessor of join block `jb`, with respect to some
* arbitrary order.
*/
cached
JoinBlockPredecessor getJoinBlockPredecessor(JoinBlock jb, int i) {
none()
/*
* result =
* rank[i + 1](JoinBlockPredecessor jbp |
* jbp = jb.getAPredecessor()
* |
* jbp order by JoinBlockPredecessors::getId(jbp), JoinBlockPredecessors::getSplitString(jbp)
* )
*/
}
cached
predicate immediatelyControls(ConditionBlock cb, BasicBlock succ, BooleanSuccessor s) {
succ = cb.getASuccessor(s) and
forall(BasicBlock pred | pred = succ.getAPredecessor() and pred != cb | succ.dominates(pred))
}
cached
predicate controls(ConditionBlock cb, BasicBlock controlled, BooleanSuccessor s) {
exists(BasicBlock succ | cb.immediatelyControls(succ, s) | succ.dominates(controlled))
}
}
private import Cached
/** Holds if `bb` is an entry basic block. */
private predicate entryBB(BasicBlock bb) { bb.getFirstNode() instanceof EntryNode }
/**
* An entry basic block, that is, a basic block whose first node is
* an entry node.
*/
class EntryBasicBlock extends BasicBlock {
EntryBasicBlock() { entryBB(this) }
}
/**
* An annotated exit basic block, that is, a basic block whose last node is
* an annotated exit node.
*/
class AnnotatedExitBasicBlock extends BasicBlock {
private boolean normal;
AnnotatedExitBasicBlock() {
exists(AnnotatedExitNode n |
n = this.getANode() and
if n.isNormal() then normal = true else normal = false
)
}
/** Holds if this block represent a normal exit. */
final predicate isNormal() { normal = true }
}
/**
* An exit basic block, that is, a basic block whose last node is
* an exit node.
*/
class ExitBasicBlock extends BasicBlock {
ExitBasicBlock() { this.getLastNode() instanceof ExitNode }
}
/*
* private module JoinBlockPredecessors {
* private predicate id(AstNode x, AstNode y) { x = y }
*
* private predicate idOf(AstNode x, int y) = equivalenceRelation(id/2)(x, y)
*
* int getId(JoinBlockPredecessor jbp) {
* idOf(Ast::toTreeSitter(jbp.getFirstNode().(AstCfgNode).getAstNode()), result)
* or
* idOf(Ast::toTreeSitter(jbp.(EntryBasicBlock).getScope()), result)
* }
*
* string getSplitString(JoinBlockPredecessor jbp) {
* result = jbp.getFirstNode().(AstCfgNode).getSplitsString()
* or
* not exists(jbp.getFirstNode().(AstCfgNode).getSplitsString()) and
* result = ""
* }
* }
*/
/** A basic block with more than one predecessor. */
class JoinBlock extends BasicBlock {
JoinBlock() { this.getFirstNode().isJoin() }
/**
* Gets the `i`th predecessor of this join block, with respect to some
* arbitrary order.
*/
JoinBlockPredecessor getJoinBlockPredecessor(int i) { result = getJoinBlockPredecessor(this, i) }
}
/** A basic block that is an immediate predecessor of a join block. */
class JoinBlockPredecessor extends BasicBlock {
JoinBlockPredecessor() { this.getASuccessor() instanceof JoinBlock }
}
/** A basic block that terminates in a condition, splitting the subsequent control flow. */
class ConditionBlock extends BasicBlock {
ConditionBlock() { this.getLastNode().isCondition() }
/**
* Holds if basic block `succ` is immediately controlled by this basic
* block with conditional value `s`. That is, `succ` is an immediate
* successor of this block, and `succ` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
predicate immediatelyControls(BasicBlock succ, BooleanSuccessor s) {
immediatelyControls(this, succ, s)
}
/**
* Holds if basic block `controlled` is controlled by this basic block with
* conditional value `s`. That is, `controlled` can only be reached from
* the callable entry point by going via the `s` edge out of this basic block.
*/
predicate controls(BasicBlock controlled, BooleanSuccessor s) { controls(this, controlled, s) }
}

View File

@@ -1,316 +0,0 @@
private import codeql.actions.Ast
private import codeql.controlflow.Cfg as CfgShared
private import codeql.Locations
module Completion {
private newtype TCompletion =
TSimpleCompletion() or
TBooleanCompletion(boolean b) { b in [false, true] } or
TReturnCompletion()
abstract class Completion extends TCompletion {
abstract string toString();
predicate isValidForSpecific(AstNode e) { none() }
predicate isValidFor(AstNode e) { this.isValidForSpecific(e) }
abstract SuccessorType getAMatchingSuccessorType();
}
abstract class NormalCompletion extends Completion { }
class SimpleCompletion extends NormalCompletion, TSimpleCompletion {
override string toString() { result = "SimpleCompletion" }
override predicate isValidFor(AstNode e) { not any(Completion c).isValidForSpecific(e) }
override NormalSuccessor getAMatchingSuccessorType() { any() }
}
class BooleanCompletion extends NormalCompletion, TBooleanCompletion {
boolean value;
BooleanCompletion() { this = TBooleanCompletion(value) }
override string toString() { result = "BooleanCompletion(" + value + ")" }
override predicate isValidForSpecific(AstNode e) { none() }
override BooleanSuccessor getAMatchingSuccessorType() { result.getValue() = value }
final boolean getValue() { result = value }
}
class ReturnCompletion extends Completion, TReturnCompletion {
override string toString() { result = "ReturnCompletion" }
override predicate isValidForSpecific(AstNode e) { none() }
override ReturnSuccessor getAMatchingSuccessorType() { any() }
}
cached
private newtype TSuccessorType =
TNormalSuccessor() or
TBooleanSuccessor(boolean b) { b in [false, true] } or
TReturnSuccessor()
class SuccessorType extends TSuccessorType {
string toString() { none() }
}
class NormalSuccessor extends SuccessorType, TNormalSuccessor {
override string toString() { result = "successor" }
}
class BooleanSuccessor extends SuccessorType, TBooleanSuccessor {
boolean value;
BooleanSuccessor() { this = TBooleanSuccessor(value) }
override string toString() { result = value.toString() }
boolean getValue() { result = value }
}
class ReturnSuccessor extends SuccessorType, TReturnSuccessor {
override string toString() { result = "return" }
}
}
module CfgScope {
abstract class CfgScope extends AstNode { }
class WorkflowScope extends CfgScope instanceof Workflow { }
class CompositeActionScope extends CfgScope instanceof CompositeAction { }
}
private module Implementation implements CfgShared::InputSig<Location> {
import codeql.actions.Ast
import Completion
import CfgScope
predicate completionIsNormal(Completion c) { not c instanceof ReturnCompletion }
// Not using CFG splitting, so the following are just dummy types.
private newtype TUnit = Unit()
additional class SplitKindBase = TUnit;
additional class Split extends TUnit {
abstract string toString();
}
predicate completionIsSimple(Completion c) { c instanceof SimpleCompletion }
predicate completionIsValidFor(Completion c, AstNode e) { c.isValidFor(e) }
CfgScope getCfgScope(AstNode e) {
exists(AstNode p | p = e.getParentNode() |
result = p
or
not p instanceof CfgScope and result = getCfgScope(p)
)
}
additional int maxSplits() { result = 0 }
predicate scopeFirst(CfgScope scope, AstNode e) {
first(scope.(Workflow), e) or
first(scope.(CompositeAction), e)
}
predicate scopeLast(CfgScope scope, AstNode e, Completion c) {
last(scope.(Workflow), e, c) or
last(scope.(CompositeAction), e, c)
}
predicate successorTypeIsSimple(SuccessorType t) { t instanceof NormalSuccessor }
predicate successorTypeIsCondition(SuccessorType t) { t instanceof BooleanSuccessor }
SuccessorType getAMatchingSuccessorType(Completion c) { result = c.getAMatchingSuccessorType() }
predicate isAbnormalExitType(SuccessorType t) { none() }
}
module CfgImpl = CfgShared::Make<Location, Implementation>;
private import CfgImpl
private import Completion
private import CfgScope
private class CompositeActionTree extends StandardPreOrderTree instanceof CompositeAction {
override ControlFlowTree getChildNode(int i) {
result =
rank[i](AstNode child, Location l |
(
child = this.(CompositeAction).getAnInput() or
child = this.(CompositeAction).getOutputs() or
child = this.(CompositeAction).getRuns()
) and
l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class RunsTree extends StandardPreOrderTree instanceof Runs {
override ControlFlowTree getChildNode(int i) { result = super.getStep(i) }
}
private class WorkflowTree extends StandardPreOrderTree instanceof Workflow {
override ControlFlowTree getChildNode(int i) {
if this instanceof ReusableWorkflow
then
result =
rank[i](AstNode child, Location l |
(
child = this.(ReusableWorkflow).getAnInput() or
child = this.(ReusableWorkflow).getOutputs() or
child = this.(ReusableWorkflow).getStrategy() or
child = this.(ReusableWorkflow).getAJob()
) and
l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
else
result =
rank[i](AstNode child, Location l |
(
child = super.getStrategy() or
child = super.getAJob()
) and
l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class OutputsTree extends StandardPreOrderTree instanceof Outputs {
override ControlFlowTree getChildNode(int i) {
result =
rank[i](AstNode child, Location l |
child = super.getAnOutputExpr() and l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class StrategyTree extends StandardPreOrderTree instanceof Strategy {
override ControlFlowTree getChildNode(int i) {
result =
rank[i](AstNode child, Location l |
child = super.getAMatrixVarExpr() and l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class JobTree extends StandardPreOrderTree instanceof LocalJob {
override ControlFlowTree getChildNode(int i) {
result =
rank[i](AstNode child, Location l |
(
child = super.getAStep() or
child = super.getOutputs() or
child = super.getStrategy()
) and
l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class ExternalJobTree extends StandardPreOrderTree instanceof ExternalJob {
override ControlFlowTree getChildNode(int i) {
result =
rank[i](AstNode child, Location l |
(
child = super.getArgumentExpr(_) or
child = super.getInScopeEnvVarExpr(_) or
child = super.getOutputs() or
child = super.getStrategy()
) and
l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class UsesTree extends StandardPreOrderTree instanceof UsesStep {
override ControlFlowTree getChildNode(int i) {
result =
rank[i](AstNode child, Location l |
(child = super.getArgumentExpr(_) or child = super.getInScopeEnvVarExpr(_)) and
l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class RunTree extends StandardPreOrderTree instanceof Run {
override ControlFlowTree getChildNode(int i) {
result =
rank[i](AstNode child, Location l |
(
child = super.getInScopeEnvVarExpr(_) or
child = super.getAnScriptExpr() or
child = super.getScript()
) and
l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class ScalarValueTree extends StandardPreOrderTree instanceof ScalarValue {
override ControlFlowTree getChildNode(int i) {
result =
rank[i](Expression child, Location l |
child = super.getAChildNode() and
l = child.getLocation()
|
child
order by
l.getStartLine(), l.getStartColumn(), l.getEndColumn(), l.getEndLine(), child.toString()
)
}
}
private class UsesLeaf extends LeafTree instanceof Uses { }
private class InputTree extends LeafTree instanceof Input { }
private class ScalarValueLeaf extends LeafTree instanceof ScalarValue { }
private class ExpressionLeaf extends LeafTree instanceof Expression { }

View File

@@ -1,131 +0,0 @@
private import actions
private import internal.ExternalFlowExtensions as Extensions
private import codeql.actions.DataFlow
private import codeql.actions.security.ArtifactPoisoningQuery
/**
* MaD sources
* Fields:
* - action: Fully-qualified action name (NWO)
* - version: Either '*' or a specific SHA/Tag
* - output arg: To node (prefixed with either `env.` or `output.`)
* - provenance: verification of the model
*/
predicate actionsSourceModel(
string action, string version, string output, string kind, string provenance
) {
Extensions::actionsSourceModel(action, version, output, kind, provenance)
}
/**
* MaD summaries
* Fields:
* - action: Fully-qualified action name (NWO)
* - version: Either '*' or a specific SHA/Tag
* - input arg: From node (prefixed with either `env.` or `input.`)
* - output arg: To node (prefixed with either `env.` or `output.`)
* - kind: Either 'Taint' or 'Value'
* - provenance: verification of the model
*/
predicate actionsSummaryModel(
string action, string version, string input, string output, string kind, string provenance
) {
Extensions::actionsSummaryModel(action, version, input, output, kind, provenance)
}
/**
* MaD sinks
* Fields:
* - action: Fully-qualified action name (NWO)
* - version: Either '*' or a specific SHA/Tag
* - input: sink node (prefixed with either `env.` or `input.`)
* - kind: sink kind
* - provenance: verification of the model
*/
predicate actionsSinkModel(
string action, string version, string input, string kind, string provenance
) {
Extensions::actionsSinkModel(action, version, input, kind, provenance)
}
/**
* Holds if source.fieldName is a MaD-defined source of a given taint kind.
*/
predicate madSource(DataFlow::Node source, string kind, string fieldName) {
exists(Uses uses, string action, string version |
actionsSourceModel(action, version, fieldName, kind, _) and
uses.getCallee() = action.toLowerCase() and
(
if version.trim() = "*"
then uses.getVersion() = any(string v)
else uses.getVersion() = version.trim()
) and
(
if fieldName.trim().matches("env.%")
then source.asExpr() = uses.getInScopeEnvVarExpr(fieldName.trim().replaceAll("env.", ""))
else
if fieldName.trim().matches("output.%")
then source.asExpr() = uses
else none()
)
)
}
/**
* Holds if the data flow from `pred` to `succ` is a MaD store step.
*/
predicate madStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::ContentSet c) {
exists(Uses uses, string action, string version, string input, string output |
actionsSummaryModel(action, version, input, output, "taint", _) and
c = any(DataFlow::FieldContent ct | ct.getName() = output.replaceAll("output.", "")) and
uses.getCallee() = action.toLowerCase() and
// version check
(
if version.trim() = "*"
then uses.getVersion() = any(string v)
else uses.getVersion() = version.trim()
) and
// pred provenance
(
input.trim().matches("env.%") and
pred.asExpr() = uses.getInScopeEnvVarExpr(input.trim().replaceAll("env.", ""))
or
input.trim().matches("input.%") and
pred.asExpr() = uses.getArgumentExpr(input.trim().replaceAll("input.", ""))
or
input.trim() = "artifact" and
exists(UntrustedArtifactDownloadStep download |
pred.asExpr() = download and
download.getAFollowingStep() = uses
)
) and
succ.asExpr() = uses
)
}
/**
* Holds if sink is a MaD-defined sink for a given taint kind.
*/
predicate madSink(DataFlow::Node sink, string kind) {
exists(Uses uses, string action, string version, string input |
actionsSinkModel(action, version, input, kind, _) and
uses.getCallee() = action.toLowerCase() and
// version check
(
if version.trim() = "*"
then uses.getVersion() = any(string v)
else uses.getVersion() = version.trim()
) and
// pred provenance
(
input.trim().matches("env.%") and
sink.asExpr() = uses.getInScopeEnvVarExpr(input.trim().replaceAll("env.", ""))
or
input.trim().matches("input.%") and
sink.asExpr() = uses.getArgumentExpr(input.trim().replaceAll("input.", ""))
or
input.trim() = "artifact" and
sink.asExpr() = uses
)
)
}

View File

@@ -1,366 +0,0 @@
private import codeql.actions.security.ArtifactPoisoningQuery
private import codeql.actions.security.UntrustedCheckoutQuery
private import codeql.actions.config.Config
private import codeql.actions.dataflow.ExternalFlow
/**
* A data flow source.
*/
abstract class SourceNode extends DataFlow::Node {
/**
* Gets a string that represents the source kind with respect to threat modeling.
*/
abstract string getThreatModel();
}
/** A data flow source of remote user input. */
abstract class RemoteFlowSource extends SourceNode {
/** Gets a string that describes the type of this remote flow source. */
abstract string getSourceType();
/** Gets the event that triggered the source. */
abstract string getEventName();
override string getThreatModel() { result = "remote" }
}
/**
* A data flow source of user input from github context.
* eg: github.head_ref
*/
class GitHubCtxSource extends RemoteFlowSource {
string flag;
string event;
GitHubExpression e;
GitHubCtxSource() {
this.asExpr() = e and
// github.head_ref
e.getFieldName() = "head_ref" and
flag = "branch" and
(
event = e.getATriggerEvent().getName() and
event = "pull_request_target"
or
not exists(e.getATriggerEvent()) and
event = "unknown"
)
}
override string getSourceType() { result = flag }
override string getEventName() { result = event }
}
class GitHubEventCtxSource extends RemoteFlowSource {
string flag;
string context;
string event;
GitHubEventCtxSource() {
exists(Expression e, string regexp |
this.asExpr() = e and
context = e.getExpression() and
(
// the context is available for the job trigger events
event = e.getATriggerEvent().getName() and
exists(string context_prefix |
contextTriggerDataModel(event, context_prefix) and
normalizeExpr(context).matches("%" + context_prefix + "%")
)
or
not exists(e.getATriggerEvent()) and
event = "unknown"
) and
untrustedEventPropertiesDataModel(regexp, flag) and
not flag = "json" and
normalizeExpr(context).regexpMatch("(?i)\\s*" + wrapRegexp(regexp) + ".*")
)
}
override string getSourceType() { result = flag }
string getContext() { result = context }
override string getEventName() { result = event }
}
abstract class CommandSource extends RemoteFlowSource {
abstract string getCommand();
abstract Run getEnclosingRun();
override string getEventName() { result = this.getEnclosingRun().getATriggerEvent().getName() }
}
class GitCommandSource extends RemoteFlowSource, CommandSource {
Run run;
string cmd;
string flag;
GitCommandSource() {
exists(Step checkout, string cmd_regex |
checkout instanceof SimplePRHeadCheckoutStep and
this.asExpr() = run.getScript() and
checkout.getAFollowingStep() = run and
run.getScript().getAStmt() = cmd and
cmd.indexOf("git") = 0 and
untrustedGitCommandDataModel(cmd_regex, flag) and
cmd.regexpMatch(cmd_regex + ".*")
)
}
override string getSourceType() { result = flag }
override string getCommand() { result = cmd }
override Run getEnclosingRun() { result = run }
}
class GhCLICommandSource extends RemoteFlowSource, CommandSource {
Run run;
string cmd;
string flag;
GhCLICommandSource() {
exists(string cmd_regex |
this.asExpr() = run.getScript() and
run.getScript().getAStmt() = cmd and
cmd.indexOf("gh ") = 0 and
untrustedGhCommandDataModel(cmd_regex, flag) and
cmd.regexpMatch(cmd_regex + ".*") and
(
cmd.regexpMatch(".*\\b(pr|pulls)\\b.*") and
run.getATriggerEvent().getName() = checkoutTriggers()
or
not cmd.regexpMatch(".*\\b(pr|pulls)\\b.*")
)
)
}
override string getSourceType() { result = flag }
override Run getEnclosingRun() { result = run }
override string getCommand() { result = cmd }
}
class GitHubEventPathSource extends RemoteFlowSource, CommandSource {
string cmd;
string flag;
string access_path;
Run run;
// Examples
// COMMENT_AUTHOR=$(jq -r .comment.user.login "$GITHUB_EVENT_PATH")
// CURRENT_COMMENT=$(jq -r .comment.body "$GITHUB_EVENT_PATH")
// PR_HEAD=$(jq --raw-output .pull_request.head.ref ${GITHUB_EVENT_PATH})
// PR_NUMBER=$(jq --raw-output .pull_request.number ${GITHUB_EVENT_PATH})
// PR_TITLE=$(jq --raw-output .pull_request.title ${GITHUB_EVENT_PATH})
// BODY=$(jq -r '.issue.body' "$GITHUB_EVENT_PATH" | sed -n '3p')
GitHubEventPathSource() {
this.asExpr() = run.getScript() and
run.getScript().getACommand() = cmd and
cmd.matches("jq%") and
cmd.matches("%GITHUB_EVENT_PATH%") and
exists(string regexp |
untrustedEventPropertiesDataModel(regexp, flag) and
not flag = "json" and
access_path = "github.event" + cmd.regexpCapture(".*\\s+([^\\s]+)\\s+.*", 1) and
normalizeExpr(access_path).regexpMatch("(?i)\\s*" + wrapRegexp(regexp) + ".*")
)
}
override string getSourceType() { result = flag }
override string getCommand() { result = cmd }
override Run getEnclosingRun() { result = run }
}
class GitHubEventJsonSource extends RemoteFlowSource {
string flag;
string event;
GitHubEventJsonSource() {
exists(Expression e, string context, string regexp |
this.asExpr() = e and
context = e.getExpression() and
untrustedEventPropertiesDataModel(regexp, _) and
(
// only contexts for the triggering events are considered tainted.
// eg: for `pull_request`, we only consider `github.event.pull_request`
event = e.getEnclosingWorkflow().getATriggerEvent().getName() and
exists(string context_prefix |
contextTriggerDataModel(event, context_prefix) and
normalizeExpr(context).matches("%" + context_prefix + "%")
) and
normalizeExpr(context).regexpMatch("(?i).*" + wrapJsonRegexp(regexp) + ".*")
or
// github.event is tainted for all triggers
event = e.getEnclosingWorkflow().getATriggerEvent().getName() and
contextTriggerDataModel(e.getEnclosingWorkflow().getATriggerEvent().getName(), _) and
normalizeExpr(context).regexpMatch("(?i).*" + wrapJsonRegexp("\\bgithub.event\\b") + ".*")
or
not exists(e.getATriggerEvent()) and
event = "unknown"
) and
flag = "json"
)
}
override string getSourceType() { result = flag }
override string getEventName() { result = event }
}
/**
* A Source of untrusted data defined in a MaD specification
*/
class MaDSource extends RemoteFlowSource {
string sourceType;
MaDSource() { madSource(this, sourceType, _) }
override string getSourceType() { result = sourceType }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}
abstract class FileSource extends RemoteFlowSource { }
/**
* A downloaded artifact.
*/
class ArtifactSource extends RemoteFlowSource, FileSource {
ArtifactSource() { this.asExpr() instanceof UntrustedArtifactDownloadStep }
override string getSourceType() { result = "artifact" }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}
/**
* A file from an untrusted checkout.
*/
private class CheckoutSource extends RemoteFlowSource, FileSource {
CheckoutSource() { this.asExpr() instanceof SimplePRHeadCheckoutStep }
override string getSourceType() { result = "artifact" }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}
/**
* A list of file names returned by dorny/paths-filter.
*/
class DornyPathsFilterSource extends RemoteFlowSource {
DornyPathsFilterSource() {
exists(UsesStep u |
u.getCallee() = "dorny/paths-filter" and
u.getArgument("list-files") = ["csv", "json"] and
this.asExpr() = u
)
}
override string getSourceType() { result = "filename" }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}
/**
* A list of file names returned by tj-actions/changed-files.
*/
class TJActionsChangedFilesSource extends RemoteFlowSource {
TJActionsChangedFilesSource() {
exists(UsesStep u, string vulnerable_action, string vulnerable_version, string vulnerable_sha |
vulnerableActionsDataModel(vulnerable_action, vulnerable_version, vulnerable_sha, _) and
u.getCallee() = "tj-actions/changed-files" and
u.getCallee() = vulnerable_action and
(
u.getArgument("safe_output") = "false"
or
(u.getVersion() = vulnerable_version or u.getVersion() = vulnerable_sha)
) and
this.asExpr() = u
)
}
override string getSourceType() { result = "filename" }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}
/**
* A list of file names returned by tj-actions/verify-changed-files.
*/
class TJActionsVerifyChangedFilesSource extends RemoteFlowSource {
TJActionsVerifyChangedFilesSource() {
exists(UsesStep u, string vulnerable_action, string vulnerable_version, string vulnerable_sha |
vulnerableActionsDataModel(vulnerable_action, vulnerable_version, vulnerable_sha, _) and
u.getCallee() = "tj-actions/verify-changed-files" and
u.getCallee() = vulnerable_action and
(
u.getArgument("safe_output") = "false"
or
(u.getVersion() = vulnerable_version or u.getVersion() = vulnerable_sha)
) and
this.asExpr() = u
)
}
override string getSourceType() { result = "filename" }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}
class Xt0rtedSlashCommandSource extends RemoteFlowSource {
Xt0rtedSlashCommandSource() {
exists(UsesStep u |
u.getCallee() = "xt0rted/slash-command-action" and
u.getArgument("permission-level").toLowerCase() = ["read", "none"] and
this.asExpr() = u
)
}
override string getSourceType() { result = "text" }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}
class ZenteredIssueFormBodyParserSource extends RemoteFlowSource {
ZenteredIssueFormBodyParserSource() {
exists(UsesStep u |
u.getCallee() = "zentered/issue-forms-body-parser" and
not exists(u.getArgument("body")) and
this.asExpr() = u
)
}
override string getSourceType() { result = "text" }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}
class OctokitRequestActionSource extends RemoteFlowSource {
OctokitRequestActionSource() {
exists(UsesStep u, string route |
u.getCallee() = "octokit/request-action" and
route = u.getArgument("route").trim() and
route.indexOf("GET") = 0 and
(
route.matches("%/commits%") or
route.matches("%/comments%") or
route.matches("%/pulls%") or
route.matches("%/issues%") or
route.matches("%/users%") or
route.matches("%github.event.issue.pull_request.url%")
) and
this.asExpr() = u
)
}
override string getSourceType() { result = "text" }
override string getEventName() { result = this.asExpr().getATriggerEvent().getName() }
}

View File

@@ -1,92 +0,0 @@
/**
* Provides classes representing various flow steps for taint tracking.
*/
private import actions
private import codeql.actions.DataFlow
private import codeql.actions.dataflow.FlowSources
/**
* Holds if a Run step declares an environment variable, uses it in its script and sets an output in its script.
* e.g.
* - name: Extract and Clean Initial URL
* id: extract-url
* env:
* BODY: ${{ github.event.comment.body }}
* run: |
* echo "::set-output name=foo::$BODY"
* echo "foo=$(echo $BODY)" >> $GITHUB_OUTPUT
* echo "foo=$(echo $BODY)" >> "$GITHUB_OUTPUT"
* echo "::set-output name=step-output::$BODY"
*/
predicate envToOutputStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::ContentSet c) {
exists(Run run, string var, string field |
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
succ.asExpr() = run and
run.getScript().getAnEnvReachingGitHubOutputWrite(var, field) and
c = any(DataFlow::FieldContent ct | ct.getName() = field)
)
}
predicate envToEnvStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::ContentSet c) {
exists(
Run run, string var, string field //string key, string value |
|
run.getInScopeEnvVarExpr(var) = pred.asExpr() and
// we store the taint on the enclosing job since the may not exist an implicit env attribute
succ.asExpr() = run.getEnclosingJob() and
run.getScript().getAnEnvReachingGitHubEnvWrite(var, field) and
c = any(DataFlow::FieldContent ct | ct.getName() = field)
)
}
/**
* A command whose output gets assigned to an environment variable or step output.
* - run: |
* echo "foo=$(cmd)" >> "$GITHUB_OUTPUT"
* - run: |
* foo=$(<cmd)"
* echo "bar=${foo}" >> "$GITHUB_OUTPUT"
*/
predicate commandToOutputStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::ContentSet c) {
exists(Run run, string key, string cmd |
(
exists(CommandSource source | source.getCommand() = cmd)
or
exists(FileSource source |
source.asExpr().(Step).getAFollowingStep() = run and
run.getScript().getAFileReadCommand() = cmd
)
) and
run.getScript().getACmdReachingGitHubOutputWrite(cmd, key) and
c = any(DataFlow::FieldContent ct | ct.getName() = key) and
pred.asExpr() = run.getScript() and
succ.asExpr() = run
)
}
/**
* A command whose output gets assigned to an environment variable or step output.
* - run: |
* echo "foo=$(cmd)" >> "$GITHUB_ENV"
* - run: |
* foo=$(<cmd)"
* echo "bar=${foo}" >> "$GITHUB_ENV"
*/
predicate commandToEnvStoreStep(DataFlow::Node pred, DataFlow::Node succ, DataFlow::ContentSet c) {
exists(Run run, string key, string cmd |
(
exists(CommandSource source | source.getCommand() = cmd)
or
exists(FileSource source |
source.asExpr().(Step).getAFollowingStep() = run and
run.getScript().getAFileReadCommand() = cmd
)
) and
run.getScript().getACmdReachingGitHubEnvWrite(cmd, key) and
c = any(DataFlow::FieldContent ct | ct.getName() = key) and
pred.asExpr() = run.getScript() and
// we store the taint on the enclosing job since there may not be an implicit env attribute
succ.asExpr() = run.getEnclosingJob()
)
}

View File

@@ -1,156 +0,0 @@
/**
* Provides classes representing various flow steps for taint tracking.
*/
private import actions
private import codeql.util.Unit
private import codeql.actions.DataFlow
private import codeql.actions.dataflow.FlowSources
/**
* A unit class for adding additional taint steps.
*
* Extend this class to add additional taint steps that should apply to all
* taint configurations.
*/
class AdditionalTaintStep extends Unit {
/**
* Holds if the step from `node1` to `node2` should be considered a taint
* step for all configurations.
*/
abstract predicate step(DataFlow::Node node1, DataFlow::Node node2);
}
/**
* A file source step followed by a Run step may read the file.
*/
predicate fileDownloadToRunStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(FileSource source, Run run |
pred = source and
source.asExpr().(Step).getAFollowingStep() = run and
succ.asExpr() = run.getScript() and
exists(run.getScript().getAFileReadCommand())
)
}
/**
* A read of the _files field of the dorny/paths-filter action.
*/
predicate dornyPathsFilterTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(StepsExpression o |
pred instanceof DornyPathsFilterSource and
o.getStepId() = pred.asExpr().(UsesStep).getId() and
o.getFieldName().matches("%_files") and
succ.asExpr() = o
)
}
/**
* A read of user-controlled field of the tj-actions/changed-files action.
*/
predicate tjActionsChangedFilesTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(StepsExpression o |
pred instanceof TJActionsChangedFilesSource and
o.getTarget() = pred.asExpr() and
o.getStepId() = pred.asExpr().(UsesStep).getId() and
o.getFieldName() =
[
"added_files", "copied_files", "deleted_files", "modified_files", "renamed_files",
"all_old_new_renamed_files", "type_changed_files", "unmerged_files", "unknown_files",
"all_changed_and_modified_files", "all_changed_files", "other_changed_files",
"all_modified_files", "other_modified_files", "other_deleted_files", "modified_keys",
"changed_keys"
] and
succ.asExpr() = o
)
}
/**
* A read of user-controlled field of the tj-actions/verify-changed-files action.
*/
predicate tjActionsVerifyChangedFilesTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(StepsExpression o |
pred instanceof TJActionsVerifyChangedFilesSource and
o.getTarget() = pred.asExpr() and
o.getStepId() = pred.asExpr().(UsesStep).getId() and
o.getFieldName() = "changed_files" and
succ.asExpr() = o
)
}
/**
* A read of user-controlled field of the xt0rted/slash-command-action action.
*/
predicate xt0rtedSlashCommandActionTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(StepsExpression o |
pred instanceof Xt0rtedSlashCommandSource and
o.getTarget() = pred.asExpr() and
o.getStepId() = pred.asExpr().(UsesStep).getId() and
o.getFieldName() = "command-arguments" and
succ.asExpr() = o
)
}
/**
* A read of user-controlled field of the zentered/issue-forms-body-parser action.
*/
predicate zenteredIssueFormBodyParserSource(DataFlow::Node pred, DataFlow::Node succ) {
exists(StepsExpression o |
pred instanceof ZenteredIssueFormBodyParserSource and
o.getTarget() = pred.asExpr() and
o.getStepId() = pred.asExpr().(UsesStep).getId() and
(
not o instanceof JsonReferenceExpression and
o.getFieldName() = "data"
or
o instanceof JsonReferenceExpression and
o.(JsonReferenceExpression).getInnerExpression().matches("%.data")
) and
succ.asExpr() = o
)
}
/**
* A read of user-controlled field of the octokit/request-action action.
*/
predicate octokitRequestActionTaintStep(DataFlow::Node pred, DataFlow::Node succ) {
exists(StepsExpression o |
pred instanceof OctokitRequestActionSource and
o.getTarget() = pred.asExpr() and
o.getStepId() = pred.asExpr().(UsesStep).getId() and
succ.asExpr() = o and
(
not o instanceof JsonReferenceExpression and
o.getFieldName() = "data"
or
o instanceof JsonReferenceExpression and
o.(JsonReferenceExpression).getInnerExpression().matches("%.data") and
o.(JsonReferenceExpression)
.getAccessPath()
.matches([
"%.title",
"%.user.login",
"%.body",
"%.head.ref",
"%.head.repo.full_name",
"%.commit.author.email",
"%.commit.commiter.email",
"%.commit.message",
"%.email",
"%.name",
])
)
)
}
class TaintSteps extends AdditionalTaintStep {
override predicate step(DataFlow::Node node1, DataFlow::Node node2) {
dornyPathsFilterTaintStep(node1, node2) or
tjActionsChangedFilesTaintStep(node1, node2) or
tjActionsVerifyChangedFilesTaintStep(node1, node2) or
xt0rtedSlashCommandActionTaintStep(node1, node2) or
xt0rtedSlashCommandActionTaintStep(node1, node2) or
zenteredIssueFormBodyParserSource(node1, node2) or
octokitRequestActionTaintStep(node1, node2)
}
}

View File

@@ -1,15 +0,0 @@
/**
* Provides Actions-specific definitions for use in the data flow library.
* Implementation of https://github.com/github/codeql/blob/main/shared/dataflow/codeql/dataflow/DataFlow.qll
*/
private import codeql.dataflow.DataFlow
private import codeql.Locations
module ActionsDataFlow implements InputSig<Location> {
import DataFlowPrivate as Private
import DataFlowPublic
import Private
predicate neverSkipInPathGraph = Private::neverSkipInPathGraph/1;
}

View File

@@ -1,403 +0,0 @@
private import codeql.util.Unit
private import codeql.dataflow.DataFlow
private import codeql.actions.Ast
private import codeql.actions.Cfg as Cfg
private import codeql.Locations
private import codeql.actions.controlflow.BasicBlocks
private import DataFlowPublic
private import codeql.actions.dataflow.ExternalFlow
private import codeql.actions.dataflow.FlowSteps
private import codeql.actions.dataflow.FlowSources
class DataFlowSecondLevelScope = Unit;
cached
newtype TNode = TExprNode(DataFlowExpr e)
class OutNode extends ExprNode {
private DataFlowCall call;
OutNode() { call = this.getCfgNode() }
DataFlowCall getCall(ReturnKind kind) {
result = call and
kind instanceof NormalReturn
}
}
/**
* Not implemented
*/
class CastNode extends Node {
CastNode() { none() }
}
/**
* Not implemented
*/
class PostUpdateNode extends Node {
PostUpdateNode() { none() }
Node getPreUpdateNode() { none() }
}
predicate isParameterNode(ParameterNode p, DataFlowCallable c, ParameterPosition pos) {
p.isParameterOf(c, pos)
}
predicate isArgumentNode(ArgumentNode arg, DataFlowCall call, ArgumentPosition pos) {
arg.argumentOf(call, pos)
}
DataFlowCallable nodeGetEnclosingCallable(Node node) {
node = TExprNode(any(DataFlowExpr e | result = e.getScope()))
}
DataFlowType getNodeType(Node node) { any() }
predicate nodeIsHidden(Node node) { none() }
class DataFlowExpr extends Cfg::Node {
DataFlowExpr() {
this.getAstNode() instanceof Job or
this.getAstNode() instanceof Expression or
this.getAstNode() instanceof Uses or
this.getAstNode() instanceof Run or
this.getAstNode() instanceof Outputs or
this.getAstNode() instanceof Input or
this.getAstNode() instanceof ScalarValue
}
}
/**
* A call corresponds to a Uses steps where a composite action or a reusable workflow get called
*/
class DataFlowCall instanceof Cfg::Node {
DataFlowCall() { super.getAstNode() instanceof Uses }
/** Gets a textual representation of this element. */
string toString() { result = super.toString() }
string getName() { result = super.getAstNode().(Uses).getCallee() }
DataFlowCallable getEnclosingCallable() { result = super.getScope() }
/** Gets a best-effort total ordering. */
int totalorder() { none() }
/** Gets the location of this call. */
Location getLocation() { result = this.(Cfg::Node).getLocation() }
}
/**
* A Cfg scope that can be called
*/
class DataFlowCallable instanceof Cfg::CfgScope {
string toString() { result = super.toString() }
string getName() {
result = this.(ReusableWorkflowImpl).getResolvedPath() or
result = this.(CompositeActionImpl).getResolvedPath()
}
/** Gets a best-effort total ordering. */
int totalorder() { none() }
/** Gets the location of this callable. */
Location getLocation() { result = this.(Cfg::CfgScope).getLocation() }
}
newtype TReturnKind = TNormalReturn()
abstract class ReturnKind extends TReturnKind {
/** Gets a textual representation of this element. */
abstract string toString();
}
class NormalReturn extends ReturnKind, TNormalReturn {
override string toString() { result = "return" }
}
/** Gets a viable implementation of the target of the given `Call`. */
DataFlowCallable viableCallable(DataFlowCall c) { c.getName() = result.getName() }
/**
* Gets a node that can read the value returned from `call` with return kind
* `kind`.
*/
OutNode getAnOutNode(DataFlowCall call, ReturnKind kind) { call = result.getCall(kind) }
private newtype TDataFlowType = TUnknownDataFlowType()
/**
* A type for a data flow node.
*
* This may or may not coincide with any type system existing for the source
* language, but should minimally include unique types for individual closure
* expressions (typically lambdas).
*/
class DataFlowType extends TDataFlowType {
string toString() { result = "" }
}
string ppReprType(DataFlowType t) { none() }
predicate compatibleTypes(DataFlowType t1, DataFlowType t2) { any() }
predicate typeStrongerThan(DataFlowType t1, DataFlowType t2) { none() }
newtype TContent =
TFieldContent(string name) {
// We only use field flow for env, steps and jobs outputs
// not for accessing other context fields such as matrix or inputs
name = any(StepsExpression a).getFieldName() or
name = any(NeedsExpression a).getFieldName() or
name = any(JobsExpression a).getFieldName() or
name = any(EnvExpression a).getFieldName()
}
predicate forceHighPrecision(Content c) { c instanceof FieldContent }
class NodeRegion instanceof Unit {
string toString() { result = "NodeRegion" }
predicate contains(Node n) { none() }
int totalOrder() { result = 1 }
}
/**
* Holds if the nodes in `nr` are unreachable when the call context is `call`.
*/
predicate isUnreachableInCall(NodeRegion nr, DataFlowCall call) { none() }
class ContentApprox = ContentSet;
ContentApprox getContentApprox(Content c) { result = c }
/**
* Made a string to match the ArgumentPosition type.
*/
class ParameterPosition extends string {
ParameterPosition() {
exists(any(ReusableWorkflow w).getInput(this)) or
exists(any(CompositeAction a).getInput(this))
}
}
/**
* Made a string to match `With:` keys in the AST
*/
class ArgumentPosition extends string {
ArgumentPosition() { exists(any(Uses e).getArgumentExpr(this)) }
}
/**
*/
predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) { ppos = apos }
/**
* Holds if there is a local flow step between a ${{ steps.xxx.outputs.yyy }} expression accesing a step output field
* and the step output itself. But only for those cases where the step output is defined externally in a MaD Source
* specification. The reason for this is that we don't currently have a way to specify that a source starts with a
* non-empty access path so we cannot write a Source that stores the taint in a Content, we can only do that for steps
* (storeStep). The easiest thing is to add this local flow step that simulates a read step from the source node for a specific
* field name.
*/
predicate stepsCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(Uses astFrom, StepsExpression astTo |
madSource(nodeFrom, _, "output." + ["*", astTo.getFieldName()]) and
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
astTo.getTarget() = astFrom
)
}
/**
* Holds if there is a local flow step between a ${{ needs.xxx.outputs.yyy }} expression accesing a job output field
* and the step output itself. But only for those cases where the job (needs) output is defined externally in a MaD Source
* specification. The reason for this is that we don't currently have a way to specify that a source starts with a
* non-empty access path so we cannot write a Source that stores the taint in a Content, we can only do that for steps
* (storeStep). The easiest thing is to add this local flow step that simulates a read step from the source node for a specific
* field name.
*/
predicate needsCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(Uses astFrom, NeedsExpression astTo |
madSource(nodeFrom, _, "output." + astTo.getFieldName()) and
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
astTo.getTarget() = astFrom
)
}
/**
* Holds if there is a local flow step between a ${{}} expression accesing an input variable and the input itself
* e.g. ${{ inputs.foo }}
*/
predicate inputsCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(AstNode astFrom, InputsExpression astTo |
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
astTo.getTarget() = astFrom
)
}
/**
* Holds if there is a local flow step between a ${{}} expression accesing a matrix variable and the matrix itself
* e.g. ${{ matrix.foo }}
*/
predicate matrixCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(AstNode astFrom, MatrixExpression astTo |
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
astTo.getTarget() = astFrom
)
}
/**
* Holds if there is a local flow step between a ${{}} expression accesing an env var and the var definition itself
* e.g. ${{ env.foo }}
*/
predicate envCtxLocalStep(Node nodeFrom, Node nodeTo) {
exists(AstNode astFrom, EnvExpression astTo |
astFrom = nodeFrom.asExpr() and
astTo = nodeTo.asExpr() and
(
madSource(nodeFrom, _, "env." + astTo.getFieldName())
or
astTo.getTarget() = astFrom
)
)
}
/**
* Holds if there is a local flow step from `nodeFrom` to `nodeTo`.
* For Actions, we dont need SSA nodes since it should be already in SSA form
* Local flow steps are always between two nodes in the same Cfg scope.
*/
pragma[nomagic]
predicate localFlowStep(Node nodeFrom, Node nodeTo) {
stepsCtxLocalStep(nodeFrom, nodeTo) or
needsCtxLocalStep(nodeFrom, nodeTo) or
inputsCtxLocalStep(nodeFrom, nodeTo) or
matrixCtxLocalStep(nodeFrom, nodeTo) or
envCtxLocalStep(nodeFrom, nodeTo)
}
/**
* This is the local flow predicate that is used as a building block in global
* data flow.
*/
cached
predicate simpleLocalFlowStep(Node nodeFrom, Node nodeTo, string model) {
localFlowStep(nodeFrom, nodeTo) and model = ""
}
/**
* Holds if data can flow from `node1` to `node2` through a non-local step
* that does not follow a call edge. For example, a step through a global
* variable.
* We throw away the call context and let us jump to any location
* AKA teleport steps
* local steps are preferible since they are more predictable and easier to control
*/
predicate jumpStep(Node nodeFrom, Node nodeTo) { none() }
/**
* Holds if a Expression reads a field from a job (needs/jobs), step (steps) output via a read of `c` (fieldname)
*/
predicate ctxFieldReadStep(Node node1, Node node2, ContentSet c) {
exists(SimpleReferenceExpression access |
(
access instanceof NeedsExpression or
access instanceof StepsExpression or
access instanceof JobsExpression or
access instanceof EnvExpression
) and
c = any(FieldContent ct | ct.getName() = access.getFieldName()) and
node1.asExpr() = access.getTarget() and
node2.asExpr() = access
)
}
/**
* Holds if data can flow from `node1` to `node2` via a read of `c`. Thus,
* `node1` references an object with a content `c.getAReadContent()` whose
* value ends up in `node2`.
* Store steps without corresponding reads are pruned aggressively very early, since they can never contribute to a complete path.
*/
predicate readStep(Node node1, ContentSet c, Node node2) { ctxFieldReadStep(node1, node2, c) }
/**
* Stores an output expression (node1) into its OutputsStm node (node2)
* using the output variable name as the access path
*/
predicate fieldStoreStep(Node node1, Node node2, ContentSet c) {
exists(Outputs out, string fieldName |
node1.asExpr() = out.getOutputExpr(fieldName) and
node2.asExpr() = out and
c = any(FieldContent ct | ct.getName() = fieldName)
)
}
/**
* Holds if data can flow from `node1` to `node2` via a store into `c`. Thus,
* `node2` references an object with a content `c.getAStoreContent()` that
* contains the value of `node1`.
* Store steps without corresponding reads are pruned aggressively very early, since they can never contribute to a complete path.
*/
predicate storeStep(Node node1, ContentSet c, Node node2) {
fieldStoreStep(node1, node2, c) or
madStoreStep(node1, node2, c) or
envToOutputStoreStep(node1, node2, c) or
envToEnvStoreStep(node1, node2, c) or
commandToOutputStoreStep(node1, node2, c) or
commandToEnvStoreStep(node1, node2, c)
}
/**
* Holds if values stored inside content `c` are cleared at node `n`. For example,
* any value stored inside `f` is cleared at the pre-update node associated with `x`
* in `x.f = newValue`.
*/
predicate clearsContent(Node n, ContentSet c) { none() }
/**
* Holds if the value that is being tracked is expected to be stored inside content `c`
* at node `n`.
*/
predicate expectsContent(Node n, ContentSet c) { none() }
/**
* Holds if flow is allowed to pass from parameter `p` and back to itself as a
* side-effect, resulting in a summary from `p` to itself.
*
* One example would be to allow flow like `p.foo = p.bar;`, which is disallowed
* by default as a heuristic.
*/
predicate allowParameterReturnInSelf(ParameterNode p) { none() }
predicate localMustFlowStep(Node nodeFrom, Node nodeTo) { localFlowStep(nodeFrom, nodeTo) }
private newtype TLambdaCallKind = TNone()
class LambdaCallKind = TLambdaCallKind;
/** Holds if `creation` is an expression that creates a lambda of kind `kind` for `c`. */
predicate lambdaCreation(Node creation, LambdaCallKind kind, DataFlowCallable c) { none() }
/** Holds if `call` is a lambda call of kind `kind` where `receiver` is the lambda expression. */
predicate lambdaCall(DataFlowCall call, LambdaCallKind kind, Node receiver) { none() }
/** Extra data-flow steps needed for lambda flow analysis. */
predicate additionalLambdaFlowStep(Node nodeFrom, Node nodeTo, boolean preservesValue) { none() }
/**
* Since our model is so simple, we dont want to compress the local flow steps.
* This compression is normally done to not show SSA steps, casts, etc.
*/
predicate neverSkipInPathGraph(Node node) { any() }
predicate knownSourceModel(Node source, string model) { none() }
predicate knownSinkModel(Node sink, string model) { none() }

View File

@@ -1,194 +0,0 @@
private import codeql.dataflow.DataFlow
private import codeql.actions.Ast
private import codeql.actions.Cfg as Cfg
private import codeql.Locations
private import DataFlowPrivate
class Node extends TNode {
/** Gets a textual representation of this element. */
string toString() { none() }
Location getLocation() { none() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
this.getLocation().hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
AstNode asExpr() { none() }
}
/**
* Any Ast Expression.
* UsesExpr, RunExpr, ArgumentExpr, VarAccessExpr, ...
*/
class ExprNode extends Node, TExprNode {
private DataFlowExpr expr;
ExprNode() { this = TExprNode(expr) }
Cfg::Node getCfgNode() { result = expr }
override string toString() { result = expr.toString() }
override Location getLocation() { result = expr.getLocation() }
override AstNode asExpr() { result = expr.getAstNode() }
}
/**
* Reusable workflow input nodes
*/
class ParameterNode extends ExprNode {
private Input input;
ParameterNode() { this.asExpr() = input }
predicate isParameterOf(DataFlowCallable c, ParameterPosition pos) {
input = c.(ReusableWorkflow).getInput(pos) or
input = c.(CompositeAction).getInput(pos)
}
override string toString() { result = "input " + input.toString() }
override Location getLocation() { result = input.getLocation() }
Input getInput() { result = input }
}
/**
* A call to a data flow callable (Uses).
*/
class CallNode extends ExprNode {
private DataFlowCall call;
CallNode() { this.getCfgNode() instanceof DataFlowCall }
DataFlowCallable getCalleeNode() { result = viableCallable(this.getCfgNode()) }
}
/**
* An argument to a Uses step (call).
*/
class ArgumentNode extends ExprNode {
ArgumentNode() { this.getCfgNode().getAstNode() = any(Uses e).getArgumentExpr(_) }
predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
this.getCfgNode() = call.(Cfg::Node).getASuccessor+() and
call.(Cfg::Node).getAstNode() =
any(Uses e | e.getArgumentExpr(pos) = this.getCfgNode().getAstNode())
}
}
/**
* Reusable workflow output nodes
*/
class ReturnNode extends ExprNode {
private Outputs outputs;
ReturnNode() {
this.asExpr() = outputs and
(
exists(ReusableWorkflow w | w.getOutputs() = outputs) or
exists(CompositeAction a | a.getOutputs() = outputs)
)
}
ReturnKind getKind() { result = TNormalReturn() }
override string toString() { result = "output " + outputs.toString() }
override Location getLocation() { result = outputs.getLocation() }
}
/** Gets the node corresponding to `e`. */
Node exprNode(DataFlowExpr e) { result = TExprNode(e) }
/**
* An entity that represents a set of `Content`s.
*
* The set may be interpreted differently depending on whether it is
* stored into (`getAStoreContent`) or read from (`getAReadContent`).
*/
class ContentSet instanceof Content {
/** Gets a content that may be stored into when storing into this set. */
Content getAStoreContent() { result = this }
/** Gets a content that may be read from when reading from this set. */
Content getAReadContent() { result = this }
/** Gets a textual representation of this content set. */
string toString() { result = super.toString() }
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
super.hasLocationInfo(filepath, startline, startcolumn, endline, endcolumn)
}
}
/**
* A reference contained in an object. Examples include instance fields, the
* contents of a collection object, the contents of an array or pointer.
*/
class Content extends TContent {
/** Gets the type of the contained data for the purpose of type pruning. */
DataFlowType getType() { any() }
/** Gets a textual representation of this element. */
abstract string toString();
/**
* Holds if this element is at the specified location.
* The location spans column `startcolumn` of line `startline` to
* column `endcolumn` of line `endline` in file `filepath`.
* For more information, see
* [Locations](https://codeql.github.com/docs/writing-codeql-queries/providing-locations-in-codeql-queries/).
*/
predicate hasLocationInfo(
string filepath, int startline, int startcolumn, int endline, int endcolumn
) {
filepath = "" and startline = 0 and startcolumn = 0 and endline = 0 and endcolumn = 0
}
}
/** A field of an object, for example an instance variable. */
class FieldContent extends Content, TFieldContent {
private string name;
FieldContent() { this = TFieldContent(name) }
/** Gets the name of the field. */
string getName() { result = name }
override string toString() { result = name }
}
predicate hasLocalFlow(Node n1, Node n2) {
n1 = n2 or
simpleLocalFlowStep(n1, n2, _) or
exists(ContentSet c | ctxFieldReadStep(n1, n2, c))
}
predicate hasLocalFlowExpr(AstNode n1, AstNode n2) {
exists(Node dn1, Node dn2 |
dn1.asExpr() = n1 and
dn2.asExpr() = n2 and
hasLocalFlow(dn1, dn2)
)
}

View File

@@ -1,24 +0,0 @@
/**
* This module provides extensible predicates for defining MaD models.
*/
/**
* Holds if a source model exists for the given parameters.
*/
extensible predicate actionsSourceModel(
string action, string version, string output, string kind, string provenance
);
/**
* Holds if a summary model exists for the given parameters.
*/
extensible predicate actionsSummaryModel(
string action, string version, string input, string output, string kind, string provenance
);
/**
* Holds if a sink model exists for the given parameters.
*/
extensible predicate actionsSinkModel(
string action, string version, string input, string kind, string provenance
);

Some files were not shown because too many files have changed in this diff Show More