Skip to content

Commit fbe00de

Browse files
committed
actions: emit empty data list for zero-row lockfile extension
A lockfile that pins no repo-level actions (e.g. only sub-path actions like github/codeql-action/init@v3, which parsePin skips) produced a bare `data:` (YAML null) extension, which CodeQL's `resolve extensions-by-pack` rejects and aborts the analysis. Emit `data: []` for the zero-row case, matching the repo convention, and note the narrow transitive-per-path over-suppression edge in the change note.
1 parent f64f112 commit fbe00de

3 files changed

Lines changed: 33 additions & 1 deletion

File tree

actions/extractor/tools/lockfile-extension-generator/generator.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,16 @@ func renderExtension(rows []row) string {
112112
b.WriteString(" - addsTo:\n")
113113
b.WriteString(" pack: codeql/actions-all\n")
114114
b.WriteString(" extensible: pinnedByLockfileDataModel\n")
115+
// `data` must be a YAML sequence. When there are no rows we must emit an
116+
// explicit empty list (`data: []`); a bare `data:` parses as null, which
117+
// CodeQL's extension loader rejects (`resolve extensions-by-pack` fails and
118+
// aborts the whole analysis). Zero rows is reachable whenever a lockfile is
119+
// present but pins no repo-level actions (e.g. only sub-path actions such as
120+
// `github/codeql-action/init@v3`, which `parsePin` intentionally skips).
121+
if len(rows) == 0 {
122+
b.WriteString(" data: []\n")
123+
return b.String()
124+
}
115125
b.WriteString(" data:\n")
116126
for _, r := range rows {
117127
fmt.Fprintf(&b, " - [%q, %q, %q]\n", r.workflowPath, r.nwo, r.ref)

actions/extractor/tools/lockfile-extension-generator/generator_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"os"
5+
"strings"
56
"testing"
67
)
78

@@ -89,3 +90,24 @@ func TestRenderExtensionMatchesGolden(t *testing.T) {
8990
t.Errorf("rendered extension does not match testdata/expected.yml:\n--- got ---\n%s", got)
9091
}
9192
}
93+
94+
func TestRenderExtensionZeroRowsEmitsEmptyList(t *testing.T) {
95+
// A lockfile that pins only sub-path actions (skipped by parsePin) or an
96+
// empty `workflows` map yields zero rows while a lockfile is present. The
97+
// rendered extension must use `data: []`, not a bare `data:` (YAML null),
98+
// which CodeQL's extension loader rejects and which aborts the analysis.
99+
rows, err := rowsFromLockfile([]byte("version: \"v0.0.2\"\nworkflows:\n \".github/workflows/ci.yml\":\n - \"github/codeql-action/init@v3\"\ndependencies: {}\n"))
100+
if err != nil {
101+
t.Fatalf("rowsFromLockfile: %v", err)
102+
}
103+
if len(rows) != 0 {
104+
t.Fatalf("expected zero rows for a sub-path-only lockfile, got %d: %v", len(rows), rows)
105+
}
106+
got := renderExtension(rows)
107+
if !strings.Contains(got, "data: []") {
108+
t.Errorf("zero-row extension must emit `data: []`, got:\n%s", got)
109+
}
110+
if strings.Contains(got, "data:\n") {
111+
t.Errorf("zero-row extension must not emit a null `data:` line, got:\n%s", got)
112+
}
113+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
---
22
category: minorAnalysis
33
---
4-
* The `actions/unpinned-tag` query no longer reports `uses:` references that are recorded as pinned by a repository's Actions lockfile (`.github/workflows/actions.lock`) via the new `pinnedByLockfileDataModel` extensible predicate. The CodeQL Actions extractor generates this data at database-creation time into a database-local model pack (`codeql/actions-lockfile-pins`); references are suppressed only when that pack is supplied to analysis (for example via `--model-packs`), so behaviour is unchanged for repositories without a lockfile or analyses that do not opt in.
4+
* The `actions/unpinned-tag` query no longer reports `uses:` references that are recorded as pinned by a repository's Actions lockfile (`.github/workflows/actions.lock`) via the new `pinnedByLockfileDataModel` extensible predicate. The CodeQL Actions extractor generates this data at database-creation time into a database-local model pack (`codeql/actions-lockfile-pins`); references are suppressed only when that pack is supplied to analysis (for example via `--model-packs`), so behaviour is unchanged for repositories without a lockfile or analyses that do not opt in. Because the lockfile keys its pins transitively by workflow path, a stale lockfile could in rare cases suppress a directly-written unpinned tag that shares an action and major version with a transitively-pinned dependency of the same workflow; keeping the lockfile current avoids this.

0 commit comments

Comments
 (0)