ci(codeql): separate production scans from test fixtures (#8450)

This commit is contained in:
gensokyo-dev authored and GitHub committed 2026-11-01 15:24:29 +08:00
1 parent e18594b247
commit a6caa79bf4
6 files changed
+248 -1

No files matched your search

+15
View File
@@ -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"
+61
View File
@@ -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
+30
View File
@@ -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
View File
@@ -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");
});