Skip to content

Commit f0cfed0

Browse files
committed
actions: generate lockfile-pinned data extension during extraction
Wire the lockfile-extension-generator into the Actions extractor autobuild so that codeql database create automatically emits the pinnedByLockfileDataModel data extension from a repository's .github/workflows/actions.lock. The extension is written into the database as a self-contained model pack under <db>/lockfile-extension (codeql/actions-lockfile-pins). A new generate-lockfile-extension.sh runs after JS extraction: it locates the lockfile relative to the captured source root, resolves the generator (prebuilt binary if shipped, else builds from source when a Go toolchain is present), and writes the pack. It is a clean no-op when the repository has no lockfile, so it is safe to run against every database. CodeQL does not auto-apply extensions carried inside a database, so analysis still adds the pack explicitly via --model-packs codeql/actions-lockfile-pins (--additional-packs <db>/lockfile-extension). Wiring that into the analysis harness is the remaining step and lives outside this repo. Verified end to end locally by overlaying the modified extractor into the CLI bundle: database create emits the extension, and analyze suppresses a lockfile-pinned short-tag ref (uses: owner/action@v4 resolved to v4.3.1) while still reporting refs not covered by the lockfile.
1 parent 75734f7 commit f0cfed0

3 files changed

Lines changed: 122 additions & 0 deletions

File tree

actions/extractor/tools/autobuild.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ else
3737
export LGTM_INDEX_FILTERS
3838
fi
3939

40+
# Capture the source root before handing off to the JavaScript autobuilder,
41+
# which may change the working directory. The Actions extractor runs from the
42+
# source root, so `pwd` here is the root of the repository being analysed.
43+
ACTIONS_SRC_ROOT="$(pwd)"
44+
4045
# Find the JavaScript extractor directory via `codeql resolve extractor`.
4146
CODEQL_EXTRACTOR_JAVASCRIPT_ROOT="$("${CODEQL_DIST}/codeql" resolve extractor --language javascript)"
4247
export CODEQL_EXTRACTOR_JAVASCRIPT_ROOT
@@ -55,3 +60,12 @@ env CODEQL_EXTRACTOR_JAVASCRIPT_DIAGNOSTIC_DIR="${CODEQL_EXTRACTOR_ACTIONS_DIAGN
5560
CODEQL_EXTRACTOR_JAVASCRIPT_TRAP_DIR="${CODEQL_EXTRACTOR_ACTIONS_TRAP_DIR}" \
5661
CODEQL_EXTRACTOR_JAVASCRIPT_WIP_DATABASE="${CODEQL_EXTRACTOR_ACTIONS_WIP_DATABASE}" \
5762
"${JAVASCRIPT_AUTO_BUILD}"
63+
64+
# Generate the lockfile-pinned data extension from the repository's Actions
65+
# lockfile, if present. This is a no-op for repositories without a lockfile.
66+
GENERATE_LOCKFILE_EXTENSION="$(CDPATH= cd "$(dirname "$0")" && pwd)/generate-lockfile-extension.sh"
67+
if [ -x "${GENERATE_LOCKFILE_EXTENSION}" ]; then
68+
echo "Generating lockfile-pinned data extension."
69+
"${GENERATE_LOCKFILE_EXTENSION}" "${ACTIONS_SRC_ROOT}" || \
70+
echo "Lockfile-pinned data extension generation failed; continuing without it." >&2
71+
fi
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/bin/sh
2+
3+
# Generates a CodeQL data extension that records the `uses:` references pinned by
4+
# the repository's GitHub Actions lockfile (`.github/workflows/actions.lock`),
5+
# populating the `pinnedByLockfileDataModel` extensible predicate consumed by the
6+
# `actions/unpinned-tag` query.
7+
#
8+
# It is invoked by the Actions extractor autobuild during `codeql database
9+
# create`. The generated extension is written into the database as a
10+
# self-contained model pack under:
11+
#
12+
# <database>/lockfile-extension/
13+
# qlpack.yml
14+
# ext/pinned_by_lockfile.model.yml
15+
#
16+
# CodeQL does not auto-apply extensions carried inside a database, so analysis
17+
# must add this pack explicitly, e.g.:
18+
#
19+
# codeql database analyze <db> ... \
20+
# --additional-packs <db>/lockfile-extension \
21+
# --model-packs codeql/actions-lockfile-pins
22+
#
23+
# The step is a clean no-op when the repository has no lockfile, so it is safe to
24+
# run against every database.
25+
26+
set -eu
27+
28+
SRC_ROOT="${1:?usage: generate-lockfile-extension.sh <source-root>}"
29+
LOCKFILE="${SRC_ROOT}/.github/workflows/actions.lock"
30+
31+
if [ ! -f "${LOCKFILE}" ]; then
32+
echo "No Actions lockfile at '${LOCKFILE}'; skipping lockfile-pinned extension."
33+
exit 0
34+
fi
35+
36+
if [ -z "${CODEQL_EXTRACTOR_ACTIONS_WIP_DATABASE:-}" ]; then
37+
echo "CODEQL_EXTRACTOR_ACTIONS_WIP_DATABASE is not set; cannot write lockfile extension." >&2
38+
exit 0
39+
fi
40+
41+
SCRIPT_DIR="$(CDPATH= cd "$(dirname "$0")" && pwd)"
42+
GEN_DIR="${SCRIPT_DIR}/lockfile-extension-generator"
43+
44+
PACK_DIR="${CODEQL_EXTRACTOR_ACTIONS_WIP_DATABASE}/lockfile-extension"
45+
EXT_DIR="${PACK_DIR}/ext"
46+
mkdir -p "${EXT_DIR}"
47+
48+
# Resolve the generator: prefer a prebuilt binary shipped with the extractor;
49+
# otherwise build from source with the Go toolchain if it is available.
50+
GEN_BIN="${GEN_DIR}/bin/lockfile-extension-generator"
51+
RUN_GENERATOR=""
52+
if [ -x "${GEN_BIN}" ]; then
53+
RUN_GENERATOR="${GEN_BIN}"
54+
elif command -v go >/dev/null 2>&1; then
55+
BUILT_BIN="${CODEQL_EXTRACTOR_ACTIONS_SCRATCH_DIR:-${PACK_DIR}}/lockfile-extension-generator"
56+
echo "Building lockfile-extension-generator from source with 'go build'."
57+
( cd "${GEN_DIR}" && go build -o "${BUILT_BIN}" . )
58+
RUN_GENERATOR="${BUILT_BIN}"
59+
else
60+
echo "No lockfile-extension-generator binary and no Go toolchain; skipping lockfile-pinned extension." >&2
61+
exit 0
62+
fi
63+
64+
"${RUN_GENERATOR}" "${SRC_ROOT}" "${EXT_DIR}/pinned_by_lockfile.model.yml"
65+
66+
cat > "${PACK_DIR}/qlpack.yml" <<'EOF'
67+
name: codeql/actions-lockfile-pins
68+
version: 0.0.1
69+
library: true
70+
warnOnImplicitThis: true
71+
# Generated per-database from the repository's Actions lockfile by the Actions
72+
# extractor. Apply it at analysis time with `--model-packs
73+
# codeql/actions-lockfile-pins` (and `--additional-packs <db>/lockfile-extension`).
74+
extensionTargets:
75+
codeql/actions-all: "*"
76+
dataExtensions:
77+
- ext/*.model.yml
78+
EOF
79+
80+
echo "Wrote lockfile-pinned data extension to '${PACK_DIR}'."

actions/extractor/tools/lockfile-extension-generator/README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,34 @@ rows ship as a data-extension model pack applied at analysis time via
2323
same parsing core can later feed an extractor-native relation without changing
2424
the query.
2525

26+
## Extractor integration
27+
28+
The Actions extractor runs this generator automatically during `codeql database
29+
create` (see `../generate-lockfile-extension.sh`, invoked from
30+
`../autobuild.sh`). When the repository has a lockfile, the extractor writes a
31+
self-contained model pack into the database:
32+
33+
```
34+
<database>/lockfile-extension/
35+
qlpack.yml # name: codeql/actions-lockfile-pins
36+
ext/pinned_by_lockfile.model.yml # generated pinnedByLockfileDataModel rows
37+
```
38+
39+
CodeQL does not auto-apply extensions carried inside a database, so analysis
40+
must add this pack explicitly:
41+
42+
```
43+
codeql database analyze <db> \
44+
codeql/actions-queries:Security/CWE-829/UnpinnedActionsTag.ql \
45+
--additional-packs <db>/lockfile-extension \
46+
--model-packs codeql/actions-lockfile-pins
47+
```
48+
49+
Wiring that flag into the analysis harness (e.g. the CodeQL Action) is the
50+
remaining integration step and lives outside this repository. Generation is a
51+
clean no-op for repositories without a lockfile, so the extractor step is safe
52+
to run unconditionally.
53+
2654
## Ref normalization
2755

2856
A lockfile records the *resolved* ref for each dependency — the CLI prefers a

0 commit comments

Comments
 (0)