ci: build on the shared ci-workflows node.yaml, remove GitHub workflows
Gitea CI runs the uv script-quality and source-header checks, typecheck, lint, check and bundle through KofTwentyTwo/ci-workflows node.yaml (Node 24, pinned uv). Removes the retired GitHub Actions workflows (ci, codeql, codeql-analysis, pr-gates, security) and the CodeQL-only scripts. The EX-0015 uri-js guard keeps its lock/integrity/license pins; its check of the pr-gates.yml allowlist entry goes with that file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
220cd529f1
commit
57670e9891
12 files changed
+35
-553
No files matched your search
@@ -0,0 +1,23 @@
|
||||
# Copyright 2026 Kingsrook, LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: build
|
||||
on:
|
||||
push:
|
||||
jobs:
|
||||
node:
|
||||
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/node.yaml@main
|
||||
with:
|
||||
node-version: '24'
|
||||
install: npm ci --ignore-scripts
|
||||
uv: true
|
||||
commands: |
|
||||
uv sync --locked
|
||||
uv run --locked --no-sync python .github/scripts/test-script-quality.py
|
||||
uv run --locked --no-sync python .github/scripts/check-script-quality.py
|
||||
uv run --locked --no-sync python .github/scripts/test-source-headers.py
|
||||
uv run --locked --no-sync python .github/scripts/check-source-headers.py
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
npm run check
|
||||
npm run bundle
|
||||
@@ -1,108 +0,0 @@
|
||||
# Copyright 2026 Kingsrook, LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""Reject missing/malformed CodeQL evidence before enforcing the severity gate."""
|
||||
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import TypedDict
|
||||
|
||||
RuleProperties = TypedDict("RuleProperties", {"security-severity": str | float | None}, total=False)
|
||||
|
||||
|
||||
class Rule(TypedDict):
|
||||
"""Keep the rule identity and optional severity metadata used by the gate."""
|
||||
|
||||
id: str
|
||||
properties: RuleProperties
|
||||
|
||||
|
||||
class Component(TypedDict, total=False):
|
||||
"""Describe CodeQL driver or extension rule metadata."""
|
||||
|
||||
name: str
|
||||
rules: list[Rule]
|
||||
|
||||
|
||||
class Tool(TypedDict, total=False):
|
||||
"""Group the driver and optional extension rule catalogs."""
|
||||
|
||||
driver: Component
|
||||
extensions: list[Component]
|
||||
|
||||
|
||||
class Result(TypedDict, total=False):
|
||||
"""Describe only the result fields consumed by the severity gate."""
|
||||
|
||||
ruleId: str
|
||||
rule: dict[str, str]
|
||||
suppressions: list[object]
|
||||
|
||||
|
||||
class Run(TypedDict, total=False):
|
||||
"""Retain invocation success and findings for one analyzed language."""
|
||||
|
||||
tool: Tool
|
||||
results: list[Result]
|
||||
invocations: list[dict[str, bool]]
|
||||
|
||||
|
||||
class Report(TypedDict, total=False):
|
||||
"""Represent SARIF fields while retaining the explicit runtime validation."""
|
||||
|
||||
version: str
|
||||
runs: list[Run]
|
||||
|
||||
|
||||
MAX_SEVERITY = 10
|
||||
|
||||
|
||||
def check(directory: str | Path, language: str, threshold: float) -> bool: # noqa: PLR0912 -- Keep the ordered evidence checks together; none may be skipped.
|
||||
"""Validate complete CodeQL evidence and report findings at the threshold."""
|
||||
if not math.isfinite(threshold) or not 0 <= threshold <= MAX_SEVERITY:
|
||||
raise ValueError("Invalid security severity threshold")
|
||||
canonical = {"java-kotlin": "java", "javascript-typescript": "javascript", "c-cpp": "cpp"}.get(language, language)
|
||||
files = sorted(Path(directory).glob("*.sarif"))
|
||||
if not canonical or not any(path.name == canonical + ".sarif" for path in files):
|
||||
raise ValueError("Missing expected CodeQL SARIF report for " + language)
|
||||
blocking: list[tuple[float, str]] = []
|
||||
for path in files:
|
||||
report: Report = json.loads(path.read_text(encoding="utf-8"))
|
||||
runs = report.get("runs")
|
||||
if report.get("version") != "2.1.0" or not isinstance(runs, list) or not runs:
|
||||
raise ValueError("Invalid or empty SARIF runs: " + path.name)
|
||||
for run in runs:
|
||||
driver = run.get("tool", {}).get("driver", {})
|
||||
results = run.get("results")
|
||||
if driver.get("name") != "CodeQL" or not isinstance(driver.get("rules"), list) or not isinstance(results, list):
|
||||
raise ValueError("Missing CodeQL tool/rules/results evidence: " + path.name)
|
||||
rules: dict[str, Rule] = {}
|
||||
for component in [driver, *run.get("tool", {}).get("extensions", [])]:
|
||||
for rule in component.get("rules", []):
|
||||
rules[rule["id"]] = rule
|
||||
if not rules:
|
||||
raise ValueError("CodeQL report has no analyzed rule metadata: " + path.name)
|
||||
for invocation in run.get("invocations", []):
|
||||
if invocation.get("executionSuccessful") is False:
|
||||
raise ValueError("CodeQL invocation failed: " + path.name)
|
||||
for result in results:
|
||||
rule_id = result.get("ruleId") or result.get("rule", {}).get("id")
|
||||
if rule_id not in rules:
|
||||
raise ValueError("Result references missing rule metadata: " + path.name)
|
||||
if result.get("suppressions"):
|
||||
continue
|
||||
severity = float(rules[rule_id].get("properties", {}).get("security-severity", 0) or 0)
|
||||
if not math.isfinite(severity) or not 0 <= severity <= MAX_SEVERITY:
|
||||
raise ValueError("Invalid rule severity: " + rule_id)
|
||||
if severity >= threshold:
|
||||
blocking.append((severity, rule_id))
|
||||
for severity, rule_id in sorted(blocking, reverse=True):
|
||||
print(f"::error title=CodeQL security finding::{rule_id} ({severity})")
|
||||
print(f"Validated {len(files)} SARIF report(s); {len(blocking)} blocking result(s)")
|
||||
return bool(blocking)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(1 if check(os.environ["SARIF_DIR"], os.environ["EXPECTED_LANGUAGE"], float(os.environ["THRESHOLD"])) else 0)
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -16,12 +15,10 @@ PYTHON_SUFFIXES = (".py", ".pyi", ".pyw")
|
||||
SCRIPT_PATTERN = ".github/scripts/*.py"
|
||||
PYRIGHT_CONFIG = ".github"
|
||||
JAVASCRIPT_SUFFIXES = (".js", ".cjs", ".mjs", ".jsx", ".ts", ".mts", ".cts", ".tsx")
|
||||
# EX-0015: the only npm package whose license classification the PR gate may exempt (the matcher ignores versions, so this guard pins it).
|
||||
URI_JS_PURL = "pkg:npm/uri-js"
|
||||
# EX-0015: the only npm package with a license exception; this guard pins the reviewed copy.
|
||||
URI_JS_VERSION = "4.4.1"
|
||||
URI_JS_INTEGRITY = "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="
|
||||
URI_JS_LICENSE_SHA256 = "0af366eff4c01ec147c9c61ea9e8ffad64a4294754c9d79355f3fd1b97cb2fb9"
|
||||
PR_GATES = ".github/workflows/pr-gates.yml"
|
||||
# Microsoft's official npm Pyright, restored from package-lock.json by `npm ci --ignore-scripts`.
|
||||
PYRIGHT_ENTRY = Path(__file__).resolve().parents[2] / "node_modules" / "pyright" / "index.js"
|
||||
|
||||
@@ -116,10 +113,6 @@ def check_uri_js_exception(root: Path) -> None:
|
||||
digest = hashlib.sha256((root / "node_modules" / "uri-js" / "LICENSE").read_bytes()).hexdigest()
|
||||
if digest != URI_JS_LICENSE_SHA256:
|
||||
raise ValueError(f"Installed uri-js LICENSE bytes differ from the reviewed EX-0015 copy ({digest})")
|
||||
caller = [line for line in (root / PR_GATES).read_text(encoding="utf-8").splitlines() if not line.lstrip().startswith("#")]
|
||||
listed = re.findall(r"pkg:npm/[^\s,]+", "\n".join(caller))
|
||||
if listed != [URI_JS_PURL]:
|
||||
raise ValueError(f"EX-0015 permits only {URI_JS_PURL} in allow-dependencies-licenses, found {listed}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
# Copyright 2026 Kingsrook, LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
"""Exercise the exact CI guards using synthetic evidence only."""
|
||||
|
||||
import contextlib
|
||||
import copy
|
||||
import importlib.util
|
||||
import io
|
||||
import json
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Any, override
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
|
||||
|
||||
def load(name: str) -> ModuleType:
|
||||
"""Load the real sibling guard for synthetic evidence tests."""
|
||||
spec = importlib.util.spec_from_file_location(name, Path(__file__).with_name(name + ".py"))
|
||||
assert spec is not None and spec.loader is not None
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
codeql = load("check-codeql-sarif")
|
||||
|
||||
|
||||
class SarifTests(unittest.TestCase):
|
||||
"""Reject incomplete SARIF evidence and preserve severity enforcement."""
|
||||
|
||||
@override
|
||||
def setUp(self) -> None:
|
||||
"""Create isolated synthetic evidence with cleanup after each test."""
|
||||
self.temporary = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temporary.cleanup)
|
||||
self.directory = Path(self.temporary.name)
|
||||
self.report: dict[str, Any] = {"version": "2.1.0", "runs": [{"tool": {"driver": {"name": "CodeQL", "rules": [{"id": "synthetic", "properties": {"security-severity": "8.0"}}]}}, "results": []}]}
|
||||
|
||||
def save(self, report: object) -> None:
|
||||
"""Write the test body as SARIF without validating it first."""
|
||||
(self.directory / "java.sarif").write_text(json.dumps(report), encoding="utf-8")
|
||||
|
||||
def test_clean_report_with_zero_findings_passes(self) -> None:
|
||||
"""Verify clean report with zero findings passes."""
|
||||
self.save(self.report)
|
||||
self.assertFalse(codeql.check(self.directory, "java-kotlin", 7))
|
||||
|
||||
def test_missing_report_or_wrong_language_fails(self) -> None:
|
||||
"""Verify missing report or wrong language fails."""
|
||||
with self.assertRaises(ValueError):
|
||||
codeql.check(self.directory, "java-kotlin", 7)
|
||||
self.save(self.report)
|
||||
with self.assertRaises(ValueError):
|
||||
codeql.check(self.directory, "csharp", 7)
|
||||
|
||||
def test_empty_or_malformed_report_fails(self) -> None:
|
||||
"""Verify empty or malformed report fails."""
|
||||
for text in ["", "{", "null", "[]"]:
|
||||
with self.subTest(text=text):
|
||||
(self.directory / "java.sarif").write_text(text)
|
||||
with self.assertRaises((ValueError, AttributeError)):
|
||||
codeql.check(self.directory, "java-kotlin", 7)
|
||||
|
||||
def test_missing_runs_tool_rules_or_results_fails(self) -> None:
|
||||
"""Verify missing runs tool rules or results fails."""
|
||||
variants: list[dict[str, Any]] = [{"version": "2.1.0"}, {"version": "2.1.0", "runs": []}]
|
||||
for field in ["tool", "results"]:
|
||||
bad = copy.deepcopy(self.report)
|
||||
del bad["runs"][0][field]
|
||||
variants.append(bad)
|
||||
bad = copy.deepcopy(self.report)
|
||||
bad["runs"][0]["tool"]["driver"]["rules"] = []
|
||||
variants.append(bad)
|
||||
for bad in variants:
|
||||
with self.subTest(report=bad):
|
||||
self.save(bad)
|
||||
with self.assertRaises(ValueError):
|
||||
codeql.check(self.directory, "java-kotlin", 7)
|
||||
|
||||
def test_high_finding_blocks_and_unknown_rule_fails(self) -> None:
|
||||
"""Verify high finding blocks and unknown rule fails."""
|
||||
self.report["runs"][0]["results"] = [{"ruleId": "synthetic"}]
|
||||
self.save(self.report)
|
||||
with contextlib.redirect_stdout(io.StringIO()):
|
||||
self.assertTrue(codeql.check(self.directory, "java-kotlin", 7))
|
||||
self.report["runs"][0]["results"] = [{"ruleId": "missing"}]
|
||||
self.save(self.report)
|
||||
with self.assertRaises(ValueError):
|
||||
codeql.check(self.directory, "java-kotlin", 7)
|
||||
|
||||
def test_failed_invocation_and_invalid_threshold_fail(self) -> None:
|
||||
"""Verify failed invocation and invalid threshold fail."""
|
||||
self.report["runs"][0]["invocations"] = [{"executionSuccessful": False}]
|
||||
self.save(self.report)
|
||||
with self.assertRaises(ValueError):
|
||||
codeql.check(self.directory, "java-kotlin", 7)
|
||||
with self.assertRaises(ValueError):
|
||||
codeql.check(self.directory, "java-kotlin", float("nan"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -234,37 +234,29 @@ class JavaScriptProbeTests(unittest.TestCase):
|
||||
self.assertIn("TS2322", bad.stdout)
|
||||
|
||||
|
||||
URI_GATES = "jobs:\n pr:\n with:\n allow-dependencies-licenses: pkg:npm/uri-js\n"
|
||||
URI_MIXED_GATES = "jobs:\n pr:\n with:\n allow-dependencies-licenses: >-\n pkg:nuget/Microsoft.WindowsAppSDK,\n pkg:npm/uri-js\n"
|
||||
|
||||
|
||||
def uri_lock(version: str = "4.4.1", integrity: str | None = None, dev: bool = True, direct: bool = False, consumer_dev: bool = True) -> dict[str, Any]:
|
||||
"""Build a minimal package-lock shape with uri-js under an ESLint-like consumer."""
|
||||
root_entry: dict[str, Any] = {"devDependencies": {"uri-js": version} if direct else {"eslint": "10.14.0"}}
|
||||
return {"packages": {"": root_entry, "node_modules/uri-js": {"version": version, "integrity": integrity or guard.URI_JS_INTEGRITY, "dev": dev}, "node_modules/eslint": {"dev": consumer_dev, "dependencies": {"uri-js": "^4.2.2"}}}}
|
||||
|
||||
|
||||
def uri_project(root: Path, lock: dict[str, Any], gates: str = URI_GATES, license_bytes: bytes | None = None) -> None:
|
||||
"""Write a private project with the lock, installed LICENSE and PR caller that the guard inspects."""
|
||||
def uri_project(root: Path, lock: dict[str, Any], license_bytes: bytes | None = None) -> None:
|
||||
"""Write a private project with the lock and installed LICENSE that the guard inspects."""
|
||||
(root / "package-lock.json").write_text(json.dumps(lock), encoding="utf-8")
|
||||
installed = root / "node_modules" / "uri-js"
|
||||
installed.mkdir(parents=True)
|
||||
installed.joinpath("LICENSE").write_bytes((ROOT / "node_modules" / "uri-js" / "LICENSE").read_bytes() if license_bytes is None else license_bytes)
|
||||
workflows = root / ".github" / "workflows"
|
||||
workflows.mkdir(parents=True)
|
||||
(workflows / "pr-gates.yml").write_text("# EX-0015 pkg:npm/uri-js is cited here\n" + gates, encoding="utf-8")
|
||||
|
||||
|
||||
class UriJsExceptionTests(unittest.TestCase):
|
||||
"""Allow the EX-0015 license exception only for the reviewed, unmodified, development-only uri-js."""
|
||||
|
||||
def test_reviewed_uri_js_with_exact_caller_is_accepted(self) -> None:
|
||||
"""Accept the reviewed package, installed license bytes and an exact caller list, mixed with NuGet entries."""
|
||||
for gates in (URI_GATES, URI_MIXED_GATES):
|
||||
with tempfile.TemporaryDirectory() as name, self.subTest(gates=gates):
|
||||
root = Path(name)
|
||||
uri_project(root, uri_lock(), gates)
|
||||
guard.check_uri_js_exception(root)
|
||||
def test_reviewed_uri_js_is_accepted(self) -> None:
|
||||
"""Accept the reviewed package with the reviewed installed license bytes."""
|
||||
with tempfile.TemporaryDirectory() as name:
|
||||
root = Path(name)
|
||||
uri_project(root, uri_lock())
|
||||
guard.check_uri_js_exception(root)
|
||||
|
||||
def test_other_version_integrity_scope_or_consumer_is_rejected(self) -> None:
|
||||
"""Reject another version, integrity, non-dev or direct use, a non-dev consumer and a second copy."""
|
||||
@@ -284,14 +276,6 @@ class UriJsExceptionTests(unittest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError, "LICENSE bytes differ"):
|
||||
guard.check_uri_js_exception(root)
|
||||
|
||||
def test_caller_must_list_only_uri_js(self) -> None:
|
||||
"""Reject a caller that lists no npm exception, another package, a versioned form or a duplicate."""
|
||||
for gates in ("jobs:\n pr:\n uses: shared\n", URI_GATES.rstrip() + ", pkg:npm/other\n", "jobs:\n pr:\n with:\n allow-dependencies-licenses: pkg:npm/other\n", URI_GATES.rstrip() + "@4.4.1\n", URI_GATES.rstrip() + ", pkg:npm/uri-js\n"):
|
||||
with tempfile.TemporaryDirectory() as name, self.subTest(gates=gates), self.assertRaisesRegex(ValueError, "permits only"):
|
||||
root = Path(name)
|
||||
uri_project(root, uri_lock(), gates)
|
||||
guard.check_uri_js_exception(root)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -3,6 +3,7 @@
|
||||
"includes": [
|
||||
".github/*.yml",
|
||||
".github/*.yaml",
|
||||
".gitea/*.yaml",
|
||||
".github/scripts/*.py",
|
||||
"tools/*.mjs"
|
||||
],
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
# Copyright 2026 Kingsrook, LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: Protocol CI and release
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
tags: ['v*']
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||
with:
|
||||
node-version: '24'
|
||||
package-manager-cache: false
|
||||
- run: npm ci --ignore-scripts
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
|
||||
with:
|
||||
version: '0.13.6'
|
||||
enable-cache: false
|
||||
- name: Restore locked Python tools
|
||||
run: uv sync --locked
|
||||
- name: Verify script quality gates and rejection paths
|
||||
run: |
|
||||
uv run --locked --no-sync python .github/scripts/test-script-quality.py
|
||||
uv run --locked --no-sync python .github/scripts/check-script-quality.py
|
||||
npm run typecheck
|
||||
npm run lint
|
||||
- name: Verify owned source notices and rejection paths
|
||||
run: |
|
||||
python3 .github/scripts/test-source-headers.py
|
||||
python3 .github/scripts/check-source-headers.py
|
||||
- run: npm run check
|
||||
- run: npm run bundle
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: protocol-bundle
|
||||
path: dist/*
|
||||
if-no-files-found: error
|
||||
|
||||
release-security:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: ./.github/workflows/security.yml
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
release-codeql:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: ./.github/workflows/codeql.yml
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
needs: [verify, release-security, release-codeql]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
attestations: write
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: protocol-bundle
|
||||
path: dist
|
||||
- name: Verify packaged checksums
|
||||
working-directory: dist
|
||||
run: sha256sum --check --strict SHA256SUMS
|
||||
- name: Attest verified release artifacts
|
||||
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
|
||||
with:
|
||||
subject-path: dist/*
|
||||
- name: Publish versioned GitHub release assets
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
shell: bash
|
||||
run: |
|
||||
prerelease=()
|
||||
if [[ "$GITHUB_REF_NAME" == *-* ]]; then prerelease+=(--prerelease); fi
|
||||
gh release create "$GITHUB_REF_NAME" dist/*.tar.gz dist/SHA256SUMS \
|
||||
--verify-tag --title "$GITHUB_REF_NAME" --notes-file CHANGELOG.md "${prerelease[@]}"
|
||||
@@ -1,128 +0,0 @@
|
||||
# Copyright 2026 Kingsrook, LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Local copy of KofTwentyTwo/standards at fdf5ed029603c938615a32b1beca3628b8787945 (v0.1.2).
|
||||
# Pins, inputs and analysis checks preserved; report validation now fails closed.
|
||||
# Static analysis (K22-CI-22): CodeQL with the security-extended suite, failing on
|
||||
# alerts of high severity or above. Called as:
|
||||
#
|
||||
# jobs:
|
||||
# codeql:
|
||||
# uses: KofTwentyTwo/standards/.github/workflows/codeql.yml@<sha> # vX.Y.Z
|
||||
# permissions:
|
||||
# contents: read
|
||||
# security-events: write
|
||||
# with:
|
||||
# languages: '["csharp", "actions"]'
|
||||
# build-mode: manual
|
||||
# build-command: dotnet build MySolution.slnx -p:Platform=x64
|
||||
# runs-on: windows-latest
|
||||
#
|
||||
# producing `codeql / analyze (<language>)` for each language. The CodeQL action
|
||||
# uploads results but never fails a build on them, so the last step reads the SARIF
|
||||
# and fails on any result whose rule has security-severity >= the threshold.
|
||||
# Suppress a false positive with a query filter in a CodeQL config file passed as
|
||||
# `config-file`, citing the exception record in a comment.
|
||||
name: codeql
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
languages:
|
||||
description: JSON array of CodeQL languages, e.g. '["csharp","actions"]'.
|
||||
type: string
|
||||
required: true
|
||||
build-mode:
|
||||
description: CodeQL build mode for compiled languages (none, autobuild, manual). Interpreted languages always use none.
|
||||
type: string
|
||||
default: autobuild
|
||||
build-command:
|
||||
description: Command run for build-mode manual.
|
||||
type: string
|
||||
default: ""
|
||||
runs-on:
|
||||
description: Runner label; Windows-only builds (WinUI, WPF) need windows-latest.
|
||||
type: string
|
||||
default: ubuntu-24.04
|
||||
setup-dotnet:
|
||||
description: Install the .NET SDK from global.json before building.
|
||||
type: boolean
|
||||
default: false
|
||||
config-file:
|
||||
description: Optional path to a CodeQL configuration file in the calling repository.
|
||||
type: string
|
||||
default: ""
|
||||
fail-on-security-severity:
|
||||
description: Minimum CVSS-style security-severity that fails the job (7.0 = high).
|
||||
type: string
|
||||
default: "7.0"
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: analyze (${{ matrix.language }})
|
||||
runs-on: ${{ inputs.runs-on }}
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write # upload SARIF results to code scanning
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: ${{ fromJSON(inputs.languages) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Test scan guard rejection paths
|
||||
shell: python
|
||||
run: |
|
||||
import runpy
|
||||
runpy.run_path(".github/scripts/test-scan-gates.py", run_name="__main__")
|
||||
|
||||
- name: Set up .NET SDK
|
||||
if: inputs.setup-dotnet
|
||||
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
|
||||
with:
|
||||
global-json-file: global.json
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ contains(fromJSON('["actions","javascript-typescript","python","ruby"]'), matrix.language) && 'none' || inputs.build-mode }}
|
||||
queries: security-extended
|
||||
config-file: ${{ inputs.config-file }}
|
||||
|
||||
- name: Build (manual mode)
|
||||
if: inputs.build-mode == 'manual' && !contains(fromJSON('["actions","javascript-typescript","python","ruby"]'), matrix.language)
|
||||
shell: bash
|
||||
env:
|
||||
BUILD_COMMAND: ${{ inputs.build-command }}
|
||||
run: |
|
||||
if [[ -z "$BUILD_COMMAND" ]]; then
|
||||
echo "::error::build-mode is manual but build-command is empty"
|
||||
exit 1
|
||||
fi
|
||||
eval "$BUILD_COMMAND"
|
||||
|
||||
- name: Analyze
|
||||
uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
|
||||
with:
|
||||
category: /language:${{ matrix.language }}
|
||||
output: codeql-sarif
|
||||
# Fork pull requests have a read-only token; analyze still runs and gates.
|
||||
upload: ${{ (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository) && 'never' || 'always' }}
|
||||
|
||||
- name: Gate on security severity
|
||||
shell: python
|
||||
env:
|
||||
SARIF_DIR: codeql-sarif
|
||||
THRESHOLD: ${{ inputs.fail-on-security-severity }}
|
||||
EXPECTED_LANGUAGE: ${{ matrix.language }}
|
||||
run: |
|
||||
import runpy
|
||||
runpy.run_path(".github/scripts/check-codeql-sarif.py", run_name="__main__")
|
||||
@@ -1,21 +0,0 @@
|
||||
# Copyright 2026 Kingsrook, LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: codeql
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '41 6 * * 1'
|
||||
workflow_call:
|
||||
permissions: {}
|
||||
jobs:
|
||||
codeql:
|
||||
uses: ./.github/workflows/codeql-analysis.yml
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
with:
|
||||
languages: '["javascript-typescript", "actions"]'
|
||||
build-mode: none
|
||||
@@ -1,29 +0,0 @@
|
||||
# Copyright 2026 Kingsrook, LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
# Pull request gates (K22-CI-01): Conventional Commit title, DCO sign-off, and
|
||||
# dependency review, from the shared KofTwentyTwo/standards workflow pinned by SHA
|
||||
# (K22-CI-11). Dependabot's github-actions ecosystem keeps the pin current.
|
||||
name: pr-gates
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: pr-gates-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pr:
|
||||
uses: KofTwentyTwo/standards/.github/workflows/pr.yml@fdf5ed029603c938615a32b1beca3628b8787945 # v0.1.2
|
||||
with:
|
||||
# EX-0015: development-only uri-js 4.4.1 (ESLint transitive; BSD-2-Clause plus an authors' views sentence).
|
||||
# https://git.kof22.com/KofTwentyTwo/standards/src/commit/a18179e90f8e6d35ba9f5e583249885c066e52b8/exceptions/register.md#ex-0015
|
||||
# The matcher ignores versions; check-script-quality.py pins the version, lock integrity and installed
|
||||
# license bytes and rejects any other pkg:npm entry here.
|
||||
allow-dependencies-licenses: pkg:npm/uri-js
|
||||
permissions:
|
||||
contents: read # read the commits being merged
|
||||
pull-requests: read # read the pull request title and dependency diff
|
||||
@@ -1,26 +0,0 @@
|
||||
# Copyright 2026 Kingsrook, LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
name: security
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: '23 6 * * 1'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: security-${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
uses: KofTwentyTwo/standards/.github/workflows/security.yml@fdf5ed029603c938615a32b1beca3628b8787945 # v0.1.2
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
+2
-2
@@ -21,6 +21,6 @@ npm run typecheck
|
||||
npm run lint
|
||||
```
|
||||
|
||||
`npm run quality` runs the repository's Python sequence in one step (`uv sync --locked`, both script-quality commands and both source-header commands); the shared Node CI calls it as its `quality-script`. It needs `uv` on `PATH` and `npm ci` first.
|
||||
`npm run quality` runs the repository's Python sequence in one step (`uv sync --locked`, both script-quality commands and both source-header commands); Gitea CI (`.gitea/workflows/build.yaml`, the shared ci-workflows `node.yaml`) runs the same commands individually, then typecheck, lint, check and bundle. It needs `uv` on `PATH` and `npm ci` first.
|
||||
|
||||
`check-script-quality.py` fails for any tracked Python outside `.github/scripts/` or JavaScript outside the `tsconfig.json` include list, and requires Pyright's analyzed-file count to equal the selected scripts. Pyright must run as `pyright -p .github`: it silently skips dot directories from a root config. Documented exceptions: CLI `print`/`console.log` only in `check-*.py` and `tools/`; synthetic test assertions and boundary constants; narrow inline `noqa` comments for fixed no-shell Git/interpreter calls and ordered fail-closed validators. Do not loosen a rule or add a type waiver to pass. `package.json` overrides `minimatch` to 11.0.1, the patched release for [GHSA-g3hv-75wg-m249](https://github.com/advisories/GHSA-g3hv-75wg-m249) (affected before 11.0.1); keep it until ESLint's own dependency range includes a patched release. The only npm license exception, [EX-0015](https://github.com/KofTwentyTwo/standards/blob/main/exceptions/register.md#ex-0015) for development-only `uri-js` 4.4.1, is enforced by `check-script-quality.py` (version, lock integrity, installed license bytes and the exact `pkg:npm/uri-js` caller entry); changing that package or the entry needs renewed approval.
|
||||
`check-script-quality.py` fails for any tracked Python outside `.github/scripts/` or JavaScript outside the `tsconfig.json` include list, and requires Pyright's analyzed-file count to equal the selected scripts. Pyright must run as `pyright -p .github`: it silently skips dot directories from a root config. Documented exceptions: CLI `print`/`console.log` only in `check-*.py` and `tools/`; synthetic test assertions and boundary constants; narrow inline `noqa` comments for fixed no-shell Git/interpreter calls and ordered fail-closed validators. Do not loosen a rule or add a type waiver to pass. `package.json` overrides `minimatch` to 11.0.1, the patched release for [GHSA-g3hv-75wg-m249](https://github.com/advisories/GHSA-g3hv-75wg-m249) (affected before 11.0.1); keep it until ESLint's own dependency range includes a patched release. The only npm license exception, [EX-0015](https://github.com/KofTwentyTwo/standards/blob/main/exceptions/register.md#ex-0015) for development-only `uri-js` 4.4.1, is enforced by `check-script-quality.py` (version, lock integrity and installed license bytes); changing that package needs renewed approval.
|
||||
Reference in new issue
Block a user