ci(codeql): separate production scans from test fixtures (#8450)
This commit is contained in:
1 parent
e18594b247
commit
a6caa79bf4
6 files changed
+248
-1
No files matched your search
@@ -0,0 +1,15 @@
|
||||
name: REA production code
|
||||
|
||||
# Match the existing default-setup scan's local-input threat model.
|
||||
threat-models: [local]
|
||||
|
||||
# Keep the default query suite and all production roots, including bridge/,
|
||||
# install.sh, runtime scripts, release controllers, and GitHub Actions.
|
||||
# Exclude test inputs at extraction time so they cannot taint production callers.
|
||||
paths-ignore:
|
||||
- tests
|
||||
- scripts/fixtures
|
||||
- "**/*.test.*"
|
||||
- "**/*.fixture.*"
|
||||
- "**/test_*.py"
|
||||
- "**/*_test.py"
|
||||
@@ -0,0 +1,61 @@
|
||||
name: CodeQL production
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: "37 5 * * 1"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: codeql-production-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
# A repository administrator must disable default setup before enabling
|
||||
# this workflow; default setup rejects advanced CodeQL result uploads.
|
||||
if: vars.REA_CODEQL_ADVANCED == 'true'
|
||||
name: Analyze production (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 1
|
||||
matrix:
|
||||
language: [javascript-typescript, python, actions]
|
||||
steps:
|
||||
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Initialize production analysis
|
||||
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: none
|
||||
config-file: .github/codeql/production.yml
|
||||
threads: 2
|
||||
ram: 4096
|
||||
- name: Analyze production code
|
||||
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
output: ${{ runner.temp }}/codeql-production-results
|
||||
threads: 2
|
||||
ram: 4096
|
||||
- name: Retain complete analysis evidence
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: codeql-production-${{ matrix.language }}
|
||||
path: ${{ runner.temp }}/codeql-production-results
|
||||
if-no-files-found: error
|
||||
retention-days: 7
|
||||
@@ -365,6 +365,36 @@ Do not report the warm result as a cold-suite improvement, and do not enable
|
||||
the cache in coverage or benchmark CI without first showing that its
|
||||
instrumentation remains equivalent.
|
||||
|
||||
## CodeQL production scope
|
||||
|
||||
[The production configuration](../.github/codeql/production.yml) excludes
|
||||
`tests/`, `scripts/fixtures/`, `*.test.*`, `*.fixture.*`, `test_*.py`, and `*_test.py` inputs
|
||||
during extraction. It retains the default queries and the existing `local`
|
||||
threat model. Production source, bridges, runtime scripts, installation and
|
||||
release controllers, and Actions remain in scope for the configured languages.
|
||||
Verification scripts remain included unless they match an explicit test path;
|
||||
excluding the entire `scripts/` directory would hide shipped entrypoints.
|
||||
These exclusions do not change which behavioral tests CI executes. CodeQL's
|
||||
JavaScript/TypeScript, Python, and Actions lanes do not add shell, Java, or Swift
|
||||
analysis; their existing checks remain responsible for those languages.
|
||||
|
||||
[The production workflow](../.github/workflows/codeql.yml) is inactive until a
|
||||
repository administrator disables CodeQL **default setup** and sets the Actions
|
||||
repository variable `REA_CODEQL_ADVANCED` to `true`. Disable default setup first:
|
||||
it rejects advanced CodeQL uploads. Then enable the variable and manually run
|
||||
**CodeQL production** on `main`; verify all three analyses complete before
|
||||
relying on the new scope. While the variable is unset, the existing default
|
||||
setup continues scanning. Adding the configuration file alone does not change
|
||||
default setup in this user-owned repository.
|
||||
|
||||
Each analysis uses two threads and a 4096 MiB CodeQL memory budget; language
|
||||
jobs run one at a time. Complete SARIF reports are retained for seven days.
|
||||
For a scope comparison, use the same source SHA, CodeQL bundle, default query
|
||||
suite, and `local` threat model on both runs. Change only `paths-ignore`, inspect
|
||||
the extracted-file inventory and diagnostics, and retain both complete reports.
|
||||
Alert counts do not establish a false-positive rate or a speedup. Report elapsed
|
||||
time, CPU time, and process-family memory with their measurement limits.
|
||||
|
||||
## Choose verification for the change
|
||||
|
||||
`test:local` is fast source-level feedback. For the strongest practical
|
||||
|
||||
+4
-1
@@ -12,6 +12,7 @@ import { planTestSelection } from "./test-selection.mjs";
|
||||
// Actionlint validates workflow syntax only. The semantic suite asserts these
|
||||
// workflows' release, publication and self-hosted runner safeguards.
|
||||
const GUARDED_WORKFLOWS = new Set([
|
||||
".github/workflows/codeql.yml",
|
||||
".github/workflows/pr-title.yml",
|
||||
".github/workflows/pr-readiness.yml",
|
||||
".github/workflows/ci-full-pr.yml",
|
||||
@@ -275,10 +276,12 @@ if (tier === "reused") {
|
||||
for (const owner of owners) selected[owner] = true;
|
||||
if (
|
||||
file.startsWith(".github/workflows/") ||
|
||||
file.startsWith(".github/codeql/") ||
|
||||
file === ".github/actionlint.yaml"
|
||||
) {
|
||||
selected.workflows = true;
|
||||
if (GUARDED_WORKFLOWS.has(file)) selected.code = true;
|
||||
if (GUARDED_WORKFLOWS.has(file) || file.startsWith(".github/codeql/"))
|
||||
selected.code = true;
|
||||
if (file !== ".github/workflows/ci.yml") continue;
|
||||
}
|
||||
// The Notes example is also the source-owned agent evaluation fixture.
|
||||
|
||||
@@ -1396,6 +1396,14 @@ it.each([
|
||||
".github/workflows/pr-readiness.yml",
|
||||
{ workflows: true, code: true, package: false, website: false },
|
||||
],
|
||||
[
|
||||
".github/workflows/codeql.yml",
|
||||
{ workflows: true, code: true, runtime: false, package: false },
|
||||
],
|
||||
[
|
||||
".github/codeql/production.yml",
|
||||
{ workflows: true, code: true, runtime: false, package: false },
|
||||
],
|
||||
[
|
||||
".github/workflows/real-ghidra-windows.yml",
|
||||
{ workflows: true, code: true, package: false, website: false },
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { matchesGlob } from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
import { parse } from "yaml";
|
||||
import { z } from "zod";
|
||||
|
||||
const github = new URL("../../../.github/", import.meta.url);
|
||||
const configuration = z
|
||||
.strictObject({
|
||||
name: z.string(),
|
||||
"threat-models": z.array(z.string()),
|
||||
"paths-ignore": z.array(z.string()),
|
||||
})
|
||||
.parse(
|
||||
parse(await readFile(new URL("codeql/production.yml", github), "utf8")),
|
||||
);
|
||||
|
||||
const excluded = (path: string): boolean =>
|
||||
configuration["paths-ignore"].some(
|
||||
(pattern) =>
|
||||
path === pattern ||
|
||||
path.startsWith(`${pattern}/`) ||
|
||||
matchesGlob(path, pattern),
|
||||
);
|
||||
|
||||
it.each([
|
||||
"tests/fixtures/fakeHopper.mjs",
|
||||
"tests/conformance/ghidra/riscv-checks.mjs",
|
||||
"tests/support/analysis-efficiency/resource-run.py",
|
||||
"scripts/fixtures/http3-lab.py",
|
||||
"scripts/fixtures/managed/pe.mjs",
|
||||
"src/process/ExecFileOutput.test.ts",
|
||||
"src/process/ProcessOwnership.fixture.ts",
|
||||
"scripts/lib/http3-verifier-lifecycle.test.mjs",
|
||||
"bridge/pwndbg/test_core_context.py",
|
||||
"bridge/example_test.py",
|
||||
])("excludes test-only input %s", (path) => {
|
||||
expect(excluded(path)).toBe(true);
|
||||
});
|
||||
|
||||
it.each([
|
||||
"src/process/ExecFileOutput.ts",
|
||||
"src/filesystem/RegularFile.ts",
|
||||
"src/application/ClientConfigurationPublication.ts",
|
||||
"bridge/pwndbg/launch.py",
|
||||
"bridge/pwntools/layout_impl.py",
|
||||
"bridge/native/rea_lldb_tracer.py",
|
||||
"bridge/ghidra/ReaGhidraBridge.java",
|
||||
"install.sh",
|
||||
"scripts/rea.mjs",
|
||||
"scripts/prepack.mjs",
|
||||
"scripts/hopper-demo-x11.py",
|
||||
"scripts/ci/release-recovery.mjs",
|
||||
"scripts/verify-package-install.mjs",
|
||||
"scripts/verify-package-update.mjs",
|
||||
"scripts/verify-windows-native-artifact.mjs",
|
||||
".github/workflows/release.yml",
|
||||
".github/actions/publish-mcp/action.yml",
|
||||
])("retains production or release input %s", (path) => {
|
||||
expect(excluded(path)).toBe(false);
|
||||
});
|
||||
|
||||
it("retains all packaged entrypoints and the existing local-input threat model", async () => {
|
||||
const manifest = z
|
||||
.object({ files: z.array(z.string()) })
|
||||
.parse(
|
||||
JSON.parse(
|
||||
await readFile(
|
||||
new URL("../../../package.json", import.meta.url),
|
||||
"utf8",
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(manifest.files.filter(excluded)).toEqual([]);
|
||||
expect(configuration["threat-models"]).toEqual(["local"]);
|
||||
});
|
||||
|
||||
it("uses the scope in a bounded workflow gated on administrator activation", async () => {
|
||||
const workflow = z
|
||||
.object({
|
||||
permissions: z.object({ contents: z.literal("read") }),
|
||||
jobs: z.object({
|
||||
analyze: z.object({
|
||||
if: z.string(),
|
||||
permissions: z.object({
|
||||
contents: z.literal("read"),
|
||||
"security-events": z.literal("write"),
|
||||
}),
|
||||
strategy: z.object({
|
||||
"max-parallel": z.literal(1),
|
||||
matrix: z.object({ language: z.array(z.string()) }),
|
||||
}),
|
||||
steps: z.array(
|
||||
z.object({
|
||||
uses: z.string(),
|
||||
with: z.record(z.string(), z.unknown()).optional(),
|
||||
}),
|
||||
),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
.parse(
|
||||
parse(await readFile(new URL("workflows/codeql.yml", github), "utf8")),
|
||||
);
|
||||
const job = workflow.jobs.analyze;
|
||||
expect(job.if).toBe("vars.REA_CODEQL_ADVANCED == 'true'");
|
||||
expect(job.strategy.matrix.language).toEqual([
|
||||
"javascript-typescript",
|
||||
"python",
|
||||
"actions",
|
||||
]);
|
||||
const init = job.steps.find((step) =>
|
||||
step.uses.startsWith("github/codeql-action/init@"),
|
||||
);
|
||||
expect(init?.with).toMatchObject({
|
||||
"config-file": ".github/codeql/production.yml",
|
||||
"build-mode": "none",
|
||||
threads: 2,
|
||||
ram: 4096,
|
||||
});
|
||||
const analyze = job.steps.find((step) =>
|
||||
step.uses.startsWith("github/codeql-action/analyze@"),
|
||||
);
|
||||
expect(analyze?.with).toMatchObject({
|
||||
category: "/language:${{ matrix.language }}",
|
||||
threads: 2,
|
||||
ram: 4096,
|
||||
});
|
||||
expect(analyze?.with).not.toHaveProperty("upload");
|
||||
});
|
||||
Reference in new issue
Block a user