From 7028789a3e6efbc001fc9c510b1c3575ab76c694 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 21:06:59 +0000 Subject: [PATCH 01/44] chore(deps): update dependency anchore/grype to v0.117.0 --- .github/skills/security-scan-docker-image-scripts/run.sh | 4 ++-- .github/workflows/supply-chain-pr.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/skills/security-scan-docker-image-scripts/run.sh b/.github/skills/security-scan-docker-image-scripts/run.sh index 73f6042e8..b8b5cf898 100755 --- a/.github/skills/security-scan-docker-image-scripts/run.sh +++ b/.github/skills/security-scan-docker-image-scripts/run.sh @@ -35,7 +35,7 @@ fi # Check Grype if ! command -v grype >/dev/null 2>&1; then log_error "Grype not found - install from: https://github.com/anchore/grype" - log_error "Installation: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.116.1" + log_error "Installation: curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.117.0" error_exit "Grype is required for vulnerability scanning" 2 fi @@ -51,7 +51,7 @@ GRYPE_INSTALLED_VERSION=$(grype version | grep -oP 'Version:\s*\Kv?[0-9]+\.[0-9] # Set defaults matching CI workflow set_default_env "SYFT_VERSION" "v1.50.0" -set_default_env "GRYPE_VERSION" "v0.116.1" +set_default_env "GRYPE_VERSION" "v0.117.0" set_default_env "IMAGE_TAG" "charon:local" set_default_env "FAIL_ON_SEVERITY" "Critical,High" diff --git a/.github/workflows/supply-chain-pr.yml b/.github/workflows/supply-chain-pr.yml index 8e0359b7f..366e4ecf2 100644 --- a/.github/workflows/supply-chain-pr.yml +++ b/.github/workflows/supply-chain-pr.yml @@ -288,7 +288,7 @@ jobs: - name: Install Grype if: steps.set-target.outputs.image_name != '' run: | - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.116.1 + curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.117.0 - name: Scan for vulnerabilities if: steps.set-target.outputs.image_name != '' From ece95c7100c9fd77be3164e4b66f1c8f7b2e4e9a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 10 Aug 2026 22:40:28 +0000 Subject: [PATCH 02/44] chore(deps): update dependency anchore/syft to v1.51.0 --- .github/skills/security-scan-docker-image-scripts/run.sh | 2 +- .github/workflows/nightly-build.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/skills/security-scan-docker-image-scripts/run.sh b/.github/skills/security-scan-docker-image-scripts/run.sh index b8b5cf898..f5e614524 100755 --- a/.github/skills/security-scan-docker-image-scripts/run.sh +++ b/.github/skills/security-scan-docker-image-scripts/run.sh @@ -50,7 +50,7 @@ SYFT_INSTALLED_VERSION=$(syft version | grep -oP 'Version:\s*\Kv?[0-9]+\.[0-9]+\ GRYPE_INSTALLED_VERSION=$(grype version | grep -oP 'Version:\s*\Kv?[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown") # Set defaults matching CI workflow -set_default_env "SYFT_VERSION" "v1.50.0" +set_default_env "SYFT_VERSION" "v1.51.0" set_default_env "GRYPE_VERSION" "v0.117.0" set_default_env "IMAGE_TAG" "charon:local" set_default_env "FAIL_ON_SEVERITY" "Critical,High" diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 72fd05254..82e545dfb 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -301,7 +301,7 @@ jobs: echo "Primary SBOM generation failed or produced missing/invalid output; using deterministic Syft fallback" - SYFT_VERSION="v1.50.0" + SYFT_VERSION="v1.51.0" OS="$(uname -s | tr '[:upper:]' '[:lower:]')" ARCH="$(uname -m)" case "$ARCH" in From 362b64325d59c7a162f9fc4c144d92ab3f8807c6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 01:47:35 +0000 Subject: [PATCH 03/44] chore(deps): update github-actions-non-major --- .github/workflows/docker-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 76d111d5d..f696fa353 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -941,7 +941,7 @@ jobs: - name: Upload Trivy results if: env.TRIGGER_EVENT != 'pull_request' && steps.trivy-check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 with: sarif_file: 'trivy-results.sarif' category: ${{ env.TRIVY_SARIF_CATEGORY }} @@ -979,7 +979,7 @@ jobs: # ("CI: no tokens available"), so retry once before failing the build - name: Attest SBOM id: attest-sbom - uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1 + uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 if: env.TRIGGER_EVENT != 'pull_request' && needs.setup.outputs.is_feature_push != 'true' continue-on-error: true with: From 076b430179853751a50cc7d8c9a0b0d7817aff8b Mon Sep 17 00:00:00 2001 From: Jeremy Hatfield Date: Tue, 11 Aug 2026 20:47:18 +0000 Subject: [PATCH 04/44] fix: correct output formatting for latest npm version check --- scripts/charon_dep_update.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/charon_dep_update.sh b/scripts/charon_dep_update.sh index 1c00e46f5..29ec02683 100644 --- a/scripts/charon_dep_update.sh +++ b/scripts/charon_dep_update.sh @@ -55,7 +55,7 @@ echo "========================================================================== echo "Current local versions (npm / npx):" npm -v && npx -v -echo -n "Latest available npm version on registry: " +echo "Latest available npm version on registry: " npm view npm version echo "Installing latest global npm..." From f41a3f5f0fdf539ec1bb1784b3ad22a7639dbce8 Mon Sep 17 00:00:00 2001 From: Jeremy Hatfield Date: Tue, 11 Aug 2026 20:50:09 +0000 Subject: [PATCH 05/44] fix: update golang.org/x/crypto to v0.55.0 and golang.org/x/text to v0.41.0 --- backend/go.mod | 4 ++-- backend/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index 84adc4cf6..c8a22eb00 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -20,11 +20,11 @@ require ( github.com/sirupsen/logrus v1.9.4 github.com/stretchr/testify v1.11.1 github.com/studio-b12/gowebdav v0.13.0 - golang.org/x/crypto v0.54.0 + golang.org/x/crypto v0.55.0 golang.org/x/mod v0.39.0 golang.org/x/net v0.57.0 golang.org/x/oauth2 v0.36.0 - golang.org/x/text v0.40.0 + golang.org/x/text v0.41.0 golang.org/x/time v0.15.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 diff --git a/backend/go.sum b/backend/go.sum index 06a0f1cf9..b490c99fb 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -218,8 +218,8 @@ go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw= go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg= golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU= golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= -golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= -golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= +golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= @@ -232,8 +232,8 @@ golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= -golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= +golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= +golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= From e1c6f49d2d7c74346ef325bff971d5396032df87 Mon Sep 17 00:00:00 2001 From: Jeremy Hatfield Date: Tue, 11 Aug 2026 20:58:21 +0000 Subject: [PATCH 06/44] chore(deps): update npm dependencies Root: @axe-core/playwright ^4.12.1 -> ^4.13.0, overrides.smol-toml ^1.7.1 -> ^1.8.0. Frontend: knip ^6.32.1 -> ^6.32.2. --- frontend/package-lock.json | 220 ++++++++++++++++--------------------- frontend/package.json | 2 +- package-lock.json | 22 ++-- package.json | 4 +- 4 files changed, 109 insertions(+), 139 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7d11e0f6e..600080b65 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -73,7 +73,7 @@ "eslint-plugin-unicorn": "^73.0.0", "eslint-plugin-unused-imports": "^4.4.1", "jsdom": "^30.0.1", - "knip": "^6.32.1", + "knip": "^6.32.2", "postcss": "^8.5.26", "tailwindcss": "^4.3.3", "typescript": "^6.0.3", @@ -1062,9 +1062,9 @@ } }, "node_modules/@oxc-parser/binding-android-arm-eabi": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.142.0.tgz", - "integrity": "sha512-ZiRGDutGsv1G6bL/ozy/koC0Sv39T1DqyoC4KD1DOy9ZoACm1O5UWhEK2c02Qdk+4lfLVkvFa/mQ0fm/4h1BtQ==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.143.0.tgz", + "integrity": "sha512-n9uozULWflPqBtdmI8lAabLqGKNgLVNN0ZH8HfgCwpKGNtzRzauB76jTiW/3YLkcA7N1zskpi9GdVnZuu1SAvg==", "cpu": [ "arm" ], @@ -1079,9 +1079,9 @@ } }, "node_modules/@oxc-parser/binding-android-arm64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.142.0.tgz", - "integrity": "sha512-WZkvGRLNQTz8lR9zP5nLjUdlroRCopBu3g9zF1p/laE6DzT1UbQo8Rdz5MWhaJUPYg/6gp+jo7HUgsyKaN1FtQ==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.143.0.tgz", + "integrity": "sha512-9BbdjHETk6O3zH/DDid9IgBtF0GlpLabNKN231uraXpRDSfY+iiZxTP5bk1Z63GBownVdhdINFIeddmMz4MzpQ==", "cpu": [ "arm64" ], @@ -1096,9 +1096,9 @@ } }, "node_modules/@oxc-parser/binding-darwin-arm64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.142.0.tgz", - "integrity": "sha512-l4khS8LQOOVYsGRVARo1gSaCT/aBSceUVXgtovWc2+drnxVuDr082WA3OCHVdVzIz5JIrP/y9CWsSKxBDNmYGg==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.143.0.tgz", + "integrity": "sha512-gh+6ecoHUy4/sUcolBl/1qPXKBbYNxFY0Pk0ujgQvINTMSftJY7o4yb8gOkDJPeZeB8+a+u7xTe6umoP8N5HFA==", "cpu": [ "arm64" ], @@ -1113,9 +1113,9 @@ } }, "node_modules/@oxc-parser/binding-darwin-x64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.142.0.tgz", - "integrity": "sha512-QBsNF3nqlXmcH2B1YOPqQYmCJoy4HuIjUxGbBO/k5JAJUl68ghU2psRY2zPk+RyBaWqKP/qfL4oaFgEMCdwskA==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.143.0.tgz", + "integrity": "sha512-qd1hl2d+lXgHv/VQ/M9qm8TrMC5T4RqDBwtOnl+1D0QMjwcz+8AaB4JSg8STgeag0GP6a6L74XEGAsrTSJWNzQ==", "cpu": [ "x64" ], @@ -1130,9 +1130,9 @@ } }, "node_modules/@oxc-parser/binding-freebsd-x64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.142.0.tgz", - "integrity": "sha512-b7Q7m4Cqc6XqNhri3R+QhU+GVy646Pn+bkdhrDdWym/Fdi0ZUa+d73H9dm5H91JtbtAQ/z1d8XKMW3oOV8a4tQ==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.143.0.tgz", + "integrity": "sha512-M5XXcNa7aOqLPKTR41msfghKu2yQ4xWvCm11/gwU0JzOzHNk5sgW//rVEjJ+LO48+VDAMzXTSzurUVxIDKwozw==", "cpu": [ "x64" ], @@ -1147,9 +1147,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.142.0.tgz", - "integrity": "sha512-3riVS5IhdH3uCZj1Y9ftDQlR0dvLsIlw/edrRqk8JhgNd5K0XSs+UBtgh50N13CAlW9/TXj6sVGXaKNBocd0Yg==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.143.0.tgz", + "integrity": "sha512-T/GXusuOkPNQhCQCSBbcU/N8j0rAypuDBl1IyFK+lyYT594XsVz80clPC/OtbSSpBGyJxj8uYEfctxVuxVYoww==", "cpu": [ "arm" ], @@ -1164,9 +1164,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.142.0.tgz", - "integrity": "sha512-NmXUOpgpTSkhl795TiXmWppTwmSJ92RC1qvD6e4XOF+slgmo3e6Ah+kEu+6AN8s7NAOEwqGmir58MgSQSWmBSA==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.143.0.tgz", + "integrity": "sha512-oKu4RcBlXSqo3OC62dp6YTnQaZIurNDpCX3BnAM3+bJxt7s8J2TJKMnC0UYer1qhlRaDCg6wkTaTw+2IlsZ12w==", "cpu": [ "arm" ], @@ -1181,9 +1181,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm64-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.142.0.tgz", - "integrity": "sha512-gc0EXsKtXgerujmU2Bql3u1L1HsSQ2774R83idq/FoNMPVV/RY/1ErFsvnit7KoiP/sLvzQixeUo4Ut0ic0wmw==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.143.0.tgz", + "integrity": "sha512-WJBbD186AZmMGaSIhlktC+rPl8L3peCTXAh88Ih9uEvK0en2mPojGyCGYiL6mHtV1RPV3JyfJW5t6n5hh0lXhA==", "cpu": [ "arm64" ], @@ -1201,9 +1201,9 @@ } }, "node_modules/@oxc-parser/binding-linux-arm64-musl": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.142.0.tgz", - "integrity": "sha512-F2XvmWSE0uWpie+jHKKIFgdVOe9ypGhkEZxKx5DuW215K6cbAC274yYaPkcM7EqY4Df3Weyhpcz3lsURyH2LVg==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.143.0.tgz", + "integrity": "sha512-t1AcYOwEzgceadT4v5e+vaCCb0AncCA3v5AyzfBAz/tMq11qzVccXKzNHtkWdjBsgvTKwRkaUF3QvT4kot8vcQ==", "cpu": [ "arm64" ], @@ -1221,9 +1221,9 @@ } }, "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.142.0.tgz", - "integrity": "sha512-wLMbT21U/QxknQsk+VvNF0b9D2/aGWhcaQQQ+VYlE8FwD5+GoWZIPPXNzyHmkYyhm0KB3itL+TBavjMatqNnYA==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.143.0.tgz", + "integrity": "sha512-RsnO/NoD8376LMJq8JS8TwI0ieNaFRTuNe2GVJntQg6gwZNMENZsEbknHdVwjpOmxdGLGodcwaGSbAeRr5Bgjw==", "cpu": [ "ppc64" ], @@ -1241,9 +1241,9 @@ } }, "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.142.0.tgz", - "integrity": "sha512-+G8F/4ckwT7FCJV4H2bt09xEzJbjNCfuL4Sp1AYNaFtFMVtgIGMuJlteT82U+K0UIZ/DzAR/LDlMFnEuajG7Kw==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.143.0.tgz", + "integrity": "sha512-48fSVfR9TZi5CASZFyv0VC6z6BCoeihFsX031mAD/oSH7d9PYsPgIqza7d9mjP7Z2KTEpTFyH6SIu0Ui6R1vdg==", "cpu": [ "riscv64" ], @@ -1261,9 +1261,9 @@ } }, "node_modules/@oxc-parser/binding-linux-riscv64-musl": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.142.0.tgz", - "integrity": "sha512-hTsHtTLxMAfCo+rpF5K3qZJKW2NpPN/CHd4mYB3y7XlSdspHkd2gehDIofP64AacA9nWQw2tY3O7wR6UY8IVOA==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.143.0.tgz", + "integrity": "sha512-T8CpdD+SfE01DnIOD4HpVxu0ZJOfMJ/VhCvikKfaXAxkZ+9veyLM/D2hpi7Y2hFUyPmVQO3FNZHmYzV/WlVR4g==", "cpu": [ "riscv64" ], @@ -1281,9 +1281,9 @@ } }, "node_modules/@oxc-parser/binding-linux-s390x-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.142.0.tgz", - "integrity": "sha512-6y7qYY3TCUDYjqswImdTGl92y+KA/80twALegQPN27kfY+bG7Ib1+L3jbmrCZQx6wrVnai9IPsEZp07I0hx7JQ==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.143.0.tgz", + "integrity": "sha512-QLdeMsCcacenPEFsfxnBUDF1y6opyz5+fmOz9bfD5Y7fiGCMupUCuB3KTPQhNwshIG1P9fPqar9MHxuBDd4bwQ==", "cpu": [ "s390x" ], @@ -1301,9 +1301,9 @@ } }, "node_modules/@oxc-parser/binding-linux-x64-gnu": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.142.0.tgz", - "integrity": "sha512-i69kAWU+2LgoH5bR+zWiiu+UzAw7Oxkwv7COeJTeY19pn4e70nKQcr9Pm6cL2Z0Z54d+gl9qADlK/0yyuCPiBA==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.143.0.tgz", + "integrity": "sha512-659ujfqLy6k7cuH3sbzhd8b+ztSq+i6E2E9pG78Q0BmHjAExfGIdgc8cGgMdwAozDXeZFHkJ+LXYJdWsaGdgyw==", "cpu": [ "x64" ], @@ -1321,9 +1321,9 @@ } }, "node_modules/@oxc-parser/binding-linux-x64-musl": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.142.0.tgz", - "integrity": "sha512-4SQs678MmjYVrmhAgCWD4o0vpaFszXw9xLX5p2Z9MMFcltxiLkA88wQjh80YHjPrXtpyZ2CWI5m+1yNKM0m2Pw==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.143.0.tgz", + "integrity": "sha512-/Mw/9j4TfZcnKphPrzOE6t4MMknXadcAAuVUlDRTF/ETWB5xOgQvOJV2Mh9We/bWxZdoxaGAdc+hy4GuYwQ2yQ==", "cpu": [ "x64" ], @@ -1341,9 +1341,9 @@ } }, "node_modules/@oxc-parser/binding-openharmony-arm64": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.142.0.tgz", - "integrity": "sha512-YHpx9N7Ln3a++Tc8rv+H7mrK1zyJQOAwCFg8LZ3lTs1T5afGWeZrLPhPT9HLnIwSjCyJqPWVMIrMxbjcmBr2oQ==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.143.0.tgz", + "integrity": "sha512-8rIKWR2BFuifbIK/1XB9wTaSdtuJ25dlE7ZQYDnEwj/2xH2vHsxnvIjHT3ZjSVuLLwGGlSslIG/fbOJ8TV8rTw==", "cpu": [ "arm64" ], @@ -1357,29 +1357,10 @@ "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@oxc-parser/binding-wasm32-wasi": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.142.0.tgz", - "integrity": "sha512-3pLDyY3+oogW73RM5uehNgAiR/Xfb7fvO2Q1Z1gIqZ2+50XDVQmBVlRkHXZTU4gKnQHpwETNsYQVsJ3joVB2iA==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.2", - "@emnapi/runtime": "1.11.2", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, "node_modules/@oxc-parser/binding-win32-arm64-msvc": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.142.0.tgz", - "integrity": "sha512-Had/VeVY28Oyb0K+Q4FV8KCzoBycIh93oDK6pCbya9lkzdq+ikMHMgBubsdqqlybjJmQRawCQRrnBRHyQwYvcQ==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.143.0.tgz", + "integrity": "sha512-5U9kQYMfRRI6Zq7KDxgbIP0RMnKrfn3gLepRMgJuRkPSUALTiRCk9d/uyhb4lGDjUdzwK7mBkKqhLgzBPCmLpQ==", "cpu": [ "arm64" ], @@ -1394,9 +1375,9 @@ } }, "node_modules/@oxc-parser/binding-win32-ia32-msvc": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.142.0.tgz", - "integrity": "sha512-GGi3+YphVHavvgs6gum2UXoNCqzHAmPt/nXkn8ZQZstV2Q1qZD1Mn8fz/nWrDkefHQtrG/+1/XrbMxsBTo6Svw==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.143.0.tgz", + "integrity": "sha512-25P7AaHk4R88Yv2XH4gToDVmh0cOu+bEURQU10CRrmvgabfRArSGAP5osmwUKeSUHj0VS50upbpbRWWW/m7mHA==", "cpu": [ "ia32" ], @@ -1411,9 +1392,9 @@ } }, "node_modules/@oxc-parser/binding-win32-x64-msvc": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.142.0.tgz", - "integrity": "sha512-Ny/Wv4Us1LGC/ljwNTp+Hx3r/pH15EFfeDF0p+n898gt+TtRd6C9SccHcuUhDiNTb8s5tt7jdeAMDRQZ4Vq6hg==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.143.0.tgz", + "integrity": "sha512-ORMh3JE1s6V7ySicdRK7vgaDQnn5o+UHg9ct989PlWHbel8O9ARrmWXM6kZjrBMtNucxNayQ8g69G0VfWzhANw==", "cpu": [ "x64" ], @@ -1428,9 +1409,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", - "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", "dev": true, "license": "MIT", "funding": { @@ -5390,9 +5371,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.403", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.403.tgz", - "integrity": "sha512-MQsYmdaLzvaCX5j+ZZBr5Fm6uCCnPQcRtlvmvRlWqrXy+BH2O4ffXIAScF+JQznQWB9brWp4lSD9Z4yNmaf2BA==", + "version": "1.5.404", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.404.tgz", + "integrity": "sha512-3WJtd7/lVq2Jnuz6wed1l9+1ZD2u2Tet1/1NBc4Iedkmgbu+I7YuAqdAQ8T+VZtnwysMsAf3IqSq9D1gyZjA2g==", "dev": true, "license": "ISC" }, @@ -7702,9 +7683,9 @@ } }, "node_modules/knip": { - "version": "6.32.1", - "resolved": "https://registry.npmjs.org/knip/-/knip-6.32.1.tgz", - "integrity": "sha512-mIiIHMTJVUgSlz0mxEgPt7wg8DmfbCp1Txqab3WpbMCJF7YHvHtC9jeAHHXfISMl72N8WzhyG71SQlaqCOGZtg==", + "version": "6.32.2", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.32.2.tgz", + "integrity": "sha512-WXTXbmocrw7gqm1A1TQvFN0OgJ7hUSU6E1g6SPRIzzHFogUBhXByc7cYeOFVtJ2uODg7DP4VbESYBYnfbtBYsg==", "dev": true, "funding": [ { @@ -7722,13 +7703,13 @@ "formatly": "^0.3.0", "get-tsconfig": "4.14.1", "jiti": "^2.7.0", - "oxc-parser": "^0.142.0", + "oxc-parser": "^0.143.0", "oxc-resolver": "11.24.2", "picomatch": "^4.0.5", "smol-toml": "^1.7.1", "strip-json-comments": "5.0.3", "tinyglobby": "^0.2.17", - "unbash": "^4.0.4", + "unbash": "^4.0.9", "yaml": "^2.9.0", "zod": "^4.4.3" }, @@ -9342,13 +9323,13 @@ } }, "node_modules/oxc-parser": { - "version": "0.142.0", - "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.142.0.tgz", - "integrity": "sha512-kKR+jPiRJYJDexVoziIg/FVGvr1fT1FZSSJOk6tVoMKKSlsf1Cso+cgGCJkOEDWOP174vRntCPFKg+AS7InWvw==", + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.143.0.tgz", + "integrity": "sha512-ov0NzaDCOInknS7mP1cwKdJERt3utPW8ldjtdUXQ8Ty0GEFD08wk422vCUN0d7pST6kqtV7dxoI9w1Zi0l/9TA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "^0.142.0" + "@oxc-project/types": "^0.143.0" }, "engines": { "node": "^20.19.0 || >=22.12.0" @@ -9357,26 +9338,25 @@ "url": "https://github.com/sponsors/Boshen" }, "optionalDependencies": { - "@oxc-parser/binding-android-arm-eabi": "0.142.0", - "@oxc-parser/binding-android-arm64": "0.142.0", - "@oxc-parser/binding-darwin-arm64": "0.142.0", - "@oxc-parser/binding-darwin-x64": "0.142.0", - "@oxc-parser/binding-freebsd-x64": "0.142.0", - "@oxc-parser/binding-linux-arm-gnueabihf": "0.142.0", - "@oxc-parser/binding-linux-arm-musleabihf": "0.142.0", - "@oxc-parser/binding-linux-arm64-gnu": "0.142.0", - "@oxc-parser/binding-linux-arm64-musl": "0.142.0", - "@oxc-parser/binding-linux-ppc64-gnu": "0.142.0", - "@oxc-parser/binding-linux-riscv64-gnu": "0.142.0", - "@oxc-parser/binding-linux-riscv64-musl": "0.142.0", - "@oxc-parser/binding-linux-s390x-gnu": "0.142.0", - "@oxc-parser/binding-linux-x64-gnu": "0.142.0", - "@oxc-parser/binding-linux-x64-musl": "0.142.0", - "@oxc-parser/binding-openharmony-arm64": "0.142.0", - "@oxc-parser/binding-wasm32-wasi": "0.142.0", - "@oxc-parser/binding-win32-arm64-msvc": "0.142.0", - "@oxc-parser/binding-win32-ia32-msvc": "0.142.0", - "@oxc-parser/binding-win32-x64-msvc": "0.142.0" + "@oxc-parser/binding-android-arm-eabi": "0.143.0", + "@oxc-parser/binding-android-arm64": "0.143.0", + "@oxc-parser/binding-darwin-arm64": "0.143.0", + "@oxc-parser/binding-darwin-x64": "0.143.0", + "@oxc-parser/binding-freebsd-x64": "0.143.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.143.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.143.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.143.0", + "@oxc-parser/binding-linux-arm64-musl": "0.143.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.143.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.143.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.143.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.143.0", + "@oxc-parser/binding-linux-x64-gnu": "0.143.0", + "@oxc-parser/binding-linux-x64-musl": "0.143.0", + "@oxc-parser/binding-openharmony-arm64": "0.143.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.143.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.143.0", + "@oxc-parser/binding-win32-x64-msvc": "0.143.0" } }, "node_modules/oxc-resolver": { @@ -10178,16 +10158,6 @@ "@rolldown/binding-win32-x64-msvc": "1.2.3" } }, - "node_modules/rolldown/node_modules/@oxc-project/types": { - "version": "0.143.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", - "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, "node_modules/safe-array-concat": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", @@ -10492,9 +10462,9 @@ } }, "node_modules/smol-toml": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz", - "integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.8.0.tgz", + "integrity": "sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==", "dev": true, "license": "BSD-3-Clause", "engines": { diff --git a/frontend/package.json b/frontend/package.json index 6c1896c42..a14a619ac 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -93,7 +93,7 @@ "eslint-plugin-unicorn": "^73.0.0", "eslint-plugin-unused-imports": "^4.4.1", "jsdom": "^30.0.1", - "knip": "^6.32.1", + "knip": "^6.32.2", "postcss": "^8.5.26", "tailwindcss": "^4.3.3", "typescript": "^6.0.3", diff --git a/package-lock.json b/package-lock.json index 8934b12e7..1c6bc16aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "type-check": "^0.4.0" }, "devDependencies": { - "@axe-core/playwright": "^4.12.1", + "@axe-core/playwright": "^4.13.0", "@bgotink/playwright-coverage": "^0.3.2", "@playwright/test": "^1.62.1", "@types/eslint-plugin-jsx-a11y": "6.10.0", @@ -28,13 +28,13 @@ } }, "node_modules/@axe-core/playwright": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.12.1.tgz", - "integrity": "sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/@axe-core/playwright/-/playwright-4.13.0.tgz", + "integrity": "sha512-6YLx+kxXu5GJceG4ozFg+33a2EMTdjYwWGloJ3sb9Kta5pp+ZNS53uxGVog5JetIY8s++P5UrtX+cri+u0VAVg==", "dev": true, "license": "MPL-2.0", "dependencies": { - "axe-core": "~4.12.1" + "axe-core": "~4.13.0" }, "peerDependencies": { "playwright-core": ">= 1.0.0" @@ -843,9 +843,9 @@ } }, "node_modules/axe-core": { - "version": "4.12.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", - "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz", + "integrity": "sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==", "dev": true, "license": "MPL-2.0", "engines": { @@ -3214,9 +3214,9 @@ } }, "node_modules/smol-toml": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz", - "integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.8.0.tgz", + "integrity": "sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==", "dev": true, "license": "BSD-3-Clause", "engines": { diff --git a/package.json b/package.json index 9989789d2..0c21bcd52 100644 --- a/package.json +++ b/package.json @@ -16,12 +16,12 @@ "type-check": "^0.4.0" }, "overrides": { - "smol-toml": "^1.7.1", + "smol-toml": "^1.8.0", "js-yaml": "^4.3.0", "markdown-it": "^15.0.0" }, "devDependencies": { - "@axe-core/playwright": "^4.12.1", + "@axe-core/playwright": "^4.13.0", "@bgotink/playwright-coverage": "^0.3.2", "@playwright/test": "^1.62.1", "@types/eslint-plugin-jsx-a11y": "6.10.0", From 6436226f17d236d7411995a8a43ccd2ca34336a4 Mon Sep 17 00:00:00 2001 From: Jeremy Hatfield Date: Tue, 11 Aug 2026 20:58:35 +0000 Subject: [PATCH 07/44] fix: avoid npm-check-updates crash on multi-entry overrides update ncu throws an unhandled "Overlapping edit" rejection (upstream bug) when --dep overrides is asked to rewrite more than one entry of package.json in a single pass. Update overrides packages one at a time instead. --- scripts/charon_dep_update.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/charon_dep_update.sh b/scripts/charon_dep_update.sh index 29ec02683..a03785d63 100644 --- a/scripts/charon_dep_update.sh +++ b/scripts/charon_dep_update.sh @@ -100,7 +100,16 @@ for MODULE in "${NPM_MODULES[@]}"; do if [ "$MODULE" != "$REPO_ROOT/frontend" ]; then # Root package.json has only flat string overrides — safe to update all # except js-yaml, which has breaking changes in v6+; keep pinned to ^5. - npx --yes npm-check-updates -u --dep overrides --reject js-yaml + # + # Update one package at a time: ncu throws an unhandled "Overlapping + # edit" rejection (a known upstream bug) when --dep overrides is + # asked to rewrite more than one entry in the same pass. + OVERRIDE_PACKAGES="$(node -e "console.log(Object.keys(require('./package.json').overrides || {}).join('\n'))")" + while IFS= read -r PKG; do + [ -z "$PKG" ] && continue + [ "$PKG" = "js-yaml" ] && continue + npx --yes npm-check-updates -u --dep overrides --filter "$PKG" + done <<< "$OVERRIDE_PACKAGES" fi rm -rf node_modules package-lock.json From ae56a67026475908ccb23671085cd8ad1650e854 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 11 Aug 2026 22:22:23 +0000 Subject: [PATCH 08/44] chore(deps): update module golang.org/x/crypto to v0.55.0 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 14f618f9d..28860c929 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ ARG EXPR_LANG_VERSION=1.17.8 # renovate: datasource=go depName=golang.org/x/net ARG XNET_VERSION=0.57.0 # renovate: datasource=go depName=golang.org/x/crypto -ARG XCRYPTO_VERSION=0.54.0 +ARG XCRYPTO_VERSION=0.55.0 # renovate: datasource=npm depName=npm ARG NPM_VERSION=12.0.2 From d89ad49a64a3d64e0f2ea75e3c9ca8ae96d66224 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 13:31:06 +0000 Subject: [PATCH 09/44] chore(deps): update dependency @testing-library/user-event to ^14.6.4 --- frontend/package-lock.json | 8 ++++---- frontend/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 600080b65..40b84a6dc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -44,7 +44,7 @@ "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.3", + "@testing-library/user-event": "^14.6.4", "@types/eslint-plugin-jsx-a11y": "6.10.0", "@types/node": "^26.2.0", "@types/react": "^19.2.18", @@ -3137,9 +3137,9 @@ } }, "node_modules/@testing-library/user-event": { - "version": "14.6.3", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.3.tgz", - "integrity": "sha512-6dBq67jT8lE+JTE8Exm02Kt6ze43hz1jdiSpSJwtTZiT1xQQ6b7nZYTTQ9njdArdU8XklOwaDp/AbT/eYSKF4g==", + "version": "14.6.4", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.4.tgz", + "integrity": "sha512-QCGwP6QrjypBLwyj5cuyfVamkaIEy/XGY+1VDehbtbQqOggYmTFpFOdWR5mPz14vX8vXLMVjDHlRNBcClyO9ew==", "dev": true, "license": "MIT", "engines": { diff --git a/frontend/package.json b/frontend/package.json index a14a619ac..d9a3144ae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -64,7 +64,7 @@ "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^7.0.1", "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.3", + "@testing-library/user-event": "^14.6.4", "@types/eslint-plugin-jsx-a11y": "6.10.0", "@types/node": "^26.2.0", "@types/react": "^19.2.18", From a51ae8555de620285197891736463c7a9f2c0df1 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Wed, 12 Aug 2026 15:18:34 -0400 Subject: [PATCH 10/44] chore(deps): update golang.org/x/net to v0.58.0 --- backend/go.mod | 2 +- backend/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index c8a22eb00..080b5a130 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -22,7 +22,7 @@ require ( github.com/studio-b12/gowebdav v0.13.0 golang.org/x/crypto v0.55.0 golang.org/x/mod v0.39.0 - golang.org/x/net v0.57.0 + golang.org/x/net v0.58.0 golang.org/x/oauth2 v0.36.0 golang.org/x/text v0.41.0 golang.org/x/time v0.15.0 diff --git a/backend/go.sum b/backend/go.sum index b490c99fb..550758de1 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -222,8 +222,8 @@ golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= +golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= From 9fd0c7705a5cdb9b2e3fd19241e5cd355923f408 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Wed, 12 Aug 2026 15:23:20 -0400 Subject: [PATCH 11/44] chore(deps): update @napi-rs/wasm-runtime to v1.2.3 and other dependencies to v1.2.4 --- frontend/package-lock.json | 152 ++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 71 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 40b84a6dc..7981bb267 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1040,9 +1040,9 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", - "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.3.tgz", + "integrity": "sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==", "dev": true, "license": "MIT", "optional": true, @@ -1057,8 +1057,8 @@ "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", - "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@oxc-parser/binding-android-arm-eabi": { @@ -2469,9 +2469,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", - "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", "cpu": [ "arm64" ], @@ -2486,9 +2486,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", - "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", "cpu": [ "arm64" ], @@ -2503,9 +2503,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", - "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", "cpu": [ "x64" ], @@ -2520,9 +2520,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", - "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", "cpu": [ "x64" ], @@ -2537,9 +2537,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", - "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", "cpu": [ "arm" ], @@ -2554,9 +2554,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", - "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", "cpu": [ "arm64" ], @@ -2574,9 +2574,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", - "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", "cpu": [ "arm64" ], @@ -2594,9 +2594,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", - "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", "cpu": [ "ppc64" ], @@ -2614,9 +2614,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", - "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", "cpu": [ "s390x" ], @@ -2634,9 +2634,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", - "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", "cpu": [ "x64" ], @@ -2654,9 +2654,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", - "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", "cpu": [ "x64" ], @@ -2674,9 +2674,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", - "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", "cpu": [ "arm64" ], @@ -2691,9 +2691,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", - "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", "cpu": [ "arm64" ], @@ -2708,9 +2708,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", - "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", "cpu": [ "x64" ], @@ -5371,9 +5371,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.404", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.404.tgz", - "integrity": "sha512-3WJtd7/lVq2Jnuz6wed1l9+1ZD2u2Tet1/1NBc4Iedkmgbu+I7YuAqdAQ8T+VZtnwysMsAf3IqSq9D1gyZjA2g==", + "version": "1.5.405", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.405.tgz", + "integrity": "sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew==", "dev": true, "license": "ISC" }, @@ -6638,9 +6638,9 @@ } }, "node_modules/globals": { - "version": "17.9.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", - "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", + "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", "dev": true, "license": "MIT", "engines": { @@ -10126,13 +10126,13 @@ } }, "node_modules/rolldown": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", - "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.143.0", + "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -10142,20 +10142,30 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.2.3", - "@rolldown/binding-darwin-arm64": "1.2.3", - "@rolldown/binding-darwin-x64": "1.2.3", - "@rolldown/binding-freebsd-x64": "1.2.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", - "@rolldown/binding-linux-arm64-gnu": "1.2.3", - "@rolldown/binding-linux-arm64-musl": "1.2.3", - "@rolldown/binding-linux-ppc64-gnu": "1.2.3", - "@rolldown/binding-linux-s390x-gnu": "1.2.3", - "@rolldown/binding-linux-x64-gnu": "1.2.3", - "@rolldown/binding-linux-x64-musl": "1.2.3", - "@rolldown/binding-openharmony-arm64": "1.2.3", - "@rolldown/binding-win32-arm64-msvc": "1.2.3", - "@rolldown/binding-win32-x64-msvc": "1.2.3" + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" + } + }, + "node_modules/rolldown/node_modules/@oxc-project/types": { + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/safe-array-concat": { From 6fa8fa402be2bbe7dd2ffcc91177b673581fb3e6 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Wed, 12 Aug 2026 15:23:26 -0400 Subject: [PATCH 12/44] chore(deps): update @oxc-project/types and @rolldown bindings to version 1.2.4 --- package-lock.json | 132 +++++++++++++++++++++++----------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c6bc16aa..6bd02cee7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -148,9 +148,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.143.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", - "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", + "version": "0.144.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz", + "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==", "dev": true, "license": "MIT", "funding": { @@ -174,9 +174,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", - "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz", + "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==", "cpu": [ "arm64" ], @@ -191,9 +191,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", - "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==", "cpu": [ "arm64" ], @@ -208,9 +208,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", - "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz", + "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==", "cpu": [ "x64" ], @@ -225,9 +225,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", - "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz", + "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==", "cpu": [ "x64" ], @@ -242,9 +242,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", - "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz", + "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==", "cpu": [ "arm" ], @@ -259,9 +259,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", - "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz", + "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==", "cpu": [ "arm64" ], @@ -279,9 +279,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", - "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz", + "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==", "cpu": [ "arm64" ], @@ -299,9 +299,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", - "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz", + "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==", "cpu": [ "ppc64" ], @@ -319,9 +319,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", - "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz", + "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==", "cpu": [ "s390x" ], @@ -339,9 +339,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", - "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz", + "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==", "cpu": [ "x64" ], @@ -359,9 +359,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", - "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz", + "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==", "cpu": [ "x64" ], @@ -379,9 +379,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", - "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz", + "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==", "cpu": [ "arm64" ], @@ -396,9 +396,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", - "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz", + "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==", "cpu": [ "arm64" ], @@ -413,9 +413,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", - "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz", + "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==", "cpu": [ "x64" ], @@ -692,9 +692,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz", + "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==", "dev": true, "license": "MIT", "engines": { @@ -3081,13 +3081,13 @@ } }, "node_modules/rolldown": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", - "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz", + "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.143.0", + "@oxc-project/types": "=0.144.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -3097,20 +3097,20 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.2.3", - "@rolldown/binding-darwin-arm64": "1.2.3", - "@rolldown/binding-darwin-x64": "1.2.3", - "@rolldown/binding-freebsd-x64": "1.2.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", - "@rolldown/binding-linux-arm64-gnu": "1.2.3", - "@rolldown/binding-linux-arm64-musl": "1.2.3", - "@rolldown/binding-linux-ppc64-gnu": "1.2.3", - "@rolldown/binding-linux-s390x-gnu": "1.2.3", - "@rolldown/binding-linux-x64-gnu": "1.2.3", - "@rolldown/binding-linux-x64-musl": "1.2.3", - "@rolldown/binding-openharmony-arm64": "1.2.3", - "@rolldown/binding-win32-arm64-msvc": "1.2.3", - "@rolldown/binding-win32-x64-msvc": "1.2.3" + "@rolldown/binding-android-arm64": "1.2.4", + "@rolldown/binding-darwin-arm64": "1.2.4", + "@rolldown/binding-darwin-x64": "1.2.4", + "@rolldown/binding-freebsd-x64": "1.2.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.4", + "@rolldown/binding-linux-arm64-gnu": "1.2.4", + "@rolldown/binding-linux-arm64-musl": "1.2.4", + "@rolldown/binding-linux-ppc64-gnu": "1.2.4", + "@rolldown/binding-linux-s390x-gnu": "1.2.4", + "@rolldown/binding-linux-x64-gnu": "1.2.4", + "@rolldown/binding-linux-x64-musl": "1.2.4", + "@rolldown/binding-openharmony-arm64": "1.2.4", + "@rolldown/binding-win32-arm64-msvc": "1.2.4", + "@rolldown/binding-win32-x64-msvc": "1.2.4" } }, "node_modules/run-parallel": { From ea52229f377478423d9fbb5329ad86e8f1003b19 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Wed, 12 Aug 2026 15:41:36 -0400 Subject: [PATCH 13/44] fix: add pinned-Syft fallback for transient SBOM generation failures anchore/sbom-action resolves its pinned syft release tag against the GitHub API at job runtime; a transient upstream 5xx there fails the step even though the pin is a valid release. supply-chain-pr.yml, supply-chain-verify.yml, and docker-build.yml previously had no resilience against this. Apply the same continue-on-error + verified pinned-Syft-binary fallback that nightly-build.yml already used, so all four SBOM generation steps degrade gracefully instead of failing the job outright. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/docker-build.yml | 46 ++++++++++++++++++++++ .github/workflows/supply-chain-pr.yml | 47 ++++++++++++++++++++++- .github/workflows/supply-chain-verify.yml | 41 ++++++++++++++++++++ 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index f696fa353..b937f153b 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -964,7 +964,13 @@ jobs: # Generate SBOM (Software Bill of Materials) for supply chain security # Only for production builds (main/development) - feature branches use downstream supply-chain-pr.yml + # anchore/sbom-action resolves the pinned syft release tag against the GitHub + # API at job runtime; transient upstream 5xx/rate-limit errors there fail the + # step even though the pin itself is valid, so continue-on-error + a + # deterministic pinned-Syft fallback (below) makes this resilient. - name: Generate SBOM + id: sbom_primary + continue-on-error: true uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 if: env.TRIGGER_EVENT != 'pull_request' && needs.setup.outputs.is_feature_push != 'true' with: @@ -973,6 +979,46 @@ jobs: output-file: sbom.cyclonedx.json syft-version: v1.45.1 + - name: Generate SBOM fallback with pinned Syft + if: env.TRIGGER_EVENT != 'pull_request' && needs.setup.outputs.is_feature_push != 'true' && always() + run: | + set -euo pipefail + + if [[ "${{ steps.sbom_primary.outcome }}" == "success" ]] && [[ -s sbom.cyclonedx.json ]] && jq -e . sbom.cyclonedx.json >/dev/null 2>&1; then + echo "Primary SBOM generation succeeded with valid JSON; skipping fallback" + exit 0 + fi + + echo "Primary SBOM generation failed or produced missing/invalid output; using deterministic Syft fallback" + + SYFT_VERSION="v1.51.0" + OS="$(uname -s | tr '[:upper:]' '[:lower:]')" + ARCH="$(uname -m)" + case "$ARCH" in + x86_64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; + esac + + TARBALL="syft_${SYFT_VERSION#v}_${OS}_${ARCH}.tar.gz" + BASE_URL="https://github.com/anchore/syft/releases/download/${SYFT_VERSION}" + + curl -fsSLo "$TARBALL" "${BASE_URL}/${TARBALL}" + curl -fsSLo checksums.txt "${BASE_URL}/syft_${SYFT_VERSION#v}_checksums.txt" + + grep " ${TARBALL}$" checksums.txt > checksum_line.txt + sha256sum -c checksum_line.txt + + tar -xzf "$TARBALL" syft + chmod +x syft + + DIGEST="${{ steps.merge.outputs.digest }}" + if [[ -z "$DIGEST" ]]; then + echo "::error::Digest from merge step is empty; the merge step did not complete successfully" + exit 1 + fi + ./syft "${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${DIGEST}" -o cyclonedx-json=sbom.cyclonedx.json + # Create verifiable attestation for the SBOM # (actions/attest-sbom is deprecated; actions/attest supports sbom-path natively) # GitHub's OIDC token endpoint occasionally fails transiently diff --git a/.github/workflows/supply-chain-pr.yml b/.github/workflows/supply-chain-pr.yml index 366e4ecf2..5229d0ea8 100644 --- a/.github/workflows/supply-chain-pr.yml +++ b/.github/workflows/supply-chain-pr.yml @@ -266,16 +266,61 @@ jobs: fi # Generate SBOM using official Anchore action (auto-updated by Renovate) + # anchore/sbom-action resolves the pinned syft release tag against the GitHub + # API at job runtime; transient upstream 5xx/rate-limit errors there fail the + # step even though the pin itself is valid, so continue-on-error + a + # deterministic pinned-Syft fallback (below) makes this resilient. - name: Generate SBOM if: steps.set-target.outputs.image_name != '' + id: sbom_primary + continue-on-error: true uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 - id: sbom with: image: ${{ steps.set-target.outputs.image_name }} format: cyclonedx-json output-file: sbom.cyclonedx.json syft-version: v1.45.1 + - name: Generate SBOM fallback with pinned Syft + if: steps.set-target.outputs.image_name != '' && always() + run: | + set -euo pipefail + + if [[ "${{ steps.sbom_primary.outcome }}" == "success" ]] && [[ -s sbom.cyclonedx.json ]] && jq -e . sbom.cyclonedx.json >/dev/null 2>&1; then + echo "Primary SBOM generation succeeded with valid JSON; skipping fallback" + exit 0 + fi + + echo "Primary SBOM generation failed or produced missing/invalid output; using deterministic Syft fallback" + + SYFT_VERSION="v1.51.0" + OS="$(uname -s | tr '[:upper:]' '[:lower:]')" + ARCH="$(uname -m)" + case "$ARCH" in + x86_64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; + esac + + TARBALL="syft_${SYFT_VERSION#v}_${OS}_${ARCH}.tar.gz" + BASE_URL="https://github.com/anchore/syft/releases/download/${SYFT_VERSION}" + + curl -fsSLo "$TARBALL" "${BASE_URL}/${TARBALL}" + curl -fsSLo checksums.txt "${BASE_URL}/syft_${SYFT_VERSION#v}_checksums.txt" + + grep " ${TARBALL}$" checksums.txt > checksum_line.txt + sha256sum -c checksum_line.txt + + tar -xzf "$TARBALL" syft + chmod +x syft + + IMAGE_NAME="${{ steps.set-target.outputs.image_name }}" + if [[ -z "$IMAGE_NAME" ]]; then + echo "::error::image_name from set-target step is empty; the target-selection step did not complete successfully" + exit 1 + fi + ./syft "${IMAGE_NAME}" -o cyclonedx-json=sbom.cyclonedx.json + - name: Count SBOM components if: steps.set-target.outputs.image_name != '' id: sbom-count diff --git a/.github/workflows/supply-chain-verify.yml b/.github/workflows/supply-chain-verify.yml index a5bb1eee3..ee7d9c20c 100644 --- a/.github/workflows/supply-chain-verify.yml +++ b/.github/workflows/supply-chain-verify.yml @@ -118,8 +118,14 @@ jobs: fi # Generate SBOM using official Anchore action (auto-updated by Renovate) + # anchore/sbom-action resolves the pinned syft release tag against the GitHub + # API at job runtime; transient upstream 5xx/rate-limit errors there fail the + # step even though the pin itself is valid, so continue-on-error + a + # deterministic pinned-Syft fallback (below) makes this resilient. - name: Generate and Verify SBOM if: steps.image-check.outputs.exists == 'true' + id: sbom_primary + continue-on-error: true uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 with: image: ghcr.io/${{ github.repository_owner }}/charon:${{ steps.tag.outputs.tag }} @@ -127,6 +133,41 @@ jobs: output-file: sbom-verify.cyclonedx.json syft-version: v1.45.1 + - name: Generate SBOM fallback with pinned Syft + if: steps.image-check.outputs.exists == 'true' && always() + run: | + set -euo pipefail + + if [[ "${{ steps.sbom_primary.outcome }}" == "success" ]] && [[ -s sbom-verify.cyclonedx.json ]] && jq -e . sbom-verify.cyclonedx.json >/dev/null 2>&1; then + echo "Primary SBOM generation succeeded with valid JSON; skipping fallback" + exit 0 + fi + + echo "Primary SBOM generation failed or produced missing/invalid output; using deterministic Syft fallback" + + SYFT_VERSION="v1.51.0" + OS="$(uname -s | tr '[:upper:]' '[:lower:]')" + ARCH="$(uname -m)" + case "$ARCH" in + x86_64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) echo "Unsupported architecture: $ARCH"; exit 1 ;; + esac + + TARBALL="syft_${SYFT_VERSION#v}_${OS}_${ARCH}.tar.gz" + BASE_URL="https://github.com/anchore/syft/releases/download/${SYFT_VERSION}" + + curl -fsSLo "$TARBALL" "${BASE_URL}/${TARBALL}" + curl -fsSLo checksums.txt "${BASE_URL}/syft_${SYFT_VERSION#v}_checksums.txt" + + grep " ${TARBALL}$" checksums.txt > checksum_line.txt + sha256sum -c checksum_line.txt + + tar -xzf "$TARBALL" syft + chmod +x syft + + ./syft "ghcr.io/${{ github.repository_owner }}/charon:${{ steps.tag.outputs.tag }}" -o cyclonedx-json=sbom-verify.cyclonedx.json + - name: Verify SBOM Completeness if: steps.image-check.outputs.exists == 'true' run: | From 47b8dc9b584fb4df191c90dc564e70325956f65f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 19:43:53 +0000 Subject: [PATCH 14/44] chore(deps): update dependency github/codeql-cli-binaries to v2.26.3 --- scripts/install-codeql.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install-codeql.sh b/scripts/install-codeql.sh index 15dbd6caf..983917bd3 100755 --- a/scripts/install-codeql.sh +++ b/scripts/install-codeql.sh @@ -41,7 +41,7 @@ NC='\033[0m' # deliberately and re-validate `lefthook run codeql` locally before raising # this — an exact pin (not "latest") keeps local scans reproducible, per # CLAUDE.md's "use exact dependency versions" convention. -CODEQL_VERSION="${CODEQL_VERSION:-v2.26.2}" +CODEQL_VERSION="${CODEQL_VERSION:-v2.26.3}" # Where to install the `codeql` PATH shim. Defaults to the first writable, # user-owned directory already on PATH so no sudo is required. From b6af07c482575077d028baa8ae5718cdb00ecbff Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Wed, 12 Aug 2026 15:46:00 -0400 Subject: [PATCH 15/44] fix: extend renovate syft version tracking to sbom-action input and new fallback scripts The custom syft regex manager only watched nightly-build.yml and the security-scan skill script, so Renovate never picked up the syft-version: input consumed by anchore/sbom-action across all four workflows, nor the new SYFT_VERSION bash fallback added to supply-chain-pr.yml, supply-chain-verify.yml, and docker-build.yml. Widen file coverage to all four workflows and add a matchString for the syft-version: field so both pins stay renovate-tracked. Co-Authored-By: Claude Sonnet 5 --- .github/renovate.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json b/.github/renovate.json index 1a5a68d8d..d36fa3470 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -275,11 +275,15 @@ "description": "Track Syft version in workflows and scripts", "managerFilePatterns": [ "/^\\.github/workflows/nightly-build\\.yml$/", + "/^\\.github/workflows/docker-build\\.yml$/", + "/^\\.github/workflows/supply-chain-pr\\.yml$/", + "/^\\.github/workflows/supply-chain-verify\\.yml$/", "/^\\.github/skills/security-scan-docker-image-scripts/run\\.sh$/" ], "matchStrings": [ "SYFT_VERSION=\\\"v(?[^\\\"\\s]+)\\\"", - "set_default_env \\\"SYFT_VERSION\\\" \\\"v(?[^\\\"]+)\\\"" + "set_default_env \\\"SYFT_VERSION\\\" \\\"v(?[^\\\"]+)\\\"", + "syft-version:\\s*v(?[^\\s]+)" ], "depNameTemplate": "anchore/syft", "datasourceTemplate": "github-releases", From 99f7ba981036d66834bdbd6d4653f49b988c10a1 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Wed, 12 Aug 2026 15:51:51 -0400 Subject: [PATCH 16/44] fix: add attribution section to settings.json for commit and PR tracking --- .claude/settings.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.claude/settings.json b/.claude/settings.json index 4e1c91b02..122fe626c 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -10,5 +10,9 @@ }, "worktree": { "bgIsolation": "none" + }, + "attribution": { + "commit": "", + "pr": "" } } \ No newline at end of file From ddc209b5209c83cdcef7157aba6d2596b8871fa5 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Wed, 12 Aug 2026 15:56:58 -0400 Subject: [PATCH 17/44] fix: retry grype installer against transient GitHub failures Wrap the direct Grype install.sh download in a bounded retry loop with backoff so a single transient GitHub failure (503s, rate limiting) during that step doesn't fail the supply-chain-pr job. Pinned version and install target are unchanged; install.sh's own internal verification is untouched. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/supply-chain-pr.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/supply-chain-pr.yml b/.github/workflows/supply-chain-pr.yml index 5229d0ea8..467f6a5e1 100644 --- a/.github/workflows/supply-chain-pr.yml +++ b/.github/workflows/supply-chain-pr.yml @@ -333,7 +333,18 @@ jobs: - name: Install Grype if: steps.set-target.outputs.image_name != '' run: | - curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.117.0 + MAX_ATTEMPTS=3 + for attempt in $(seq 1 "$MAX_ATTEMPTS"); do + if curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin v0.117.0; then + break + fi + if [[ "$attempt" -eq "$MAX_ATTEMPTS" ]]; then + echo "::error::Failed to install Grype after ${MAX_ATTEMPTS} attempts" + exit 1 + fi + echo "Grype install attempt ${attempt} failed; retrying after backoff..." + sleep $((attempt * 5)) + done - name: Scan for vulnerabilities if: steps.set-target.outputs.image_name != '' From d370fed6df9f409b98b8cf98d1e2fe5ae5d4d445 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:15:08 +0000 Subject: [PATCH 18/44] chore(deps): update dependency anchore/syft to v1.51.0 --- .github/workflows/docker-build.yml | 2 +- .github/workflows/nightly-build.yml | 2 +- .github/workflows/supply-chain-pr.yml | 2 +- .github/workflows/supply-chain-verify.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index b937f153b..9f6a012dd 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -977,7 +977,7 @@ jobs: image: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.merge.outputs.digest }} format: cyclonedx-json output-file: sbom.cyclonedx.json - syft-version: v1.45.1 + syft-version: v1.51.0 - name: Generate SBOM fallback with pinned Syft if: env.TRIGGER_EVENT != 'pull_request' && needs.setup.outputs.is_feature_push != 'true' && always() diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 82e545dfb..99d68fd96 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -287,7 +287,7 @@ jobs: image: ${{ env.GHCR_REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.resolve_digest.outputs.digest }} format: cyclonedx-json output-file: sbom-nightly.json - syft-version: v1.45.1 + syft-version: v1.51.0 - name: Generate SBOM fallback with pinned Syft if: always() diff --git a/.github/workflows/supply-chain-pr.yml b/.github/workflows/supply-chain-pr.yml index 467f6a5e1..fa4548ee5 100644 --- a/.github/workflows/supply-chain-pr.yml +++ b/.github/workflows/supply-chain-pr.yml @@ -279,7 +279,7 @@ jobs: image: ${{ steps.set-target.outputs.image_name }} format: cyclonedx-json output-file: sbom.cyclonedx.json - syft-version: v1.45.1 + syft-version: v1.51.0 - name: Generate SBOM fallback with pinned Syft if: steps.set-target.outputs.image_name != '' && always() diff --git a/.github/workflows/supply-chain-verify.yml b/.github/workflows/supply-chain-verify.yml index ee7d9c20c..8fb1e6978 100644 --- a/.github/workflows/supply-chain-verify.yml +++ b/.github/workflows/supply-chain-verify.yml @@ -131,7 +131,7 @@ jobs: image: ghcr.io/${{ github.repository_owner }}/charon:${{ steps.tag.outputs.tag }} format: cyclonedx-json output-file: sbom-verify.cyclonedx.json - syft-version: v1.45.1 + syft-version: v1.51.0 - name: Generate SBOM fallback with pinned Syft if: steps.image-check.outputs.exists == 'true' && always() From 673044dcf70562d0952a91f7956bef2b718bfaac Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 12 Aug 2026 20:54:00 +0000 Subject: [PATCH 19/44] chore(deps): update module golang.org/x/net to v0.58.0 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 28860c929..715730853 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,7 +25,7 @@ ARG CROWDSEC_RELEASE_SHA256=704e37121e7ac215991441cef0d8732e33fa3b1a2b2b88b53a0b # renovate: datasource=github-tags depName=expr-lang/expr extractVersion=^v(?.+)$ ARG EXPR_LANG_VERSION=1.17.8 # renovate: datasource=go depName=golang.org/x/net -ARG XNET_VERSION=0.57.0 +ARG XNET_VERSION=0.58.0 # renovate: datasource=go depName=golang.org/x/crypto ARG XCRYPTO_VERSION=0.55.0 # renovate: datasource=npm depName=npm From e82c1617be67c2ff6f267c5619fe7cfa15407cba Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Thu, 13 Aug 2026 11:59:20 -0400 Subject: [PATCH 20/44] chore(deps): update logrus to v1.10.0 and add golang.org/x/sys v0.13.0 --- backend/go.mod | 2 +- backend/go.sum | 4 ++-- go.work.sum | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/go.mod b/backend/go.mod index 080b5a130..82d0aa900 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -17,7 +17,7 @@ require ( github.com/pkg/sftp v1.13.11 github.com/prometheus/client_golang v1.24.1 github.com/robfig/cron/v3 v3.0.1 - github.com/sirupsen/logrus v1.9.4 + github.com/sirupsen/logrus v1.10.0 github.com/stretchr/testify v1.11.1 github.com/studio-b12/gowebdav v0.13.0 golang.org/x/crypto v0.55.0 diff --git a/backend/go.sum b/backend/go.sum index 550758de1..315507cdf 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -165,8 +165,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.0 h1:T8MxJJXVZkfcC5zSRMRAg2F8+lxjmUCGGWPzFxO+Msc= +github.com/sirupsen/logrus v1.10.0/go.mod h1:FXZFonkDAnFozmO+5hGAFvB0Yg9/j2SIhA/QuIkP180= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/go.work.sum b/go.work.sum index 6bca41c3e..a68e5aef5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -289,6 +289,7 @@ golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwE golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= From 7a6caf78816f74e64b3633957b3f85b2aebdd8bd Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Thu, 13 Aug 2026 12:03:09 -0400 Subject: [PATCH 21/44] chore(deps): update logrus to v1.10.0 --- agent/go.mod | 2 +- agent/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/agent/go.mod b/agent/go.mod index 2235fadf1..78e4ba79c 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -5,7 +5,7 @@ go 1.26.5 require ( github.com/gorilla/websocket v1.5.3 github.com/hashicorp/yamux v0.1.2 - github.com/sirupsen/logrus v1.9.4 + github.com/sirupsen/logrus v1.10.0 github.com/stretchr/testify v1.11.1 ) diff --git a/agent/go.sum b/agent/go.sum index 0efd37fcc..00d20580c 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -18,8 +18,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= -github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/sirupsen/logrus v1.10.0 h1:T8MxJJXVZkfcC5zSRMRAg2F8+lxjmUCGGWPzFxO+Msc= +github.com/sirupsen/logrus v1.10.0/go.mod h1:FXZFonkDAnFozmO+5hGAFvB0Yg9/j2SIhA/QuIkP180= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= From 0778efc4fd2663402d429f1ae54493846e3731f7 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Thu, 13 Aug 2026 12:27:52 -0400 Subject: [PATCH 22/44] chore(deps): update baseline-browser-mapping to v2.11.14 --- frontend/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7981bb267..83a64b0d5 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4568,9 +4568,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.11.13", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz", - "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==", + "version": "2.11.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.14.tgz", + "integrity": "sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==", "dev": true, "license": "Apache-2.0", "bin": { From d252a929e299af23d77e9f80daefc890196353f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 13 Aug 2026 16:32:15 +0000 Subject: [PATCH 23/44] chore(deps): update github-actions-non-major --- .github/workflows/codeql.yml | 6 +++--- .github/workflows/docker-build.yml | 4 ++-- .github/workflows/nightly-build.yml | 2 +- .github/workflows/security-pr.yml | 2 +- .github/workflows/security-weekly-rebuild.yml | 2 +- .github/workflows/supply-chain-pr.yml | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a28524369..6a0aa2ad5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -52,7 +52,7 @@ jobs: run: bash scripts/ci/check-codeql-parity.sh - name: Initialize CodeQL - uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 + uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 with: languages: ${{ matrix.language }} queries: security-and-quality @@ -92,11 +92,11 @@ jobs: run: mkdir -p sarif-results - name: Autobuild - uses: github/codeql-action/autobuild@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 + uses: github/codeql-action/autobuild@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 - name: Perform CodeQL Analysis id: codeql_analyze - uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 + uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 with: category: "/language:${{ matrix.language }}" output: sarif-results/${{ matrix.language }} diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 9f6a012dd..3b4bcf8ac 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -941,7 +941,7 @@ jobs: - name: Upload Trivy results if: env.TRIGGER_EVENT != 'pull_request' && steps.trivy-check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: 'trivy-results.sarif' category: ${{ env.TRIVY_SARIF_CATEGORY }} @@ -1228,7 +1228,7 @@ jobs: - name: Upload Trivy scan results if: always() && steps.trivy-pr-check.outputs.exists == 'true' - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: 'trivy-pr-results.sarif' category: ${{ env.TRIVY_SARIF_CATEGORY }} diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 99d68fd96..1f6f3e9aa 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -618,7 +618,7 @@ jobs: trivyignores: '.trivyignore' - name: Upload Trivy results - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: 'trivy-nightly.sarif' category: 'trivy-nightly' diff --git a/.github/workflows/security-pr.yml b/.github/workflows/security-pr.yml index 17b4dd2c6..1291f65fa 100644 --- a/.github/workflows/security-pr.yml +++ b/.github/workflows/security-pr.yml @@ -392,7 +392,7 @@ jobs: - name: Upload Trivy SARIF to GitHub Security if: always() && steps.trivy-sarif-check.outputs.exists == 'true' # github/codeql-action v4.36.2 - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: 'trivy-binary-results.sarif' category: ${{ steps.pr-info.outputs.is_push == 'true' && format('security-scan-{0}', github.event_name == 'workflow_run' && github.event.workflow_run.head_branch || github.ref_name) || format('security-scan-pr-{0}', steps.pr-info.outputs.pr_number) }} diff --git a/.github/workflows/security-weekly-rebuild.yml b/.github/workflows/security-weekly-rebuild.yml index e582efc48..ffd3e4da1 100644 --- a/.github/workflows/security-weekly-rebuild.yml +++ b/.github/workflows/security-weekly-rebuild.yml @@ -121,7 +121,7 @@ jobs: - name: Upload Trivy results to GitHub Security id: upload-trivy-weekly - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 with: sarif_file: 'trivy-weekly-results.sarif' category: ${{ env.TRIVY_SARIF_CATEGORY }} diff --git a/.github/workflows/supply-chain-pr.yml b/.github/workflows/supply-chain-pr.yml index fa4548ee5..c06d195e9 100644 --- a/.github/workflows/supply-chain-pr.yml +++ b/.github/workflows/supply-chain-pr.yml @@ -421,7 +421,7 @@ jobs: - name: Upload SARIF to GitHub Security if: steps.set-target.outputs.image_name != '' - uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4 + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4 continue-on-error: true with: sarif_file: grype-results.sarif From ac26ef64ec187102b996912c9c1070e464def553 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Thu, 13 Aug 2026 22:25:16 -0400 Subject: [PATCH 24/44] chore: bump Go toolchain to 1.26.6 and update golang.org/x deps --- Dockerfile | 2 +- agent/go.mod | 2 +- backend/go.mod | 4 ++-- backend/go.sum | 8 ++++---- go.work | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 715730853..bce0941de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ ARG BUILD_DEBUG=0 # ---- Pinned Toolchain Versions ---- # renovate: datasource=docker depName=golang versioning=docker -ARG GO_VERSION=1.26.5 +ARG GO_VERSION=1.26.6 # renovate: datasource=docker depName=alpine versioning=docker ARG ALPINE_IMAGE=alpine:3.24.1@sha256:28bd5fe8b56d1bd048e5babf5b10710ebe0bae67db86916198a6eec434943f8b diff --git a/agent/go.mod b/agent/go.mod index 78e4ba79c..9f3174863 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -1,6 +1,6 @@ module github.com/Wikid82/charon/agent -go 1.26.5 +go 1.26.6 require ( github.com/gorilla/websocket v1.5.3 diff --git a/backend/go.mod b/backend/go.mod index 82d0aa900..19a9d28be 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,6 +1,6 @@ module github.com/Wikid82/charon/backend -go 1.26.5 +go 1.26.6 require ( filippo.io/age v1.3.1 @@ -21,7 +21,7 @@ require ( github.com/stretchr/testify v1.11.1 github.com/studio-b12/gowebdav v0.13.0 golang.org/x/crypto v0.55.0 - golang.org/x/mod v0.39.0 + golang.org/x/mod v0.40.0 golang.org/x/net v0.58.0 golang.org/x/oauth2 v0.36.0 golang.org/x/text v0.41.0 diff --git a/backend/go.sum b/backend/go.sum index 315507cdf..7cd563f9a 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -220,8 +220,8 @@ golang.org/x/arch v0.30.0 h1:sB9h+1gRGa2+LauFSV0tm8bK1J2yo1bx6/Uyi/P6DTU= golang.org/x/arch v0.30.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= -golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74= -golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= +golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs= +golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE= golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= @@ -236,8 +236,8 @@ golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI= +golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/go.work b/go.work index fe3169ac3..f8865202d 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.26.5 +go 1.26.6 use ( ./agent From c510085f3be7d3acb958d7e740f85c7cf828f3fa Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Thu, 13 Aug 2026 22:25:31 -0400 Subject: [PATCH 25/44] chore: bump electron-to-chromium to 1.5.406 --- frontend/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 83a64b0d5..083f2e39c 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -5371,9 +5371,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.405", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.405.tgz", - "integrity": "sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew==", + "version": "1.5.406", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.406.tgz", + "integrity": "sha512-hWH5ORBi3d0IipnMh7BN5GDTaAmrSSSWmznwt2zltdiRNEWoEQyTwF0FFSBxzHO7hLSRT6loQu3IQGV0wg/Tvg==", "dev": true, "license": "ISC" }, From 939ddb785dfe1669a33d178024b396365339d502 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 14 Aug 2026 02:31:34 +0000 Subject: [PATCH 26/44] chore(deps): update go-non-major --- .github/skills/examples/gorm-scanner-ci-workflow.yml | 2 +- .github/workflows/benchmark.yml | 2 +- .github/workflows/codecov-upload.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/e2e-tests-split.yml | 2 +- .github/workflows/nightly-build.yml | 2 +- .github/workflows/orthrus-build.yml | 2 +- .github/workflows/quality-checks.yml | 2 +- .github/workflows/release-goreleaser.yml | 2 +- .github/workflows/renovate.yml | 2 +- scripts/security-scan.sh | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/skills/examples/gorm-scanner-ci-workflow.yml b/.github/skills/examples/gorm-scanner-ci-workflow.yml index 0666e5b74..fb5383608 100644 --- a/.github/skills/examples/gorm-scanner-ci-workflow.yml +++ b/.github/skills/examples/gorm-scanner-ci-workflow.yml @@ -25,7 +25,7 @@ jobs: - name: Setup Go uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7 with: - go-version: "1.26.5" + go-version: "1.26.6" - name: Run GORM Security Scanner id: gorm-scan diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 5f605a4c1..631d34d1d 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -12,7 +12,7 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' GOTOOLCHAIN: local # Minimal permissions at workflow level; write permissions granted at job level for push only diff --git a/.github/workflows/codecov-upload.yml b/.github/workflows/codecov-upload.yml index 5e596211a..a868ee9a7 100644 --- a/.github/workflows/codecov-upload.yml +++ b/.github/workflows/codecov-upload.yml @@ -28,7 +28,7 @@ concurrency: cancel-in-progress: true env: - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' NODE_VERSION: '24.19.0' GOTOOLCHAIN: local diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6a0aa2ad5..39132accf 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -15,7 +15,7 @@ concurrency: env: GOTOOLCHAIN: local - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' permissions: contents: read diff --git a/.github/workflows/e2e-tests-split.yml b/.github/workflows/e2e-tests-split.yml index 7e7acf6dc..3fb9611d6 100644 --- a/.github/workflows/e2e-tests-split.yml +++ b/.github/workflows/e2e-tests-split.yml @@ -83,7 +83,7 @@ on: env: NODE_VERSION: '24.19.0' - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' GOTOOLCHAIN: local DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: ${{ github.repository_owner }}/charon diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index 1f6f3e9aa..1929e1972 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -15,7 +15,7 @@ on: default: "false" env: - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' NODE_VERSION: '24.19.0' GOTOOLCHAIN: local GHCR_REGISTRY: ghcr.io diff --git a/.github/workflows/orthrus-build.yml b/.github/workflows/orthrus-build.yml index 48aa2bcb7..23f0ada33 100644 --- a/.github/workflows/orthrus-build.yml +++ b/.github/workflows/orthrus-build.yml @@ -29,7 +29,7 @@ env: GHCR_REGISTRY: ghcr.io DOCKERHUB_REGISTRY: docker.io IMAGE_NAME: wikid82/orthrus - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' permissions: contents: read diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index 6a679062a..f7948c6fb 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -16,7 +16,7 @@ permissions: checks: write env: - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' NODE_VERSION: '24.19.0' GOTOOLCHAIN: local diff --git a/.github/workflows/release-goreleaser.yml b/.github/workflows/release-goreleaser.yml index 151492f9e..05cdc55ce 100644 --- a/.github/workflows/release-goreleaser.yml +++ b/.github/workflows/release-goreleaser.yml @@ -10,7 +10,7 @@ concurrency: cancel-in-progress: false env: - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' NODE_VERSION: '24.19.0' GOTOOLCHAIN: local diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index bc1281087..6d63abc06 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -16,7 +16,7 @@ permissions: issues: write env: - GO_VERSION: '1.26.5' + GO_VERSION: '1.26.6' jobs: renovate: diff --git a/scripts/security-scan.sh b/scripts/security-scan.sh index b207b5b84..b057b2a08 100755 --- a/scripts/security-scan.sh +++ b/scripts/security-scan.sh @@ -20,7 +20,7 @@ echo "🔒 Running local security scan..." if ! command -v govulncheck &> /dev/null; then echo -e "${YELLOW}Installing govulncheck...${NC}" # renovate: datasource=go depName=golang.org/x/vuln - go install golang.org/x/vuln/cmd/govulncheck@v1.6.0 + go install golang.org/x/vuln/cmd/govulncheck@v1.7.0 fi # Run govulncheck on backend Go code From 6bf066f8bd60814625657caeb52a97ca243cefc1 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 14 Aug 2026 05:32:32 -0400 Subject: [PATCH 27/44] feat(security): add opt-in SARIF output mode to local Semgrep script and add CI parity guard Adds a backward-compatible SEMGREP_SARIF_OUTPUT hook to semgrep-scan.sh so CI can reuse the exact same rule configs, exclusions, and severity gate as the local pre-commit/pre-push scan instead of duplicating them in workflow YAML. Every existing call site (lefthook pre-commit, security-full, make security-local) never sets this var, so behavior is byte-identical to before. Adds scripts/ci/check-semgrep-parity.sh, a structural guard that verifies the upcoming Semgrep CI workflow keeps delegating to this script rather than reintroducing an inline semgrep invocation. Also extracts the branch-list assertion helpers shared between check-codeql-parity.sh and check-semgrep-parity.sh into scripts/ci/lib/workflow-yaml-asserts.sh (no behavior change). --- scripts/ci/check-codeql-parity.sh | 81 +------------------- scripts/ci/check-semgrep-parity.sh | 77 +++++++++++++++++++ scripts/ci/lib/workflow-yaml-asserts.sh | 97 ++++++++++++++++++++++++ scripts/pre-commit-hooks/semgrep-scan.sh | 8 +- 4 files changed, 185 insertions(+), 78 deletions(-) create mode 100755 scripts/ci/check-semgrep-parity.sh create mode 100755 scripts/ci/lib/workflow-yaml-asserts.sh diff --git a/scripts/ci/check-codeql-parity.sh b/scripts/ci/check-codeql-parity.sh index 13cd1058f..8c5126f1f 100755 --- a/scripts/ci/check-codeql-parity.sh +++ b/scripts/ci/check-codeql-parity.sh @@ -1,6 +1,10 @@ #!/usr/bin/env bash set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/workflow-yaml-asserts.sh +source "${SCRIPT_DIR}/lib/workflow-yaml-asserts.sh" + CODEQL_WORKFLOW=".github/workflows/codeql.yml" TASKS_FILE=".vscode/tasks.json" GO_PRECOMMIT_SCRIPT="scripts/pre-commit-hooks/codeql-go-scan.sh" @@ -26,83 +30,6 @@ ensure_task_command() { "$tasks_file" >/dev/null } -ensure_event_branches() { - local workflow_file="$1" - local event_name="$2" - local expected_line="$3" - - awk -v event_name="$event_name" -v expected_line="$expected_line" ' - /^on:/ { - in_on = 1 - next - } - - in_on && $1 == event_name ":" { - in_event = 1 - next - } - - in_on && in_event && $1 == "branches:" { - line = $0 - gsub(/^ +/, "", line) - if (line == expected_line) { - found = 1 - } - in_event = 0 - next - } - - in_on && in_event && $1 ~ /^[a-z_]+:$/ { - in_event = 0 - } - - END { - exit found ? 0 : 1 - } - ' "$workflow_file" -} - -ensure_event_branches_with_yq() { - local workflow_file="$1" - local event_name="$2" - shift 2 - local expected_branches=("$@") - - local expected_json - local actual_json - - expected_json="$(printf '%s\n' "${expected_branches[@]}" | jq -R . | jq -s .)" - - if actual_json="$(yq eval -o=json ".on.${event_name}.branches // []" "$workflow_file" 2>/dev/null)"; then - : - elif actual_json="$(yq -o=json ".on.${event_name}.branches // []" "$workflow_file" 2>/dev/null)"; then - : - else - return 1 - fi - - jq -e \ - --argjson expected "$expected_json" \ - 'if type != "array" then false else ((map(tostring) | unique | sort) == ($expected | map(tostring) | unique | sort)) end' \ - <<<"$actual_json" >/dev/null -} - -ensure_event_branches_semantic() { - local workflow_file="$1" - local event_name="$2" - local fallback_line="$3" - shift 3 - local expected_branches=("$@") - - if command -v yq >/dev/null 2>&1; then - if ensure_event_branches_with_yq "$workflow_file" "$event_name" "${expected_branches[@]}"; then - return 0 - fi - fi - - ensure_event_branches "$workflow_file" "$event_name" "$fallback_line" -} - [[ -f "$CODEQL_WORKFLOW" ]] || fail "Missing workflow file: $CODEQL_WORKFLOW" [[ -f "$TASKS_FILE" ]] || fail "Missing tasks file: $TASKS_FILE" [[ -f "$GO_PRECOMMIT_SCRIPT" ]] || fail "Missing pre-commit script: $GO_PRECOMMIT_SCRIPT" diff --git a/scripts/ci/check-semgrep-parity.sh b/scripts/ci/check-semgrep-parity.sh new file mode 100755 index 000000000..dd7e4d0a2 --- /dev/null +++ b/scripts/ci/check-semgrep-parity.sh @@ -0,0 +1,77 @@ +#!/usr/bin/env bash +# Structural parity guard for the Semgrep CI workflow, modeled on +# check-codeql-parity.sh's approach (grep/structural assertions, not full +# YAML parsing). Unlike the CodeQL guard, this script does NOT compare two +# independent rule-config lists between local and CI — the Semgrep CI +# workflow delegates directly to scripts/pre-commit-hooks/semgrep-scan.sh +# for both its SARIF-producing pass and its hard-fail gate pass, so there is +# exactly one place in the repo that defines --config/--exclude/ +# --exclude-rule values. See docs/plans/current_spec.md §2.7/§3.4 for the +# full rationale. +# +# This guard instead checks the invariants that remain worth checking even +# with zero config duplication: +# 1. Required files exist. +# 2. The additive SEMGREP_SARIF_OUTPUT hook is still present in +# semgrep-scan.sh (a future refactor could drop it without realizing +# CI depends on it). +# 3. semgrep.yml still delegates to the real script for both passes, +# rather than a future edit reintroducing an inline `semgrep scan` +# call (which would silently reintroduce config duplication). +# 4. The pinned container image reference has both a tag and a digest +# (catches an accidental un-pin, e.g. a quick edit to `:latest`). +# 5. pull_request/push trigger branches match [main, nightly, development]. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=lib/workflow-yaml-asserts.sh +source "${SCRIPT_DIR}/lib/workflow-yaml-asserts.sh" + +SEMGREP_WORKFLOW=".github/workflows/semgrep.yml" +SEMGREP_SCRIPT="scripts/pre-commit-hooks/semgrep-scan.sh" + +fail() { + local message="$1" + echo "::error title=Semgrep parity drift::${message}" + exit 1 +} + +# --- Check 1: required files exist --- +[[ -f "$SEMGREP_WORKFLOW" ]] || fail "Missing workflow file: $SEMGREP_WORKFLOW" +[[ -f "$SEMGREP_SCRIPT" ]] || fail "Missing pre-commit script: $SEMGREP_SCRIPT" + +command -v jq >/dev/null 2>&1 || fail "jq is required for semantic Semgrep parity checks" + +# --- Check 2: additive SARIF hook still present in the script --- +grep -Fq 'SEMGREP_SARIF_OUTPUT' "$SEMGREP_SCRIPT" || fail "$SEMGREP_SCRIPT must retain the SEMGREP_SARIF_OUTPUT hook so CI can produce SARIF via the same script CI/local both use" + +# --- Check 3: workflow delegates to the real script for both passes, --- +# --- rather than a reimplemented/inlined `semgrep scan ...` invocation --- +grep -Fq 'SEMGREP_SARIF_OUTPUT' "$SEMGREP_WORKFLOW" || fail "$SEMGREP_WORKFLOW must set SEMGREP_SARIF_OUTPUT for its SARIF-producing pass" +grep -Fq "$SEMGREP_SCRIPT" "$SEMGREP_WORKFLOW" || fail "$SEMGREP_WORKFLOW must delegate to $SEMGREP_SCRIPT instead of reimplementing the semgrep scan invocation inline" + +# Count distinct delegating calls to the real script; there must be at least +# two (the SARIF pass and the hard-fail gate pass). +DELEGATE_CALL_COUNT="$(grep -Fc "bash ${SEMGREP_SCRIPT}" "$SEMGREP_WORKFLOW" || true)" +if [[ "$DELEGATE_CALL_COUNT" -lt 2 ]]; then + fail "$SEMGREP_WORKFLOW must call 'bash $SEMGREP_SCRIPT' at least twice (SARIF pass + hard-fail gate pass); found $DELEGATE_CALL_COUNT" +fi + +! grep -Eq '^\s*semgrep scan\b' "$SEMGREP_WORKFLOW" || fail "$SEMGREP_WORKFLOW must not contain an inline 'semgrep scan' invocation — delegate to $SEMGREP_SCRIPT" + +# --- Check 4: pinned image reference has both tag and digest --- +grep -Eq 'semgrep/semgrep:[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64}' "$SEMGREP_WORKFLOW" || fail "$SEMGREP_WORKFLOW must pin the semgrep/semgrep image with both an exact tag and a sha256 digest" + +# --- Check 5: trigger branches --- +ensure_event_branches_semantic \ + "$SEMGREP_WORKFLOW" \ + "pull_request" \ + "branches: [main, nightly, development]" \ + "main" "nightly" "development" || fail "semgrep.yml pull_request branches must be [main, nightly, development]" +ensure_event_branches_semantic \ + "$SEMGREP_WORKFLOW" \ + "push" \ + "branches: [main, nightly, development]" \ + "main" "nightly" "development" || fail "semgrep.yml push branches must be [main, nightly, development]" + +echo "Semgrep parity check passed (script delegation + SARIF hook present + image pin format + trigger branches)" diff --git a/scripts/ci/lib/workflow-yaml-asserts.sh b/scripts/ci/lib/workflow-yaml-asserts.sh new file mode 100755 index 000000000..42b702d62 --- /dev/null +++ b/scripts/ci/lib/workflow-yaml-asserts.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# Shared assertion helpers for CI parity-guard scripts (e.g. +# check-codeql-parity.sh, check-semgrep-parity.sh) that need to verify a +# GitHub Actions workflow's `on..branches` list matches an expected +# set of branches. Extracted per CLAUDE.md's "consolidate after second +# occurrence" DRY guideline once a second parity script needed the same +# branch-list assertion logic. +# +# Intended usage: source this file, then call ensure_event_branches_semantic. +# This file only defines functions — it has no side effects when sourced and +# does not set -euo pipefail itself (the sourcing script controls that). + +# ensure_event_branches: AWK-based fallback branch check. Compares the +# literal `branches:` line under `on.:` against expected_line, +# e.g. "branches: [main, nightly, development]". Used when `yq` is +# unavailable or fails. +ensure_event_branches() { + local workflow_file="$1" + local event_name="$2" + local expected_line="$3" + + awk -v event_name="$event_name" -v expected_line="$expected_line" ' + /^on:/ { + in_on = 1 + next + } + + in_on && $1 == event_name ":" { + in_event = 1 + next + } + + in_on && in_event && $1 == "branches:" { + line = $0 + gsub(/^ +/, "", line) + if (line == expected_line) { + found = 1 + } + in_event = 0 + next + } + + in_on && in_event && $1 ~ /^[a-z_]+:$/ { + in_event = 0 + } + + END { + exit found ? 0 : 1 + } + ' "$workflow_file" +} + +# ensure_event_branches_with_yq: semantic (order-independent) branch check +# using yq + jq to parse the workflow YAML directly, rather than matching a +# literal formatted line. +ensure_event_branches_with_yq() { + local workflow_file="$1" + local event_name="$2" + shift 2 + local expected_branches=("$@") + + local expected_json + local actual_json + + expected_json="$(printf '%s\n' "${expected_branches[@]}" | jq -R . | jq -s .)" + + if actual_json="$(yq eval -o=json ".on.${event_name}.branches // []" "$workflow_file" 2>/dev/null)"; then + : + elif actual_json="$(yq -o=json ".on.${event_name}.branches // []" "$workflow_file" 2>/dev/null)"; then + : + else + return 1 + fi + + jq -e \ + --argjson expected "$expected_json" \ + 'if type != "array" then false else ((map(tostring) | unique | sort) == ($expected | map(tostring) | unique | sort)) end' \ + <<<"$actual_json" >/dev/null +} + +# ensure_event_branches_semantic: prefers the semantic yq-based check when +# `yq` is installed, falling back to the literal-line AWK check otherwise. +ensure_event_branches_semantic() { + local workflow_file="$1" + local event_name="$2" + local fallback_line="$3" + shift 3 + local expected_branches=("$@") + + if command -v yq >/dev/null 2>&1; then + if ensure_event_branches_with_yq "$workflow_file" "$event_name" "${expected_branches[@]}"; then + return 0 + fi + fi + + ensure_event_branches "$workflow_file" "$event_name" "$fallback_line" +} diff --git a/scripts/pre-commit-hooks/semgrep-scan.sh b/scripts/pre-commit-hooks/semgrep-scan.sh index 6eb5d6a7a..475a90e6a 100755 --- a/scripts/pre-commit-hooks/semgrep-scan.sh +++ b/scripts/pre-commit-hooks/semgrep-scan.sh @@ -39,11 +39,17 @@ else TARGETS=(Dockerfile backend frontend/src scripts .github/workflows) fi +if [ -n "${SEMGREP_SARIF_OUTPUT:-}" ]; then + OUTPUT_FLAGS=(--sarif --output "${SEMGREP_SARIF_OUTPUT}") +else + OUTPUT_FLAGS=(--error) +fi + semgrep scan \ "${SEMGREP_CONFIGS[@]}" \ --severity ERROR \ --severity WARNING \ - --error \ + "${OUTPUT_FLAGS[@]}" \ --exclude "frontend/node_modules" \ --exclude "frontend/coverage" \ --exclude "frontend/dist" \ From a0aa941541a3f4bc579b92cac075edc777073fd9 Mon Sep 17 00:00:00 2001 From: Jeremy Hatfield Date: Fri, 14 Aug 2026 09:35:31 +0000 Subject: [PATCH 28/44] fix(security): patch vulnerable transitive dependency in bundled CrowdSec binaries Pin golang.org/x/mod to the fixed release in the crowdsec-builder Docker stage, following the same explicit go-get-then-tidy pattern already used for the other transitive CVE patches in that stage. go mod tidy's MVS resolution otherwise lands on an older, vulnerable version even though Charon's own backend already depends on the fixed release. Verified locally: `docker build --target crowdsec-builder` compiles clean and `go version -m` on both /usr/local/bin/crowdsec and cscli confirms the patched module version is embedded in the resulting binaries. --- Dockerfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index bce0941de..6cabd91df 100644 --- a/Dockerfile +++ b/Dockerfile @@ -590,6 +590,12 @@ RUN set -e; \ # GHSA-r277-6w6q-xmqw: kin-openapi ValidationHandler.Load() Fail-Open Authentication Bypass via NoopAuthenticationFunc Default # renovate: datasource=go depName=github.com/getkin/kin-openapi _retry go get github.com/getkin/kin-openapi@v0.144.0; \ + # CVE-2026-56864 / CVE-2026-56865: golang.org/x/mod/sumdb GOSUMDB tile-verification bypass + # (a colluding GOPROXY+GOSUMDB pair could forge sumdb tiles / serve module content outside + # the transparency log). Affects /usr/local/bin/crowdsec and /usr/local/bin/cscli — go mod + # tidy's MVS resolution otherwise lands on v0.38.0. Fix available at v0.40.0. + # renovate: datasource=go depName=golang.org/x/mod + _retry go get golang.org/x/mod@v0.40.0; \ _retry go mod tidy # Fix compatibility issues with expr-lang v1.17.7 From 2fbecf07b3d19e381099fb2e4303bfead6e29836 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 14 Aug 2026 05:36:40 -0400 Subject: [PATCH 29/44] feat(security): add pinned Semgrep SAST scan to CI, mirroring local pre-commit/pre-push scan Adds .github/workflows/semgrep.yml, running Semgrep inside the official semgrep/semgrep:1.173.0 container image (pinned by tag + digest), triggered on pull_request/push to [main, nightly, development], workflow_dispatch, and a weekly schedule. The job delegates to scripts/pre-commit-hooks/semgrep-scan.sh for both a non-blocking SARIF-producing pass (uploaded to the GitHub Security tab) and a hard-fail gate pass, so CI reproduces the exact same rule configs/exclusions/severity gate as the local scan instead of maintaining a second, duplicated invocation. The parity guard from the previous commit runs first to structurally verify this stays true. Mirrors codeql.yml's trigger/concurrency/permissions shape and security-pr.yml's SARIF-then-gate step split, reusing the same pinned actions/checkout, codeql-action/upload-sarif, and upload-artifact SHAs already in use elsewhere in this repo. --- .github/workflows/semgrep.yml | 100 ++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/semgrep.yml diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 000000000..f1b0366fd --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,100 @@ +name: Semgrep - SAST Scan + +on: + pull_request: + branches: [main, nightly, development] + push: + branches: [main, nightly, development] + workflow_dispatch: + schedule: + - cron: '0 4 * * 1' # Mondays 04:00 UTC — offset 1h after CodeQL's 03:00 to avoid runner contention + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + +permissions: + contents: read + security-events: write + actions: read + pull-requests: read + +jobs: + semgrep-scan: + name: Semgrep SAST Scan + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + security-events: write + actions: read + pull-requests: read + container: + image: semgrep/semgrep:1.173.0@sha256:67319956da3dcb58baf5b322899c15458e3963e7018a86aeeb5cd224e69cb77a # semgrep/semgrep 1.173.0 + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ github.ref }} + + - name: Configure git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + + - name: Verify Semgrep parity guard + run: bash scripts/ci/check-semgrep-parity.sh + + - name: Verify Semgrep version + run: semgrep --version + + - name: Run Semgrep (SARIF output) + id: semgrep_sarif + continue-on-error: true + env: + SEMGREP_SARIF_OUTPUT: semgrep-results.sarif + run: bash scripts/pre-commit-hooks/semgrep-scan.sh + + - name: Check Semgrep SARIF output exists + id: semgrep_sarif_check + if: always() + run: | + if [ -f semgrep-results.sarif ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "No Semgrep SARIF output found; skipping SARIF upload" + fi + + - name: Upload Semgrep SARIF to GitHub Security + if: always() && steps.semgrep_sarif_check.outputs.exists == 'true' + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + with: + sarif_file: semgrep-results.sarif + category: semgrep + continue-on-error: true + + - name: Run Semgrep (hard-fail gate) + run: bash scripts/pre-commit-hooks/semgrep-scan.sh + + - name: Upload SARIF artifact + if: always() && steps.semgrep_sarif_check.outputs.exists == 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v4.4.3 + with: + name: semgrep-sarif-${{ github.run_id }} + path: semgrep-results.sarif + retention-days: 14 + continue-on-error: true + + - name: Create job summary + if: always() + run: | + { + echo "## Semgrep SAST Scan Results" + echo "" + echo "**Rulesets**: p/golang, p/javascript, p/typescript, p/react, p/secrets, p/dockerfile" + echo "**Severity Gate**: ERROR, WARNING (--error)" + if [ "${{ job.status }}" == "success" ]; then + echo "PASSED: no blocking Semgrep findings" + else + echo "FAILED: Semgrep reported blocking findings — see step logs and the Security tab" + fi + } >> "$GITHUB_STEP_SUMMARY" From 7c6fb04fc3d8e96e8631db03589e5a48f3f0ecea Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 14 Aug 2026 05:38:34 -0400 Subject: [PATCH 30/44] docs: document Semgrep CI scan in SECURITY.md and ARCHITECTURE.md Adds Semgrep to SECURITY.md's Automated Scanning table and describes the new semgrep.yml workflow under Scanning Workflows. Updates ARCHITECTURE.md's tech-stack table, CI Jobs list, and Container Scanning components list to include Semgrep alongside the existing Trivy/CodeQL/Grype coverage. --- ARCHITECTURE.md | 5 +++-- SECURITY.md | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index e11d9b66b..f77c29f1d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -163,7 +163,7 @@ graph TB | **Base Image** | Debian Trixie Slim | Latest | Security-hardened base | | **CI/CD** | GitHub Actions | N/A | Automated testing and deployment | | **Registry** | Docker Hub + GHCR | N/A | Image distribution | -| **Security Scanning** | Trivy + Grype | Latest | Vulnerability detection | +| **Security Scanning** | Trivy + Grype + Semgrep | Latest | Vulnerability detection | | **SBOM Generation** | Syft | Latest | Software Bill of Materials | | **Signature Verification** | Cosign | Latest | Supply chain integrity | @@ -1373,7 +1373,7 @@ go test ./integration/... 1. **Lint:** golangci-lint, ESLint, markdownlint, hadolint 2. **Test:** Go tests, Vitest, Playwright -3. **Security:** Trivy, CodeQL, Grype, Govulncheck +3. **Security:** Trivy, CodeQL, Grype, Govulncheck, Semgrep 4. **Build:** Docker image build 5. **Coverage:** Upload to Codecov (85% gate) — `backend`, `frontend`, and `agent` each upload under a distinct Codecov flag @@ -1499,6 +1499,7 @@ go test ./integration/... - Trivy: Fast vulnerability scanning (filesystem) - Grype: Deep image scanning (layers, dependencies) - CodeQL: Static analysis (Go, JavaScript) + - Semgrep: Static analysis for security anti-patterns (Go, JS/TS, React, secrets, Dockerfile) 3. **Cryptographic Signing:** - Cosign signs Docker images with keyless signing (OIDC) diff --git a/SECURITY.md b/SECURITY.md index deae334b2..bf21754fd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -997,6 +997,7 @@ for developer iteration. Tag-only overrides MUST NOT be used in CI contexts. |------|---------| | Trivy | Container image vulnerability scanning | | CodeQL | Static analysis for Go and JavaScript | +| Semgrep | Static analysis for security anti-patterns (Go, JS/TS, React, secrets, Dockerfile) | | govulncheck | Go module vulnerability scanning | | golangci-lint (gosec) | Go code linting | | npm audit | Frontend dependency scanning | @@ -1019,6 +1020,14 @@ artifacts for 90 days. **PR-Specific Scanning** — extracts and scans only the Charon application binary on each pull request. Fails the PR if CRITICAL or HIGH vulnerabilities are found in application code. +**Semgrep SAST Scan** (`.github/workflows/semgrep.yml`) — runs on every push and pull request to +`main`, `nightly`, and `development`, on manual dispatch, and weekly on Mondays at 04:00 UTC. Scans +the full repository inside a pinned `semgrep/semgrep` container using the `p/golang`, +`p/javascript`, `p/typescript`, `p/react`, `p/secrets`, and `p/dockerfile` rulesets — the same +rule configs, exclusions, and ERROR/WARNING severity gate developers already run locally via +`scripts/pre-commit-hooks/semgrep-scan.sh`. Uploads SARIF results to the GitHub Security tab and +fails the build on any blocking finding. + ### Manual Reviews - Security code reviews for all major features From 3257028ea05c1118b88db25aefa5a9b9fe4e3eaa Mon Sep 17 00:00:00 2001 From: Jeremy Hatfield Date: Fri, 14 Aug 2026 09:39:39 +0000 Subject: [PATCH 31/44] fix: correct jq escaping bug in PR Trivy findings renderer The "Diagnose unsuppressed PR Trivy blockers" step's per-finding jq program was double-escaping double quotes (\") inside an already single-quoted bash string, so bash passed literal backslashes through to jq and the program failed to compile ("unexpected INVALID_CHARACTER"). The step's pass/fail gate was unaffected (it reads FINDINGS_COUNT from a separate, correctly-escaped jq call), but the per-finding detail lines always fell back to "unable to render parsed findings". Also widened the package-name capture regex to include `/`, since Go module paths (e.g. golang.org/x/mod) were being truncated at the first slash. Verified by replaying the actual SARIF shape from the failed run (PR #1249) through both the old and new jq programs locally. --- .github/workflows/docker-build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index 3b4bcf8ac..44fee90a7 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -1331,15 +1331,15 @@ jobs: $result.ruleId // ($result.rule // {} | .id) // ( - if ($result.ruleIndex != null and (($run.tool.driver.rules? // null) | type) == \"array\") then - ($run.tool.driver.rules[$result.ruleIndex].id // \"unknown\") + if ($result.ruleIndex != null and (($run.tool.driver.rules? // null) | type) == "array") then + ($run.tool.driver.rules[$result.ruleIndex].id // "unknown") else - \"unknown\" + "unknown" end ) )) | package: \(( - ($result.message.text // \"\") - | (try capture(\"(?i)(?:Package|PkgName|Pkg|Library)\\\\s*[:=]\\\\s*`?(?[A-Za-z0-9._+:+-]+)`?\").pkg catch \"n/a\") + ($result.message.text // "") + | (try capture("(?i)(?:Package|PkgName|Pkg|Library)\\s*[:=]\\s*`?(?[A-Za-z0-9._+:/-]+)`?").pkg catch "n/a") ))" ' "${SARIF_PATH}"; then echo "- unable to render parsed findings" From 9dc2be4e4b4c04d1c6a48d2adc0105289f895814 Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 14 Aug 2026 06:06:45 -0400 Subject: [PATCH 32/44] chore(deps): track pinned Semgrep image in Renovate Add a custom regex manager so Renovate opens update PRs for the semgrep/semgrep container image pinned in .github/workflows/semgrep.yml, matching this repo's existing pattern for other pinned image digests. --- .github/renovate.json | 13 +++++++++++++ .github/workflows/semgrep.yml | 1 + 2 files changed, 14 insertions(+) diff --git a/.github/renovate.json b/.github/renovate.json index d36fa3470..a6a9f3860 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -244,6 +244,19 @@ "datasourceTemplate": "golang-version", "versioningTemplate": "semver" }, + { + "customType": "regex", + "description": "Track pinned Semgrep container image digest in the Semgrep CI workflow", + "managerFilePatterns": [ + "/^\\.github/workflows/semgrep\\.yml$/" + ], + "matchStrings": [ + "#\\s*renovate:\\s*datasource=docker\\s+depName=semgrep/semgrep\\s*\\n\\s*image:\\s*semgrep/semgrep:(?[^@\\s]+)@(?sha256:[a-f0-9]+)" + ], + "depNameTemplate": "semgrep/semgrep", + "datasourceTemplate": "docker", + "versioningTemplate": "docker" + }, { "customType": "regex", "description": "Track NODE_VERSION in Actions workflows", diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml index f1b0366fd..b28fdd1c1 100644 --- a/.github/workflows/semgrep.yml +++ b/.github/workflows/semgrep.yml @@ -30,6 +30,7 @@ jobs: actions: read pull-requests: read container: + # renovate: datasource=docker depName=semgrep/semgrep image: semgrep/semgrep:1.173.0@sha256:67319956da3dcb58baf5b322899c15458e3963e7018a86aeeb5cd224e69cb77a # semgrep/semgrep 1.173.0 steps: - name: Checkout repository From 035387edf0ddabc5145f04f1877ff56cae75208e Mon Sep 17 00:00:00 2001 From: Wikid82 Date: Fri, 14 Aug 2026 06:06:57 -0400 Subject: [PATCH 33/44] docs: finalize Semgrep CI plan, QA report, and manual test tracking Record the final plan revisions and QA verification results from the Semgrep CI rollout, and track the two known CI-only follow-ups (timeout tuning, Renovate coverage) as a manual test plan to verify once the workflow runs for real. --- docs/issues/semgrep-ci-manual-test-plan.md | 128 ++++ docs/plans/current_spec.md | 832 +++++++++++---------- docs/reports/qa_report.md | 136 ++-- 3 files changed, 631 insertions(+), 465 deletions(-) create mode 100644 docs/issues/semgrep-ci-manual-test-plan.md diff --git a/docs/issues/semgrep-ci-manual-test-plan.md b/docs/issues/semgrep-ci-manual-test-plan.md new file mode 100644 index 000000000..55bb0def9 --- /dev/null +++ b/docs/issues/semgrep-ci-manual-test-plan.md @@ -0,0 +1,128 @@ +--- +title: "Manual Test Plan - Semgrep CI Security Scan" +status: Open +priority: Medium +labels: testing, ci, security +--- + +# Test Objective + +Confirm that the new `.github/workflows/semgrep.yml` CI workflow behaves correctly once it +actually runs against a live GitHub Actions PR — image pull, scan execution, SARIF upload, and +the hard-fail gate. This is the one part of the Semgrep CI Security Scan feature that could not +be verified locally: Supervisor code review and the qa-security audit both passed (see +`docs/plans/current_spec.md` and `docs/reports/qa_report.md`), but neither can observe a real +GitHub Actions runner pulling the pinned container image or timing a full-repo scan under actual +CI conditions. + +# What Was Built + +- `scripts/pre-commit-hooks/semgrep-scan.sh` gained an additive, backward-compatible + `SEMGREP_SARIF_OUTPUT` env var so CI can reuse the exact same scan invocation developers already + run locally, for both a SARIF-producing pass and a hard-fail gate pass. +- `.github/workflows/semgrep.yml` runs that script inside a pinned + `semgrep/semgrep:1.173.0@sha256:...` container on every push/PR to `main`, `nightly`, and + `development`, on manual dispatch, and weekly (Mondays 04:00 UTC). It uploads SARIF results to + the GitHub Security tab and hard-fails the job on any ERROR/WARNING-severity finding. +- `scripts/ci/check-semgrep-parity.sh` guards against the workflow and the local script silently + drifting apart in the future. +- `SECURITY.md` and `ARCHITECTURE.md` were updated to document the new coverage. + +Commits: `6bf066f8` (script hook + parity guard), `2fbecf07` (workflow), `7c6fb04f` (docs). + +# Prerequisites + +- A pull request open against `development` (or `main`/`nightly`) that includes these three + commits, so `semgrep.yml`'s `pull_request` trigger fires. +- Repo admin/write access to view the Actions run and the Security → Code scanning alerts tab. + +# Manual Scenarios + +## 1) Workflow triggers and appears as a PR check + +- [ ] Open the PR containing commits `6bf066f8`, `2fbecf07`, `7c6fb04f`. +- [ ] **Expected**: A check named **Semgrep SAST Scan** (job `semgrep-scan` in workflow + `Semgrep - SAST Scan`) appears in the PR's checks list shortly after the PR is opened or + updated. + +## 2) Pinned container image pulls successfully + +- [ ] Open the Actions run for the Semgrep workflow, expand the earliest steps. +- [ ] **Expected**: No container-pull error (e.g. `manifest unknown`, rate-limit, or timeout + pulling `semgrep/semgrep:1.173.0@sha256:...`). The job proceeds past the container-setup + phase into "Checkout repository." + +## 3) Job completes within the timeout; check actual timing + +- [ ] Note the total run duration for the `semgrep-scan` job once it finishes. +- [ ] **Expected**: Job completes well within the current `timeout-minutes: 15` cap. +- [ ] **If the run takes noticeably close to 15 minutes** (cold image pull + rule-registry fetch + was never observed live before this PR — flagged as an open risk by both Supervisor and + DevOps): file a follow-up to bump `timeout-minutes` to ~20-25 in `semgrep.yml`. This is not + a blocker for merging this PR, but should not be left unaddressed if observed. + +## 4) SARIF results appear in the Security tab + +- [ ] Navigate to the repo's **Security → Code scanning alerts** tab. +- [ ] Filter by tool **Semgrep**, category **semgrep**. +- [ ] **Expected**: A scan result is listed for the commit/PR, even if it shows 0 findings (a + SARIF upload with an empty `results` array is still a valid, visible scan entry — this + confirms the upload step itself worked, not just that the repo is clean). + +## 5) Hard-fail gate passes on a clean repo + +- [ ] Check the **Run Semgrep (hard-fail gate)** step's log output. +- [ ] **Expected**: Step exits 0. The repo is expected to be clean — 0 findings was reproduced + locally multiple times (both in DevOps validation and independently in QA's audit) — so + this step should pass without needing any fix commits. + +## 6) Job summary renders correctly + +- [ ] Open the Actions run's **Summary** tab (not the individual job log). +- [ ] **Expected**: A "Semgrep SAST Scan Results" section is present, listing the rulesets + scanned (`p/golang, p/javascript, p/typescript, p/react, p/secrets, p/dockerfile`), the + severity gate (`ERROR, WARNING`), and a clear PASSED/FAILED line matching the job's actual + outcome. + +# Expected Results + +| Scenario | Expected outcome | +|---|---| +| PR trigger | "Semgrep SAST Scan" check appears on the PR | +| Image pull | Pinned `semgrep/semgrep` image pulls with no error | +| Timing | Job finishes comfortably under 15 minutes | +| SARIF upload | Result visible under Security → Code scanning alerts, tool "Semgrep", category `semgrep` | +| Hard-fail gate | Passes (0 findings expected) | +| Job summary | Renders ruleset, severity gate, and pass/fail line in the run summary tab | + +# Pass / Fail Criteria + +**PASS** — All six scenarios behave as expected: the check appears, the image pulls, the job +finishes well under the timeout, SARIF results are visible in the Security tab under the correct +category, the gate step passes, and the job summary renders correctly. + +**FAIL** — Any of: the check never appears on the PR, the image fails to pull, the job times out +or runs suspiciously close to the 15-minute cap, no SARIF entry appears in the Security tab, the +gate step fails unexpectedly on a repo believed to be clean, or the job summary is missing/blank. + +A FAIL on the gate step specifically should be triaged on its merits (a real finding vs. a CI +environment issue) before assuming the feature itself is broken — see +`docs/plans/current_spec.md` §3.7 for documented edge cases. + +# Known Follow-Ups (not blockers) + +1. **Renovate coverage for the pinned image is not yet configured.** `.github/renovate.json` does + not currently have an explicit entry confirmed to track `container: image:` refs in workflow + YAML for the `semgrep/semgrep` image. Confirm whether Renovate's default Docker-image datasource + already picks this up automatically; if not, add an explicit entry so future version/digest + bumps aren't a fully manual chore. +2. **`timeout-minutes` may need adjustment after observing real timing.** Set to 15 based on local + estimates (~45-48s per full-repo scan pass locally); this was never observed against a cold + image pull + rule-registry fetch on an actual GitHub Actions runner. See Scenario 3 above — + bump to ~20-25 if the real run comes in close to the cap. + +# Related + +- `docs/plans/current_spec.md` — full implementation plan for the Semgrep CI Security Scan feature. +- `docs/reports/qa_report.md` — QA/security audit (PASS, no blocking issues). +- Commits `6bf066f8`, `2fbecf07`, `7c6fb04f` on `development`. diff --git a/docs/plans/current_spec.md b/docs/plans/current_spec.md index 3e74a26d9..7aa3ef37f 100644 --- a/docs/plans/current_spec.md +++ b/docs/plans/current_spec.md @@ -1,9 +1,9 @@ -# Issue #619 — Phase 3 Technical Debt: Test Infrastructure Cleanup +# Semgrep CI Security Scan — Implementation Plan -Status: Planning complete, pending supervisor review. -Branch: `test/issue-619-test-infra-debt` (tip of `development`, working tree clean at plan time). -PR base branch: **`development`** (per `gh pr list` convention — `main` only receives weekly `nightly` promotion merges via merge commit; this is a normal feature PR). -Closes: `#619` ("Phase 3 Technical Debt Issues" — bundles 5 sub-issues, verified below). +Status: Planning complete, revised per Supervisor review (round 2). +Owner for implementation: **devops** agent (CI/CD-only change; no backend-dev or frontend-dev involvement — no application code, no models, no UI). +Branch: current working branch (`development`) per `CLAUDE.md` — no worktree. +PR base branch: `development` (standard feature PR convention observed in this repo; `main` only receives weekly `nightly` promotion merges). --- @@ -11,471 +11,495 @@ Closes: `#619` ("Phase 3 Technical Debt Issues" — bundles 5 sub-issues, verifi ### 1.1 Objective -Close out GitHub issue #619 with a single feature PR that: +Add an independent Semgrep SAST scan to GitHub Actions CI that reproduces, byte-for-byte, the same scan behavior developers already run locally via `scripts/pre-commit-hooks/semgrep-scan.sh` (wired through `lefthook.yml`'s `pre-commit`/`pre-push`/`security-full` targets and `make security-local`). Today, Semgrep coverage exists **only** on the developer's machine — CI has zero Semgrep footprint (confirmed: no match in `.github/workflows/`, `.github/renovate.json`, or any Dockerfile/compose file). This means: -1. Un-skips 5 confirmed-stale Vitest suites blocked on a long-fixed `undici`/jsdom WebSocket bug (sub-issue 1), resolves the 6th related skip with a root-cause-appropriate fix (not a blind unskip), and proves no regressions via a full frontend suite run. -2. Replaces 59 tautological (`expect(x || true).toBeTruthy()`-shaped) Playwright assertions across 11 E2E spec files with real, deterministic assertions or explicit `test.skip()` calls with accurate reasons (sub-issue 2) — the bulk of this PR's work. -3. Confirms backend coverage for `internal/services` and the relocated `backend/pkg/dnsprovider/builtin` package remains healthy with no code changes required (sub-issue 3). -4. Confirms the feature-flag async propagation flakiness was already resolved via `waitForFeatureFlagPropagation()` in the reorganized spec file, with no code changes required (sub-issue 4). -5. Confirms WebKit E2E test discovery/config is healthy and schedules the outstanding full WebKit run as a Definition-of-Done gate (sub-issue 5). +- A developer who bypasses lefthook (`--no-verify`, an emergency hotfix, a machine without semgrep installed) ships code with no Semgrep signal at all. +- Nobody re-verifies the "clean" local Semgrep run against a controlled, versioned environment — the local binary's version, ruleset revision, and installed registry rules can silently drift per-developer with no CI backstop. -### 1.2 Why one PR +This plan adds CI-side Semgrep coverage that is authoritative (independent of the developer's local environment) while staying faithful to the existing local invocation. -Per `CLAUDE.md` "Commit Slicing & PR Strategy" and repo memory (`feedback_one_feature_one_pr.md`): issue #619 is one feature (test-infrastructure debt), closed by one PR with ordered commits. Sub-issues 3 and 4 require **no code changes** — they contribute verification evidence to the PR's DoD run and the closing PR description, not separate commits. +### 1.2 Goals + +1. A new CI job runs the **exact same** rule configs, exclusions, and severity/error-gating behavior as `scripts/pre-commit-hooks/semgrep-scan.sh`'s default (no-override) path, scanning the full repo. +2. Semgrep's version is pinned in CI (image tag + digest) — today there is no version pin anywhere in the repo for Semgrep, local or CI. +3. Findings are visible in the GitHub Security tab (SARIF upload), consistent with how CodeQL and Trivy results are surfaced today. +4. A hard-fail gate blocks the PR/branch on ERROR/WARNING findings, mirroring the local script's `--error` behavior — CI is a gate, not just an informational report. +5. `scripts/pre-commit-hooks/semgrep-scan.sh`'s binary/version resolution logic (the `command -v semgrep` check, §2.1) is **not touched** — that stays developer-local tooling, per the original brief's explicit scope boundary. The script's rule-config/exclude/severity logic, by contrast, **is** extended with one small, additive, backward-compatible hook (§2.7/§3.0) so CI can reuse it directly instead of duplicating it — see §2.7 for why this is a different constraint than "freeze the whole file," and why the narrower reading is the right one. +6. Documentation (`SECURITY.md` and `ARCHITECTURE.md`) is updated to reflect the new CI coverage. ### 1.3 Non-goals -- No production code changes (backend or frontend application code). This PR touches only test files, test infrastructure, and documentation. -- No changes to `.gitignore`, `.dockerignore`, `codecov.yml`, or any `Dockerfile` — reviewed explicitly in §3.6, all confirmed already correct for this change (see findings). -- `CrowdSecBouncerKeyDisplay.test.tsx` (4 `it.skip` at lines 205/209/213/219, unrelated clipboard-API mock issue) is explicitly **out of scope** and must not be touched. +- No change to how the local pre-commit/pre-push semgrep **binary** is discovered, installed, or versioned (the `command -v semgrep` / exit-127 block in `scripts/pre-commit-hooks/semgrep-scan.sh` is untouched). +- No new GitHub Action marketplace dependency requiring npm/JS runtime — Semgrep ships as a self-contained CLI in an official container image, which is used directly. +- No change to `.gitignore`, `.dockerignore`, `.codecov.yml`, or any `Dockerfile` (see §2.9 — reviewed explicitly, no changes needed). +- No attempt to unify Trivy's/CodeQL's SARIF-upload plumbing into a shared reusable workflow — out of scope for this feature; each scanner's workflow remains independent, consistent with current repo structure (`codeql.yml`, `security-pr.yml`, `security-weekly-rebuild.yml` are all separate files today). --- -## 2. Research Findings — Ground Truth Verification (2026-08-07) - -All findings below were re-verified directly against the current working tree (branch `test/issue-619-test-infra-debt`, tip of `development`) — greps, file reads, and non-mutating test/coverage runs. Numbers in the original issue text and the prior same-day investigation summary are corrected where they drifted. - -### 2.1 Sub-issue 1 — undici/WebSocket jsdom blocker: CONFIRMED STALE, ACTION REQUIRED - -Dependency state confirmed via `npm ls`: -- `jsdom@30.0.1` (root + deduped under `vitest@4.1.10`) -- `undici@8.10.0` (transitive, via jsdom only) - -The upstream bug this blocker cited (`nodejs/undici#1671`, WebSocket mock `InvalidArgumentError`) is long fixed at this version pair. - -**Confirmed skip inventory** (exact, re-counted against source, not the prior summary): - -| File | Skip marker | Test count (verified via grep) | -|---|---|---| -| `frontend/src/pages/__tests__/Security.test.tsx:35` | `describe.skip('Security', ...)`, comment `// BLOCKER 3: Temporarily skipped due to undici InvalidArgumentError in WebSocket mocks` | 22 | -| `frontend/src/pages/__tests__/Security.audit.test.tsx:52` | `describe.skip('Security Page - QA Security Audit', ...)` | 18 | -| `frontend/src/pages/__tests__/Security.errors.test.tsx:68` | `describe.skip('Security Error Handling Tests', ...)` | 13 | -| `frontend/src/pages/__tests__/Security.loading.test.tsx:59` | `describe.skip('Security Loading Overlay Tests', ...)` | 12 | -| `frontend/src/pages/__tests__/Security.dashboard.test.tsx:67` | `describe.skip('Security Dashboard - Card Status Tests', ...)` | 18 | - -Subtotal: **83 tests** across 5 files. Sum matches exactly. - -**The 6th file — `Security.functional.test.tsx:680`, `it.skip('should open notification settings modal when button is clicked', ...)`, comment `// Skip: Modal component uses WebSocket connections internally`:** - -This comment is **inaccurate**, and unskipping as-is would produce a real (non-WebSocket) failure. Root-cause trace performed per `CLAUDE.md`'s Root Cause Analysis Protocol: - -- `frontend/src/pages/Security.tsx:297-303` — the "Notifications" header button's `onClick` is `() => navigate('/settings/notifications')`. It is a **React Router navigation**, not a modal. There is no `role="dialog"` anywhere in `Security.tsx`. -- `Security.functional.test.tsx:20-27` mocks `useNavigate` (`mockNavigate = vi.hoisted(() => vi.fn())`) — the file's own test harness already expects navigation, not a modal, elsewhere. -- **The correct test already exists in the same file**, passing, uncontested: `Security.functional.test.tsx:452-464`, `it('should navigate to notifications settings when Notifications button is clicked', ...)`, which asserts `expect(mockNavigate).toHaveBeenCalledWith('/settings/notifications')`. - -Conclusion: the skipped test at line ~680 is **dead, stale test code** describing UI behavior (a modal) that was replaced by a navigation at some prior refactor, and the replacement behavior already has full, correct, passing coverage elsewhere in the same file. Per `CLAUDE.md` "CLEAN: Delete dead code immediately," the correct fix is **deletion of the stale `it.skip` block** (the `describe('Notification Settings Modal', ...)` wrapper at line ~677 becomes empty and should be removed with it), not an unskip and not a comment-only edit. This is a stronger, more correct resolution than either option the investigation brief offered, and it should be called out explicitly in the PR description as the resolution for this file. - -**Current full-suite baseline** (`npx vitest run --coverage=false`, non-mutating, run to completion — 635s): +## 2. Research Findings + +### 2.1 Local Semgrep invocation (`scripts/pre-commit-hooks/semgrep-scan.sh`) + +Full script behavior (verified by reading the file): + +- Requires `semgrep` on `PATH`; exits 127 if missing (this resolution logic is untouched by this plan — see §1.3). +- Default rule configs (used unless `SEMGREP_CONFIG` env override is set): + ``` + --config p/golang + --config p/javascript + --config p/typescript + --config p/react + --config p/secrets + --config p/dockerfile + ``` +- Targets: staged files if passed as args (lefthook `pre-commit`), else full-repo default `Dockerfile backend frontend/src scripts .github/workflows` (lefthook `security-full` / manual run). +- Exact scan flags (current, pre-change): + ``` + semgrep scan \ + "${SEMGREP_CONFIGS[@]}" \ + --severity ERROR \ + --severity WARNING \ + --error \ + --exclude "frontend/node_modules" \ + --exclude "frontend/coverage" \ + --exclude "frontend/dist" \ + --exclude-rule "go.secrets.gorm.gorm-empty-password.gorm-empty-password" \ + "${TARGETS[@]}" + ``` +- `--error` makes semgrep exit non-zero if any ERROR/WARNING-severity finding exists — this is the local "hard fail" behavior CI must reproduce. + +Wiring confirmed in `lefthook.yml`: +- `pre-commit.semgrep` (line ~113-116): glob-scoped, staged-files-only, blocking. +- `security-full.semgrep` (line ~137-140, manual stage, `lefthook run security-full`): full-repo, no args → this is the invocation CI should mirror most closely (full-repo, not staged-file-scoped). +- `Makefile:security-local` additionally runs `SEMGREP_CONFIG=p/golang` as a fast pre-push subset — this is a narrower override path, not the target for CI parity (CI should mirror the **full** default ruleset, matching `security-full`). + +### 2.2 Confirmed: zero Semgrep footprint in CI today + +`grep -rn "semgrep" .github/workflows/ .github/renovate.json` (and Dockerfiles/compose) returns no matches. Semgrep is 100% local-only today. (Note: the repo's Renovate config lives at `.github/renovate.json`, not a root-level `renovate.json` — corrected throughout this plan.) + +### 2.3 Existing CI patterns to mirror + +**`.github/workflows/codeql.yml`** (closest pattern for a source-level SAST tool): +- Triggers: `pull_request`/`push` on `[main, nightly, development]`, `workflow_dispatch`, weekly `schedule` cron (`0 3 * * 1`, Mondays 03:00 UTC). +- `concurrency` group keyed on workflow/event/ref, `cancel-in-progress: true`. +- `permissions:` declared at **both** the workflow (top) level and again, identically, at job level (`contents: read`, `security-events: write`, `actions: read`, `pull-requests: read`). +- All third-party actions pinned by commit SHA with a `# vX.Y.Z` trailing comment, e.g. `github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4`. +- Has a **parity guard** step ("Verify CodeQL parity guard" → `scripts/ci/check-codeql-parity.sh`) that runs *before* the scan, structurally checking that local pre-commit scripts, `.vscode/tasks.json`, and the CI workflow all agree on query-suite pinning and trigger branches — added specifically because CodeQL's local/CI ruleset previously drifted silently (see `check-codeql-parity.sh` comment referencing a real incident: a suppressed finding rode through PR #1216 unnoticed because local and CI independently duplicated blocking logic). +- Emits results to `$GITHUB_STEP_SUMMARY`, then a **separate, later step** does the actual hard-fail (`Fail on High-Severity Findings`) — reporting and gating are deliberately split into two steps so the summary always renders even on failure. + +**`.github/workflows/security-pr.yml`** (closest pattern for "pinned scanner → SARIF upload → hard-fail gate"): +- Runs Trivy via `aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25` (SHA-pinned, `# aquasecurity/trivy-action 0.36.0` comment), with an explicit `version: 'v0.73.0'` input additionally pinning the *scanner* version, not just the action wrapper. +- Runs the scan **twice**: once with `format: 'sarif'` (`continue-on-error: true`, purely for the Security tab), then again with `format: 'table'` + `exit-code: '1'` (no continue-on-error) as the actual blocking gate. It also has an explicit "Check Trivy SARIF output exists" gating step between the SARIF-producing run and the upload step. This two-pass "report, then gate" split, plus the existence check, is the direct template for Semgrep's SARIF-vs-hard-fail split (§3.3). +- SARIF uploaded via `github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7` (same SHA-pinned action already used elsewhere in this repo for SARIF ingestion — no new third-party dependency needed for the upload step). +- Trigger shape is materially more complex than needed here (`workflow_run` chaining off `docker-build.yml`, PR-number resolution, artifact download) because Trivy scans a **built container image**. Semgrep scans **source**, so it needs none of that — it can trigger directly on `push`/`pull_request` like CodeQL, with no dependency on a prior Docker build. + +### 2.4 Repo-wide pinning convention + +Every third-party action in this repo is pinned to an exact commit SHA with a trailing `# vX.Y.Z` comment — never a floating tag, never `@latest`. This is enforced by convention/review, not currently by a lint rule for actions specifically. Any new job must follow this exactly. + +### 2.5 Semgrep version/mechanism research + +Options considered: + +| Option | Assessment | +|---|---| +| `pip install semgrep==` on `ubuntu-latest` | Works, but reintroduces a Python toolchain dependency into a Go+TS repo purely for CI plumbing (`CLAUDE.md`: "No Python — do not introduce Python scripts or requirements"). While this is arguably a tooling install rather than an authored script, it still pulls in `pip`/Python resolution behavior (version solving, transitive dependency drift) that the repo's own conventions steer away from. Rejected. | +| `semgrep/semgrep-action` (formerly `returntocorp/semgrep-action`) marketplace GitHub Action | Semgrep's own current CI docs no longer lead with this as the primary GitHub Actions pattern; it's a thin wrapper around the same official Docker image. Using it would add an extra layer of indirection (an Action wrapping an image) for no behavioral benefit over using the image directly, and re-pinning *that* action's SHA doesn't pin Semgrep's own version any more precisely than pinning the image does. Rejected in favor of the image directly. | +| Official `semgrep/semgrep` Docker image, used as a job-level `container:`, pinned by exact tag **and** digest | Matches this repo's SHA-pinning strictness (a digest is the container-image equivalent of an action's commit SHA — both are content-addressed, immutable references). Gives the CLI directly, with the identical `semgrep scan ...` invocation used locally — maximizes behavioral parity with `semgrep-scan.sh`. **Selected.** | + +Confirmed via the Semgrep GitHub releases API (`api.github.com/repos/semgrep/semgrep/releases/latest`) and PyPI, current stable version at plan time is **`1.173.0`**. Resolved the corresponding Docker Hub manifest digest for `semgrep/semgrep:1.173.0`: ``` -Test Files 263 passed | 5 skipped (268) - Tests 3247 passed | 88 skipped | 2 todo (3337) +sha256:67319956da3dcb58baf5b322899c15458e3963e7018a86aeeb5cd224e69cb77a ``` -88 skipped = 83 (sub-issue-1, in scope) + 1 (`Security.functional.test.tsx` notification-modal test, in scope, to be deleted not unskipped) + 4 (`CrowdSecBouncerKeyDisplay.test.tsx`, confirmed out of scope). Arithmetic reconciles exactly — no other undici/WebSocket-flavored skips exist anywhere else in `frontend/src` (verified via repo-wide grep for `undici`, `BLOCKER 3`, `WebSocket connections internally`). - -### 2.2 Sub-issue 2 — Weak/tautological E2E assertions: CONFIRMED, LARGER THAN ORIGINAL ISSUE TEXT, MAJORITY REQUIRE REAL FIXES - -Pattern searched: literal `|| true` immediately preceding `.toBeTruthy()` in `tests/**/*.spec.ts` (the actual pattern in this repo — confirmed not a generic `x` placeholder). Exact current count: **59 occurrences across 11 files**, matching the prior investigation's file list and the prior day's rough counts almost exactly (one file's estimate, `system-settings-feature-toggles.spec.ts`, is 1, not the previously-noted range — reconfirmed by direct grep): - -| File | Count | Lines | -|---|---|---| -| `tests/security-enforcement/zzz-security-ui/access-lists-crud.spec.ts` | 13 | 156, 200, 339, 465, 545, 623, 825, 845, 862, 949, 985, 1018, 1061 | -| `tests/core/certificates.spec.ts` | 9 | 204, 229, 718, 759, 1037, 1052, 1118, 1146, 1158 | -| `tests/security-enforcement/zzz-security-ui/encryption-management.spec.ts` | 8 | 188, 314, 393, 498, 596, 601, 684, 708 | -| `tests/core/proxy-hosts.spec.ts` | 8 | 209, 255, 461, 523, 547, 643, 969, 1014 | -| `tests/security-enforcement/zzz-security-ui/system-security-settings.spec.ts` | 7 | 290, 325, 352, 452, 555, 680, 736 | -| `tests/core/navigation.spec.ts` | 4 | 238, 559, 733, 758 | -| `tests/settings/smtp-settings.spec.ts` | 4 | 121, 165, 231, 906 | -| `tests/core/dashboard.spec.ts` | 3 | 232, 370, 491 | -| `tests/settings/account-settings.spec.ts` | 1 | 875 | -| `tests/security/system-settings-feature-toggles.spec.ts` | 1 | 317 | -| `tests/manual-dns-provider.spec.ts` | 1 | 311 | -| **Total** | **59** | | - -**Decision framework applied to every occurrence** (per task instructions): - -- **(a) Real conditional assertion** — used when the test's own name or an adjacent comment already states a definite, deterministic expectation ("should show X", "X should appear") that the app can be made to satisfy reliably. Mechanical sub-case: when the expression already has a *real* multi-condition OR (e.g. `hasX || hasY || true`), the fix is simply dropping the trailing `|| true` — the meaningful disjunction underneath is preserved. -- **(b) Explicit `test.skip()` / early return with accurate comment** — used only where the underlying condition is genuinely environment- or timing-dependent (cross-browser keyboard focus order, network-dependent external reachability *content* — as opposed to "some feedback appeared," which is still deterministic, race conditions in animation/skeleton timing). This repo already has an established, correct convention for this — `tests/proxy-host-drag-drop.spec.ts` (19 call sites) and `tests/certificate-delete.spec.ts` / `tests/certificate-bulk-delete.spec.ts` (1 each) all use `test.skip(true, '')` mid-test when a precondition isn't met. **Reuse this exact convention** — do not invent a new pattern. -- **(dead code) Delete** — used when a hard `expect(...).toBeVisible()` (or equivalent) already precedes the tautological line for the *same* condition, making the soft check unreachable/redundant. - -Classification results by file (full per-line detail for implementers; "(a)", "(b)", "(dead)" tags below are the required fix per line): - -#### `tests/core/certificates.spec.ts` (9 `|| true` occurrences, plus 1 additional vacuous test with no tautology to grep for) — includes the 3 originally-named tests plus 2 more sharing the same defect - -- **L204** `hasSortIcon || true` — comment above states "Sort icon should appear" as a definite requirement → **(a)**: `expect(hasSortIcon).toBe(true)`. -- **L229** `hasAlert || true`, test `'should show SSL info alert'` → **(a)**: test name itself is the requirement. -- **L718** `hasDelete || true`, test `'should show delete button for staging certificates'` → **(a)**. -- **L759** `hasToast || true`, test `'should warn if certificate is in use by proxy host'` → **(a), root-cause fix required, see below.** -- **L1037** `hasSslColumn || true` — comment: "SSL column *may* show certificate info" → checked against the source (`frontend/src/pages/ProxyHosts.tsx:556-557`): the SSL column (`key: 'ssl', header: t('proxyHosts.columnSSL')`) is a **static column definition**, not conditional per-row/per-feature-flag — unlike proxy-hosts.spec.ts L643's `hasWs`/`hasAcl` (which genuinely vary per host's configuration and need a seeded host to be deterministic), this column header renders unconditionally whenever the table itself renders. → **(a), and simpler than L643**: no seeding needed — the `hasTable` check already above this line guarantees the table is rendered, so `expect(hasSslColumn).toBe(true)` is deterministic as-is; verify at implementation time that no feature flag gates the column before finalizing. -- **L1052** `hasHeading || true` — a **hard** `await expect(heading).toBeVisible({ timeout: 10000 })` already executes immediately above this line for the identical locator → **(dead)**: delete the redundant soft-check (3 lines). -- **L1118** `hasError || true`, test `'should show error message on API failure'` — **root-cause issue**: the test never injects a failure (no `page.route(...)` interception forcing a 4xx/5xx). It cannot show an error message because no error is ever induced. → **(a) with expanded scope**: add a `page.route('**/api/v1/certificates', route => route.fulfill({ status: 500, ... }))` (or equivalent, matching the mocking convention used elsewhere in this same file's "Error Handling" section if one exists — verify at implementation time) before navigation, then assert the error message is real and visible. This is not a one-line fix; note it in the commit as a slightly larger item. -- **L1146** `hasDescription || true`, test `'should have PageShell with title and description'` → **(a)**. -- **L1158** `hasIcon || true` — comment: "Button should have Plus icon" → **(a)**. - -**The 3 named tests, plus 2 more sharing the identical defect, in detail** (backend root-cause traced via `backend/internal/api/handlers/certificate_handler.go:387-470`, `CertificateHandler.Delete`): - -Note: a 5th test in the same `describe` block, `'should show config reload overlay during deletion'` (~L806-821), was not caught by the initial `|| true` grep sweep because it doesn't end in a tautology — it uses the identical broken `page.once('dialog', dialog => dialog.accept())` pattern against the same non-existent native dialog, then only does `await waitForDebounce(page)` with **no assertion at all** afterward. It is just as vacuous as the other four and requires the identical interaction-model fix, so it is grouped with them below (item 5) and included in the same commit. +Pinned reference to use in the workflow: -Critical finding: **the delete UI does not use a native `window.confirm()` dialog.** `frontend/src/components/dialogs/DeleteCertificateDialog.tsx` is a fully custom React modal (uses the shared `Dialog`/`DialogContent`/`DialogFooter` primitives, `Button` components with `onClick={onCancel}` / `onClick={onConfirm}`, translated via i18n keys `certificates.deleteTitle`/`deleteConfirmCustom`/`deleteButton`/`common.cancel`). It contains **no `confirm()` call and no "backup" text is guaranteed** — the backup-mentioning copy (`certificates.deleteConfirmCustom`: *"This will permanently delete this certificate. A backup will be created first."*, `frontend/src/locales/en/translation.json:234`) is used **only** when `getWarningKey()` falls through to the default case (i.e. the certificate is not `expired`, not `expiring`, and not `letsencrypt-staging`); the other 3 status-specific messages (`deleteConfirmStaging`, `deleteConfirmExpired`, `deleteConfirmExpiring`) never mention backups at all. - -All five existing tests (`'should show delete confirmation dialog'` L723, `'should warn if certificate is in use by proxy host'` L741, `'should cancel delete when confirmation dismissed'` L764, `'should create backup before deletion'` L788, `'should show config reload overlay during deletion'` L806) currently drive the flow via `page.once('dialog', ...)` — Playwright's **native browser dialog** handler. Since the app never opens a native dialog for this flow, **that handler callback never fires**; the tests currently click the delete button (opening the *custom* modal, which is left dangling/unclosed) and then either do nothing further or check a `hasX || true` that trivially passes. These tests currently exercise almost none of the real deletion flow. This is a larger, root-cause-level fix, not a one-line assertion swap: - -1. **`'should show delete confirmation dialog'` (L723, not currently `|| true` but must be fixed alongside the others for the block to work at all)**: replace `page.once('dialog', ...)` with locating the actual custom modal (`page.getByRole('dialog')` from the shared `Dialog` primitive — verify exact role/testid in `frontend/src/components/ui/Dialog.tsx` at implementation time) and asserting its title (`t('certificates.deleteTitle')` → "Delete Certificate") and Cancel/Delete buttons are visible. -2. **`'should warn if certificate is in use by proxy host'` (L741/L759)**: backend `Delete()` returns `409 {"error": "certificate is in use by one or more proxy hosts"}` **before** any backup is attempted, when `IsCertificateInUse`/`IsCertificateInUseByUUID` is true. Real fix: select (or seed via API, matching this file's existing seeding convention) a certificate that is actually attached to a proxy host, click delete, click the custom modal's Confirm button, and assert a real error toast/message appears (matching the app's toast convention — `sonner`/`[role="alert"]`, consistent with other files in this PR) rather than the current always-true check. Do not rely on "whichever cert happens to be first in the table." -3. **`'should cancel delete when confirmation dismissed'` (L764)**: replace `page.once('dialog', dialog => dialog.dismiss())` with clicking the custom modal's **Cancel** button. The existing row-count check (`rowsBefore === rowsAfter`) is real and should be **kept**, but per the task's explicit instruction, **add a backend-state assertion**: `GET /api/v1/certificates/{id}` (via `getCertificateViaAPI` from `tests/utils/api-helpers.ts`, the file's already-established API-verification helper — reuse it, do not invent a new one) returns `200` and the certificate is still present, proving cancellation didn't merely hide a row client-side. -4. **`'should create backup before deletion'` (L788, currently checks `dialog.message()` contains "backup" via a handler that never fires — the current implementation is not even a tautology, it is dead/vacuous)**: backend confirms `CreateBackup()` (via `BackupServiceInterface`) is called synchronously in the `Delete` handler, for a certificate that is **not** in use, before the delete completes. Correct fix: capture the backup list via `GET /api/v1/backups` (same endpoint mocked/used in `tests/tasks/backups-create.spec.ts`; no existing typed helper for it in `tests/utils/api-helpers.ts` — add one, `getBackupsViaAPI`, following the exact pattern of the file's other `get*ViaAPI` functions) **before** the delete, click Confirm on the custom modal for a certificate guaranteed not in use, wait for the delete to complete, then `GET /api/v1/backups` again and assert a new backup entry exists (by count increase and/or a `created_at`/filename close to "now"). Do **not** assert on dialog text — the text does not reliably mention "backup" depending on certificate status, as shown above. -5. **`'should show config reload overlay during deletion'` (~L806-821, currently `page.once('dialog', dialog => dialog.accept())` then only `await waitForDebounce(page)` — no assertion at all, silently vacuous)**: replace with clicking the custom modal's **Confirm/Delete** button for a certificate guaranteed not in use, then assert the actual loading/config-reload overlay is real: locate it the same way `tests/security/system-settings-feature-toggles.spec.ts:317`'s `overlayVisible` check does (`.fixed.inset-0.z-50` / `[data-testid="config-reload-overlay"]` — reuse that locator convention rather than inventing a new one) and assert it becomes visible during the delete request and then resolves/disappears once the request completes, rather than the current no-op. - -#### `tests/core/proxy-hosts.spec.ts` (8 occurrences) - -- **L209** `hasBulkBar || true` — comment: "Should show bulk action bar" → **(a)**. -- **L255** `isInvalid || true` — comment: "Browser validation or custom validation should prevent submission" → **(a)**; consider also asserting no `POST /api/v1/proxy-hosts` was sent (stronger, matches sub-issue 2's "verify backend state" spirit) if feasible without large rework. -- **L461** `hostCreated || true` — this is the **creation-verification step of a core CRUD test**. → **(a), strengthen**: assert UI text visible **and** verify via `getProxyHostsViaAPI`/`getProxyHostViaAPI` (already in `tests/utils/api-helpers.ts`) that the host exists server-side with the expected `domain`/`forward_host`/`forward_port` — mirrors the existing convention in this same helper file. -- **L523** `exists || true` (loop over expected security-option checkboxes: force SSL, HTTP/2, HSTS, block exploits, websocket) → **(a)**: these are static, always-rendered form fields; assert each `expect(exists).toBe(true)`. -- **L547** `exists || true` (loop over preset dropdown options: plex, jellyfin, homeassistant, nextcloud) → **(a)**, pending a quick implementation-time check that these presets are indeed static/guaranteed (grep the preset source, e.g. `frontend/src/**/presets*`) rather than feature-flagged. -- **L643** `hasWs || hasAcl || true`, test `'should show feature badges (WebSocket, ACL)'` — comment: "May or may not exist depending on host configuration" → **(a) via test-setup fix**: rather than leaving this permanently unverifiable, seed/select a host in the test's own setup with `websocket_support: true` (via `createProxyHostViaAPI`) so `hasWs` is deterministic; drop `|| true`. -- **L969** `hasApply || hasRemove || true` — comment: "Should have apply/remove tabs or buttons" (definite) → **(a)**: drop `|| true`. -- **L1014** `hasFocus || true` (keyboard nav: 3 Tabs inside an open modal, expect something focused) → **(a)** preferred (modals should trap/receive focus deterministically); fall back to **(b)** only if empirically flaky per-browser during implementation (cross-reference with sub-issue 5's WebKit focus-order risk). - -#### `tests/security-enforcement/zzz-security-ui/access-lists-crud.spec.ts` (13 occurrences) - -- **L156** `hasBadge || true` (allow/deny type badge in first seeded row) → **(a)**, contingent on the `beforeEach` seed fixture guaranteeing a row with a known type — verify at implementation time. -- **L200** `isInvalid || true` — comment: "HTML5 validation should prevent submission" (definite) → **(a)**. -- **L339** `hasInfo || true` — "Blacklist should show 'Recommended' info box" (definite, immediately after `selectOption('blacklist')`) → **(a)**. -- **L465** `hasPresets || true` (preset options after clicking "Show presets") → **(a)**. -- **L545** `hasUpdated || true` (rename ACL, verify) → **(a)**, strengthen with `getAccessListViaAPI` name check (same convention as certificates §2.2 backup verification). -- **L623** `hasSuccess || true` (save success toast) → **(a)**. -- **L825** `hasBulkBar || true` → **(a)** (same pattern as proxy-hosts L209). -- **L845** `hasBulkDelete || true`, test `'should show bulk delete button when items selected'` → **(a)**. -- **L862** `hasHeading || true`, test `'should navigate between Access Lists and Proxy Hosts'` — **no** preceding hard assert here (unlike the near-identical certificates.spec.ts:1052 case, which is dead code) → **(a)**: promote to a hard `await expect(heading).toBeVisible({ timeout: 5000 })`. -- **L949** `hasWarning || true`, test `'should show CGNAT warning when ACLs exist'` → **(a)**, contingent on seeded ACL data guaranteeing the CGNAT condition — verify seed fixture at implementation time. -- **L985** `hasExternalIcon || true` (external-link icon on "best practices" link) → **(a)**. -- **L1018** `hasFocus || true` (same keyboard-tab pattern as proxy-hosts L1014) → **(a)** preferred, **(b)** fallback if flaky. -- **L1061** `isHidden || true` (IP input hidden when "local network only" toggle enabled) → **(a)**: deterministic conditional-field-visibility behavior. - -#### `tests/security-enforcement/zzz-security-ui/encryption-management.spec.ts` (8 occurrences) - -- **L188** `hasWarning || true` (rotate-key confirm dialog warning content) → **(a)**: dialog title/confirm/cancel are already hard-asserted immediately above; the warning text check should be promoted to match. -- **L314** `hasProgress || true` — comment: "Progress may appear briefly - capture if visible" → **(b)**: genuinely a timing race (progress indicator can legitimately complete before the 5s poll ever samples it); use `test.skip()`-with-reason or remove if no stronger signal (e.g., a `waitForResponse` on the rotation request) can be substituted. -- **L393** `hasWarning || true` — inside `if (isDisabled)` guard, checking rotation-disabled warning text → **(a)**: once inside the guard the condition is deterministic (button is confirmed disabled). -- **L498** `hasWarning || true` — comment: "Warnings may or may not be present - just verify we can detect them" → **(b)**: explicitly optional per comment; convert to non-blocking annotation or remove — it currently asserts nothing meaningful either way. -- **L596** `hasBadge || true` (action-type badge in first audit-log row) → **(a)**, contingent on seeded rotation-history data. -- **L601** `hasVersionInfo || true` (version/duration info in same row) → **(a)**, same seeding caveat as L596. -- **L684** `hasToast || true` (keyboard-activated validate button should trigger a result toast) → **(a)**: deterministic feedback requirement. -- **L708** `accessibleName || true` (every visible button should have an accessible name) → **(a)**: real, valuable a11y assertion; drop `|| true`. +``` +semgrep/semgrep:1.173.0@sha256:67319956da3dcb58baf5b322899c15458e3963e7018a86aeeb5cd224e69cb77a +``` -#### `tests/security-enforcement/zzz-security-ui/system-security-settings.spec.ts` (7 occurrences) +**Note for the implementer (devops agent):** re-resolve this digest at implementation time (`docker buildx imagetools inspect semgrep/semgrep:1.173.0` or the registry API) rather than trusting the value transcribed into this plan verbatim, in case the tag's digest has moved between planning and implementation (Docker Hub does not guarantee a tag's digest is immutable the way a Git SHA is — pinning to the *tag+digest pair as observed at merge time* is the achievable guarantee here, and Renovate, already active in this repo via `.github/renovate.json`, will pick up future digest/tag bumps the same way it tracks other pinned SHAs if configured to watch this image — see §3.7 edge case). -- **L290** `hasValidation || true` — comment: "May not have inline validation" (explicit hedge) → **(b)**: convert to `test.skip()`/annotation. -- **L325** `toastVisible || true` — the immediately preceding step already hard-asserts `expect(saveResponse.ok()).toBeTruthy()` (a real successful save) → **(a)**: success feedback must follow a confirmed-successful save; drop `|| true`. -- **L352** `hasSuccess || true` (green-checkmark validation indicator for a valid URL) → **(a)**, pending a quick check that this indicator is unconditionally rendered for the field (verify at implementation time). -- **L452** `toastVisible || true` — comment: "URL reachability depends on network - just verify test button works" → **(a) for the meta-assertion**: regardless of network outcome (reachable/unreachable), *some* toast must always appear after clicking Test — that part is deterministic. Drop `|| true`; do not assert on toast *content*. -- **L555** `hasVersion || true` (version string format) → **(a)**: app always renders a build version (semver or `dev`). -- **L680** `newState !== initialState || true` — this directly overlaps sub-issue 4's feature-flag propagation fix; the test already awaits both the `PUT` and `GET` feature-flags responses via `Promise.all` before reading `newState` → **(a)**: the toggle state change is deterministic once both responses have resolved; drop `|| true`. -- **L736** `accessibleName || true` — same pattern as encryption-management L708 → **(a)**. +**Known gotcha (Semgrep's own docs, `semgrep.dev/docs/kb/semgrep-ci/using-nonroot-docker-image-with-gha`):** running `semgrep/semgrep` as a job-level `container:` against a `actions/checkout`-produced workspace can hit git's "dubious ownership" safety check because the container user doesn't match the checkout's file ownership. Mitigate with an explicit `git config --global --add safe.directory "$GITHUB_WORKSPACE"` step before invoking `semgrep-scan.sh` (§3.3). -#### `tests/core/navigation.spec.ts` (4 occurrences) +**Correction (Supervisor round 2, required change 1):** the container image reference **cannot** be centralized in a workflow-level `env:` var and referenced as `container.image: ${{ env.SEMGREP_IMAGE }}`. GitHub Actions' documented context-availability rules do not expose the `env` context to `jobs..container` — this is a known, currently-true limitation (not something that needs "verification at implementation time"; treating it as an open question in the prior draft was itself the error). The plan now specifies the pinned string **inlined directly** in `container.image` as the only correct form (§3.2) — no `env` indirection. -- **L238** `hasActiveCurrent || hasActiveClass || true` → **(a)**, mechanical: drop `|| true`, keep the two-way OR (an active nav item must signal state via `aria-current` or an active class — at least one is a real requirement). -- **L559** `foundNavLink || true` — comment: "May not find nav link depending on focus order - this is acceptable" → **(b)**: convert to `test.skip(true, 'no focusable nav link found via keyboard tab order in this run')` per the established `test.skip(true, reason)` convention, rather than a fake pass. -- **L733** `hasAriaCurrent || true` — comment: "aria-current is recommended but not always implemented" → **(a), converge with L238's convention**: check `aria-current` **or** active class (matching the pattern already used at L238 in the same file) instead of `aria-current` alone with a fake fallback — this makes it a real, DRY assertion instead of leaving it permanently soft. -- **L758** `outline || true` (focus-visible indicator style) → **(a)** preferred: assert `outline` is non-empty/not `'none'`; fall back to **(b)** only if empirically flaky across Chromium/Firefox/WebKit during implementation. +### 2.6 Placement decision: new file vs. an existing workflow -#### `tests/core/dashboard.spec.ts` (3 occurrences) +*(Per mid-task correction from Management: decide the best location and justify it, rather than defaulting to a new file. Approved as-is by Supervisor round 2 — no changes in this revision.)* -- **L232** `foundButton || true` (quick-action button reachable via keyboard tab loop) → **(b)**: tab-order flakiness, same pattern as navigation.spec.ts:559; convert to `test.skip()`-with-reason when not found within the loop bound, rather than fake pass. The real assertion (`expect(focused).toBeFocused()`) already fires correctly when found. -- **L370** `hasEmptyState || hasActualContent || true` — comment: "Dashboard should show either empty state or content, not crash" (a genuine, environment-independent invariant) → **(a)**, mechanical: drop `|| true`. -- **L491** `reachedCard || focusableElementsFound > 0 || true` — comment: "verify we at least found some focusable elements" → **(a)**, mechanical: drop `|| true`, keep the two-way OR. +Three placements were evaluated: -#### `tests/settings/smtp-settings.spec.ts` (4 occurrences) +| Placement | Verdict | +|---|---| +| **New job added to `codeql.yml`** | Rejected. `codeql.yml`'s entire structure is a `strategy.matrix` over CodeQL *languages* (`go`, `javascript-typescript`), with per-language conditional steps (`if: matrix.language == 'go'`) for Go toolchain setup/build and the CodeQL parity guard. Semgrep is not a CodeQL language variant — it's a different tool with a different container, different config format, and a different (single, non-matrixed) invocation. Bolting it in as a third matrix leg would force awkward `if: matrix.language == 'semgrep'` conditionals across steps that don't apply to it (Autobuild, `codeql-action/init`, Go build verification), degrading the readability of a file whose entire premise is "one job, matrixed by CodeQL language." Also couples Semgrep's schedule/trigger lifecycle to CodeQL's, when they are independent tools that should be able to fail, be disabled, or be re-scheduled independently. | +| **New job added to `security-pr.yml`** | Rejected. That workflow's trigger shape and majority of its steps exist *solely* to solve "how do I scan a Docker image that was already built by a separate upstream workflow" — PR-number resolution from `workflow_run` payloads, artifact download/load fallback logic, container extraction of the `charon` binary, a trust-boundary validation step for the `workflow_run` event. None of that applies to Semgrep, which scans source text directly on `push`/`pull_request` with no dependency on `docker-build.yml` having run first. Adding a source-scanning job to an image-scanning workflow would mean either (a) it inherits triggers/conditions built for image scanning that don't fit it (e.g. `workflow_dispatch` inputs are `pr_number`-shaped, meaningless for a source scan), or (b) it needs its own parallel `if:` conditions bolted onto an already condition-heavy file, adding complexity for no shared benefit — the two jobs would share a file but no actual logic. | +| **New file: `.github/workflows/semgrep.yml`** | **Selected.** Semgrep is source-level SAST, triggered directly on `push`/`pull_request`/`schedule`/`workflow_dispatch` — structurally identical in trigger shape to `codeql.yml`, but a distinct tool with its own container, config, and failure/gating semantics. This also matches the repo's existing convention of **one file per scanner**: `codeql.yml` (CodeQL), `security-pr.yml` (Trivy on PR images), `security-weekly-rebuild.yml` (Trivy weekly full scan) are already separate files rather than merged into one "security" workflow, even though they're conceptually related. A dedicated `semgrep.yml` continues that pattern: each scanner is independently triggerable, independently disable-able, and independently readable, at the cost of one more file — a cost the repo has already accepted three times over for its other scanners. | -- **L121** `skeletonVisible || true` — comment: "Either skeleton is shown or page loads very fast" → **(b)**: genuine loading-timing race; convert to `test.skip()`-with-reason or remove — a 500ms artificial delay plus a 1000ms visibility timeout should make the skeleton reliably visible, so first try tightening the mock/timeout to make this **(a)** before falling back to **(b)**. -- **L165** `hasValidation || true` — comment: "Either inline validation or form submission is blocked" (definite requirement, required-field case) → **(a)**. -- **L231** `hasValidation || true` — comment: "Validation should occur (inline or via toast)" (definite, invalid-email-format case) → **(a)**. -- **L906** `hasAccessibleError || true` — comment: "Some form of accessible error feedback should exist" (definite a11y requirement) → **(a)**. +### 2.7 "Freeze the whole script" reconsidered — design revision (Supervisor round 2, required change 3) -#### `tests/settings/account-settings.spec.ts` (1 occurrence) +**The original brief's non-goal, re-read precisely:** *"Do NOT touch `scripts/pre-commit-hooks/semgrep-scan.sh`'s binary/version resolution logic itself — that's explicitly out of scope, reserved for the user's own local tooling."* This is a constraint about **binary/version discovery** (the `command -v semgrep` / exit-127 block, §2.1) — not a blanket freeze on every line of the file. The first draft of this plan over-read it into "never touch this file at all," which forced: -- **L875** `foundApiButton || true` — comment: "Non-blocking assertion" (explicit hedge, keyboard tab-order search for API key buttons) → **(b)**: convert to `test.skip()`-with-reason, consistent with the tab-order-flakiness cases above. +- A second, hand-written `semgrep scan ...` invocation inline in the workflow YAML, duplicating all six `--config` flags, all three `--exclude` flags, and the `--exclude-rule` value. +- A `check-semgrep-parity.sh` script whose primary job was detecting drift between that duplicated invocation and the real script. +- Pressure to extract shared assertion helpers out of `check-codeql-parity.sh` mainly to support that parity script's config-matching checks. -#### `tests/security/system-settings-feature-toggles.spec.ts` (1 occurrence) +That is real, avoidable complexity, not an inherent requirement. **Revised design (adopted — option (a) from Supervisor's feedback):** add one small, additive, backward-compatible hook to `semgrep-scan.sh` itself, leaving the binary/version-resolution logic (the actual thing the non-goal protects) completely untouched: -- **L317** `overlayVisible || true` — comment: "Overlay may appear briefly - either is acceptable" → **(b)**: genuine timing race (config-reload overlay can complete before the 1s poll samples it); the `responsePromise` for the `PUT /feature-flags` call is already captured above but never awaited/used to gate this check — first try awaiting that promise before sampling the overlay (would make this **(a)**); fall back to **(b)** if still flaky. +```bash +# Existing lines (SEMGREP_CONFIGS / TARGETS construction) unchanged above this point. -#### `tests/manual-dns-provider.spec.ts` (1 occurrence) +if [ -n "${SEMGREP_SARIF_OUTPUT:-}" ]; then + OUTPUT_FLAGS=(--sarif --output "${SEMGREP_SARIF_OUTPUT}") +else + OUTPUT_FLAGS=(--error) +fi -- **L311** `hasVisibleIcon || true` (status icon inside an already-hard-asserted status indicator) → **(a)**: the indicator itself is already hard-asserted visible immediately above; the icon inside it should be deterministic too. +semgrep scan \ + "${SEMGREP_CONFIGS[@]}" \ + --severity ERROR \ + --severity WARNING \ + "${OUTPUT_FLAGS[@]}" \ + --exclude "frontend/node_modules" \ + --exclude "frontend/coverage" \ + --exclude "frontend/dist" \ + --exclude-rule "go.secrets.gorm.gorm-empty-password.gorm-empty-password" \ + "${TARGETS[@]}" +``` -**Summary**: of 59 `|| true` occurrences, **~47 become real assertions (a)**, **~4 are dead code to delete**, and **~8 are genuinely environment/timing-dependent and become explicit `test.skip()` calls (b)** using the repo's existing convention — plus 1 additional vacuous test (certificates.spec.ts's `'should show config reload overlay during deletion'`) that has no `|| true` to count here but requires the identical interaction-model fix (see the certificates.spec.ts breakdown above). Exact per-line final disposition is confirmed during implementation per the guidance above; the DoD gate in §5 enforces that zero bare `|| true`-before-`toBeTruthy()` patterns remain regardless of which bucket each line lands in. +Behavior: +- **`SEMGREP_SARIF_OUTPUT` unset (every existing call site — `pre-commit`, `pre-push`/`security-full`, `make security-local`):** `OUTPUT_FLAGS=(--error)` — byte-identical to today's behavior. Zero change for any existing developer workflow. +- **`SEMGREP_SARIF_OUTPUT=` set (new — CI only):** swaps `--error` for `--sarif --output `, while every `--config`, `--exclude`, and `--exclude-rule` argument stays exactly as-is, sourced from exactly one place. -### 2.3 Sub-issue 3 — Backend coverage gaps: CONFIRMED STALE / ALREADY RESOLVED, NO CODE CHANGES +This lets CI invoke the **same script** for both the SARIF-producing pass and the hard-fail gate pass (§3.3 steps 5 and 8), varying only an env var. Consequences: -Re-ran directly (non-mutating `go test -cover`): +- The duplicated `--config`/`--exclude` list in the workflow YAML is **eliminated entirely** — there is now exactly one place (`semgrep-scan.sh`) that defines what gets scanned, for both local and CI, for both the reporting pass and the gating pass. +- `check-semgrep-parity.sh` shrinks correspondingly (§3.4) — it no longer needs to compare two independent config lists (nothing to compare; there's only one). It still has a real, narrower job: confirming the additive hook isn't silently removed, confirming the workflow actually delegates to the script for both passes (rather than a future edit reintroducing an inline duplicate), and confirming the image pin and trigger branches stay correct. This is a smaller, more clearly justified guard than the original draft's. +- The pressure to extract `scripts/ci/lib/workflow-yaml-asserts.sh` out of `check-codeql-parity.sh` is now a plain, optional DRY nicety (the branch-check helper is still needed by both scripts) rather than something load-bearing for the config-parity story — see §3.5. -``` -ok internal/services coverage: 88.4% of statements (target 85%) -ok internal/services/remotestorage coverage: 90.3% of statements -ok backend/pkg/dnsprovider/builtin coverage: 91.8% of statements (target 50% incremental) -``` +**Why not stop here and also drop the parity guard entirely?** Because two failure-independent invariants remain worth checking even with zero config duplication: (1) that the additive `SEMGREP_SARIF_OUTPUT` hook stays present in the script (a future refactor of `semgrep-scan.sh` could drop it without realizing CI depends on it), and (2) that the workflow keeps *delegating* to the script for both passes rather than a future edit reintroducing an inline `semgrep scan` call (e.g. someone "simplifying" the SARIF step by hand and accidentally dropping an `--exclude`). Both are cheap, structural, grep-level checks — proportionate, not over-engineering, and much smaller than the original draft's guard (§3.4). -Confirms the prior investigation exactly. `backend/pkg/dnsprovider/builtin` is the correct current location (relocated from `internal/dnsprovider/builtin` as the original issue text said) and is excluded from `codecov.yml` reporting (`ignore:` list, line 136 — "tested via integration tests, not unit tests") but not from `go-test-coverage.sh`'s enforcement; either way, actual coverage is far above both the codecov project target (87%) and the issue's original incremental target (50%). **No regression, no code changes required.** This PR's only obligation here is to capture a coverage run as DoD evidence (§5) and state this explicitly in the PR description (§6). +This section supersedes the original §2.7 ("Parity guard: warranted, and why") from the first draft. -### 2.4 Sub-issue 4 — Feature flag async propagation tests: CONFIRMED STALE / ALREADY RESOLVED, NO CODE CHANGES +### 2.8 Documentation review -`tests/settings/system-settings.spec.ts` no longer exists (confirmed via `find`); the feature-flag tests were reorganized into `tests/security/system-settings-feature-toggles.spec.ts`, which: -- Imports and calls `waitForFeatureFlagPropagation` **9 times** (exact count via `grep -c`, correcting the prior investigation's "11" estimate) across all 9 tests in the file. -- Has **zero** `.skip`/`.fixme` markers (aside from the one tautological assertion at L317, covered under sub-issue 2 above — a different problem, not the async-propagation flakiness this sub-issue was about). +- **`SECURITY.md`** (`## Security Audits & Scanning` → `### Automated Scanning` table, lines 992-1020): lists Trivy, CodeQL, govulncheck, golangci-lint (gosec), npm audit, and a `### Scanning Workflows` subsection describing each workflow file's purpose (`docker-build.yml`, `supply-chain-verify.yml`, `security-weekly-rebuild.yml`, PR-specific scanning). **This is the primary file to update** — add a `Semgrep` row to the table and a new `**Semgrep SAST Scan**` paragraph under `### Scanning Workflows` describing `.github/workflows/semgrep.yml`. +- **`docs/security.md`**: verified by full-text search (`codeql|trivy|scan|pipeline`, no matches) — this file is entirely about the Cerberus runtime security feature (CrowdSec/WAF/access lists), unrelated to the CI/SAST scanning pipeline. **No change needed here.** +- **`ARCHITECTURE.md` (Supervisor round 2, required change 2 — added to scope):** `CLAUDE.md` requires `ARCHITECTURE.md` updates for changes touching security architecture, and this file already documents the CI security-scanning stack in three places that must be kept current: + - Line 166, tech-stack table: `| **Security Scanning** | Trivy + Grype | Latest | Vulnerability detection |` — append Semgrep, e.g. `Trivy + Grype + Semgrep`. + - Line 1376, CI Jobs list: `3. **Security:** Trivy, CodeQL, Grype, Govulncheck` — append `, Semgrep`. + - Lines 1498-1501, "Container Scanning" components list (`Trivy: ...`, `Grype: ...`, `CodeQL: ...`) — add a fourth line, `Semgrep: Static analysis for security anti-patterns (Go, JS/TS, React, secrets, Dockerfile)`, consistent with the existing one-line-per-tool style. + - This is now part of Commit 3's scope (§6) and Acceptance Criteria (§5), alongside `SECURITY.md`. -**No regression, no code changes required.** This file is already fully in-scope for the mandatory full E2E run in §5 (it was already going to run; no special inclusion action needed). +### 2.9 Ignore-file / build-file review (explicit confirmation per `CLAUDE.md`) -### 2.5 Sub-issue 5 — WebKit E2E tests not executing: CONFIG CONFIRMED HEALTHY, ONE REAL RUN STILL OUTSTANDING +- **`.gitignore`**: already contains a blanket `*.sarif` ignore (line 189) with a narrow `!scripts/security/testdata/*.sarif` carve-out (line 190). A new `semgrep-results.sarif` file in the repo root matches the existing wildcard — **no change needed**. +- **`.dockerignore`**: already excludes `*.sarif` (line 179) — irrelevant anyway, since this is a CI-only workflow change with no Docker image content change — **no change needed**. +- **`.codecov.yml`**: workflow-only YAML change, produces no coverage-relevant files — **no change needed**. +- **Any `Dockerfile`**: not touched; Semgrep runs in its own CI container, never inside the Charon application image — **no change needed**. -- WebKit `26.5` installed; `npx playwright test --list --project=webkit` discovers **963 tests across 86 files** (re-verified, matches prior investigation exactly). -- `playwright.config.js` (repo root — the config actually governing `tests/`, distinct from the unrelated minimal `frontend/e2e/playwright.config.ts`) reviewed line-by-line: the `webkit` project (L299-314) has **identical** `dependencies`, `testMatch`, and `testIgnore` patterns to `chromium`/`firefox` — no webkit-specific exclusion, no `browserName`-conditioned `test.skip()` anywhere in `tests/**` (repo-wide grep confirmed zero matches). -- Note (informational, not a defect): the `webkit` project's `testIgnore` excludes `**/security-enforcement/**` and `**/tests/security/**`, same as chromium/firefox — those specs only run under the dedicated `security-tests` project, which is **Chromium-only by design** (L237-254, "SEQUENTIAL, Chromium only"). This means 29 of this PR's 59 sub-issue-2 fixes (all of `access-lists-crud.spec.ts`, `encryption-management.spec.ts`, `system-security-settings.spec.ts`, `system-settings-feature-toggles.spec.ts`) are **out of WebKit's run scope entirely, by existing design** — not something this PR changes or needs to change. -- A dedicated `tests/core/caddy-import/caddy-import-webkit.spec.ts` (`@webkit-only` tag) already exists for known WebKit-specific quirks in the Caddyfile-import flow, and `caddy-import-cross-browser.spec.ts` already parameterizes assertions per `browserName` — evidence the team has previously handled real WebKit differences correctly elsewhere; no similar per-browser branching is missing here. -- **Risk flagged**: none identified in config. The keyboard-focus-order tautologies converted to real assertions in §2.2 (proxy-hosts.spec.ts:1014, access-lists-crud.spec.ts:1018, navigation.spec.ts:758) are the most plausible source of **new** WebKit-specific flakiness once they stop being unconditionally true — this is exactly why §2.2 marks them "(a) preferred, (b) fallback if empirically flaky" rather than a hard mandate, and why the full WebKit run (§5) must happen **after** the sub-issue-2 commits land, not before. -- **Not run in this planning pass** (explicitly deferred to execution/QA phase per task instructions): the actual full `npx playwright test --project=webkit` execution. This is a mandatory, explicit Definition-of-Done gate (§5) for this PR. +(Approved as-is by Supervisor round 2 — no changes in this revision.) -### 2.6 `.gitignore` / `.dockerignore` / `codecov.yml` / `Dockerfile` review +### 2.10 Commit scope: `feat:` vs `feat(security):` -All reviewed; **no changes required** for this PR: +Per `CLAUDE.md`, `feat:`/`fix:`/`perf:` trigger Docker builds; `chore:` skips them, and `feat(security):`/`fix(security):` is reserved for "genuinely security-relevant... real vulnerability fixes, new protective mechanisms." **Decision:** the workflow-adding commit (Commit 2, §6) qualifies as a **new protective mechanism** — it is, definitionally, new automated vulnerability/anti-pattern detection gating merges — so it uses `feat(security):`, not plain `feat:`. Commit 1 (the additive `semgrep-scan.sh` hook + parity guard) also touches genuine security tooling directly and is scoped `feat(security):` for the same reason. Commit 3 (docs) stays `docs:`, matching repo convention for documentation-only changes regardless of what they document. Per `CLAUDE.md`'s vagueness requirement for `(security)` subjects, none of these commit subjects name a vulnerability class or attack vector — they describe the category ("add CI security scanning coverage") only, which is appropriate here since this isn't a vulnerability fix in the first place, just extra coverage. -- `.gitignore`: `frontend/coverage/`, `frontend/test-results/`, `/test-results/`, `/playwright-report/` already cover all artifacts this PR's test runs will produce. -- `.dockerignore`: `tests/`, `test-results/`, `test-data/` already excluded from the Docker build context; no new test directories are being introduced by this PR (only edits to existing spec/test files). -- `codecov.yml`: `**/e2e/**`, `**/*.spec.ts`, `**/__tests__/**` already excluded from coverage accounting; `backend/pkg/dnsprovider/builtin/**` already excluded (consistent with §2.3's finding that this package is verified via integration tests). No new source paths are introduced. -- No `Dockerfile` changes — this PR ships no runtime code. +**Nuance retained from the original draft:** this change touches zero Docker-build-relevant paths (no `Dockerfile`, no backend/frontend source), so the triggered Docker build (a side effect of `feat`/`feat(security)` prefixes repo-wide) is harmless but expected — not a sign something is wrong with a "just workflow files + one shell script" PR. --- ## 3. Technical Specifications -This PR is test-infrastructure-only. There is no new API surface, no database schema change, and no new component. The "component design" for this PR is the test-file structure itself. - -### 3.1 Affected files (exhaustive) - -**Frontend unit tests (sub-issue 1):** -- `frontend/src/pages/__tests__/Security.test.tsx` -- `frontend/src/pages/__tests__/Security.audit.test.tsx` -- `frontend/src/pages/__tests__/Security.errors.test.tsx` -- `frontend/src/pages/__tests__/Security.loading.test.tsx` -- `frontend/src/pages/__tests__/Security.dashboard.test.tsx` -- `frontend/src/pages/__tests__/Security.functional.test.tsx` - -**E2E specs (sub-issue 2):** -- `tests/core/certificates.spec.ts` (includes the 5-test "Certificate Deletion" block rewrite — see §2.2) -- `tests/core/proxy-hosts.spec.ts` -- `tests/core/navigation.spec.ts` -- `tests/core/dashboard.spec.ts` -- `tests/settings/smtp-settings.spec.ts` -- `tests/settings/account-settings.spec.ts` -- `tests/security-enforcement/zzz-security-ui/access-lists-crud.spec.ts` -- `tests/security-enforcement/zzz-security-ui/encryption-management.spec.ts` -- `tests/security-enforcement/zzz-security-ui/system-security-settings.spec.ts` -- `tests/security/system-settings-feature-toggles.spec.ts` -- `tests/manual-dns-provider.spec.ts` - -**Test utility additions (sub-issue 2):** -- `tests/utils/api-helpers.ts` — add `getBackupsViaAPI(request, token?)`, following the exact signature/error-handling pattern of the file's existing `get*ViaAPI` functions (e.g. `getCertificatesViaAPI`), targeting `GET /api/v1/backups`. +### 3.0 Change to `scripts/pre-commit-hooks/semgrep-scan.sh` (additive, in scope per §2.7) -**No changes**: any `backend/**` file, any `frontend/src` file outside `__tests__/`, `.gitignore`, `.dockerignore`, `codecov.yml`, any `Dockerfile`. +**File:** `scripts/pre-commit-hooks/semgrep-scan.sh` +**Change:** insert the `OUTPUT_FLAGS` branch (§2.7) immediately before the existing `semgrep scan \` invocation, and replace the invocation's `--error` line with `"${OUTPUT_FLAGS[@]}"`. No other line in the file changes — the `command -v semgrep` check, the `SEMGREP_CONFIG` override branch, and the `TARGETS` construction are byte-identical to today. +**Backward compatibility:** every existing call site (`lefthook.yml`'s `pre-commit.semgrep`, `security-full.semgrep`, `Makefile`'s `security-local`) never sets `SEMGREP_SARIF_OUTPUT`, so `OUTPUT_FLAGS=(--error)` unconditionally for all of them — identical exit-code and output behavior to the pre-change script. +**New behavior (CI-only):** `SEMGREP_SARIF_OUTPUT= bash scripts/pre-commit-hooks/semgrep-scan.sh [targets...]` scans with the same configs/exclusions but emits SARIF to `` instead of hard-failing on findings. -### 3.2 API contracts referenced (read-only, no changes) +### 3.1 New file: `.github/workflows/semgrep.yml` -These existing endpoints are what the strengthened assertions in §2.2 verify against — documented here for implementer reference, not as new contracts: +No API/DB/frontend surface — this is CI/YAML only. Full structural spec below (devops agent should treat this as the authoritative shape; exact YAML syntax is implementer's to finalize, but every element listed must be present). -| Endpoint | Method | Used by (test) | Purpose in this PR | -|---|---|---|---| -| `/api/v1/certificates/:uuid` | `GET` | `certificates.spec.ts` cancel-delete test | Verify certificate still exists after a dismissed delete | -| `/api/v1/certificates/:uuid` | `DELETE` | `certificates.spec.ts` in-use/backup tests | Existing delete flow (`certificate_handler.go:387-470`) — unchanged | -| `/api/v1/backups` | `GET` | `certificates.spec.ts` backup-creation test | Verify a new backup entry appears after a successful cert delete | -| `/api/v1/access-lists/:id` | `GET` | `access-lists-crud.spec.ts` rename test | Verify renamed ACL persisted server-side | -| `/api/v1/proxy-hosts` / `/api/v1/proxy-hosts/:id` | `GET` | `proxy-hosts.spec.ts` creation test | Verify created host persisted server-side | +**Workflow name:** `Semgrep - SAST Scan` -### 3.3 Error handling / edge cases to cover in the new assertions - -- Certificate delete "in use" path: assert the **specific** 409 error surface (toast/message), not merely "a toast of some kind." -- Certificate delete "backup" path: must select/seed a certificate guaranteed **not** in use (backend returns 409 before attempting backup if in use — asserting backup creation against an in-use cert would be a false test). -- ACL/proxy-host rename/creation: API-level verification must tolerate eventual consistency the same way existing passing tests in these files already do (reuse existing `waitFor`/polling helpers, do not add new ad hoc `setTimeout`s). -- WebKit-sensitive keyboard-focus assertions (§2.2, "(a) preferred, (b) fallback"): implementers must actually run the affected spec under `--project=webkit` (not just chromium/firefox) before finalizing as (a); if flaky, fall back to (b) with an accurate WebKit-specific skip reason, not a silent revert to `|| true`. +**Triggers:** +```yaml +on: + pull_request: + branches: [main, nightly, development] + push: + branches: [main, nightly, development] + workflow_dispatch: + schedule: + - cron: '0 4 * * 1' # Mondays 04:00 UTC — offset 1h after CodeQL's 03:00 to avoid runner contention +``` -### 3.4 Data flow notes +**Concurrency:** +```yaml +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.head_ref || github.ref_name }} + cancel-in-progress: true +``` +(Identical pattern to `codeql.yml`.) + +**Permissions — declared at both workflow (top) level and job level, identically, matching `codeql.yml`'s style (Supervisor round 2, non-blocking fix):** +```yaml +permissions: + contents: read + security-events: write + actions: read + pull-requests: read +``` +This exact block appears twice: once at the workflow top level (sibling of `on:`/`concurrency:`), and again inside `jobs.semgrep-scan.permissions` (§3.2). + +### 3.2 Job: `semgrep-scan` + +**Correction (Supervisor round 2, required change 1):** the pinned image is inlined directly as a literal string in `container.image` — `jobs..container` does not have access to the `env` context per GitHub Actions' documented context-availability rules, so a workflow-level `env:` indirection (as drafted originally) would not resolve at all. Inlining is the only correct form, not a fallback. + +```yaml +jobs: + semgrep-scan: + name: Semgrep SAST Scan + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + security-events: write + actions: read + pull-requests: read + container: + image: semgrep/semgrep:1.173.0@sha256:67319956da3dcb58baf5b322899c15458e3963e7018a86aeeb5cd224e69cb77a # semgrep/semgrep 1.173.0 +``` -No data flow changes. The certificate-deletion backup verification (§2.2) exercises an **existing** synchronous flow: `DELETE /api/v1/certificates/:uuid` → `IsCertificateInUse` check → (if not in use) `backupService.GetAvailableSpace()` → `backupService.CreateBackup()` → `service.DeleteCertificateByID()` → response. All calls are synchronous within the single request; no polling/async job is involved for this specific path (unlike the general `POST /api/v1/backups` flow used elsewhere, which does return `202` + a job id — do not conflate the two; the cert-delete backup call is a direct, blocking `CreateBackup()`). +### 3.3 Steps + +1. **Checkout repository** + ```yaml + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: + ref: ${{ github.ref }} + ``` + (Same SHA already pinned and in active use in `codeql.yml` — reuse, don't re-pin a different version.) + +2. **Fix git safe.directory for container user** (mitigates the "dubious ownership" issue documented in Semgrep's own GHA KB article, §2.5): + ```yaml + - name: Configure git safe.directory + run: git config --global --add safe.directory "$GITHUB_WORKSPACE" + ``` + +3. **Verify Semgrep parity guard** (§3.4): + ```yaml + - name: Verify Semgrep parity guard + run: bash scripts/ci/check-semgrep-parity.sh + ``` + +4. **Print Semgrep version** (cheap sanity check that the pinned image actually resolves to the expected CLI version — catches a bad digest pin immediately and legibly, rather than surfacing as a confusing downstream scan failure): + ```yaml + - name: Verify Semgrep version + run: semgrep --version + ``` + +5. **Run Semgrep (SARIF output, non-blocking)** — calls the real script (§2.7/§3.0) with the new opt-in var; no duplicated config list. + ```yaml + - name: Run Semgrep (SARIF output) + id: semgrep_sarif + continue-on-error: true + env: + SEMGREP_SARIF_OUTPUT: semgrep-results.sarif + run: bash scripts/pre-commit-hooks/semgrep-scan.sh + ``` + `continue-on-error: true` because this pass must not block the job even if semgrep itself errors — the SARIF file's presence is checked explicitly next (step 6), and the actual gate is step 8, not this step. + +6. **Check Semgrep SARIF output exists** (mirrors `security-pr.yml`'s `Check Trivy SARIF output exists` step — this was an orphaned reference in the first draft of this plan; it is now a real, numbered step): + ```yaml + - name: Check Semgrep SARIF output exists + id: semgrep_sarif_check + if: always() + run: | + if [ -f semgrep-results.sarif ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + echo "No Semgrep SARIF output found; skipping SARIF upload" + fi + ``` + +7. **Upload Semgrep SARIF to GitHub Security** (gated on step 6's output rather than blindly attempting the upload): + ```yaml + - name: Upload Semgrep SARIF to GitHub Security + if: always() && steps.semgrep_sarif_check.outputs.exists == 'true' + uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7 + with: + sarif_file: semgrep-results.sarif + category: semgrep + continue-on-error: true + ``` + (Reuses the exact SHA already pinned for this purpose in `security-pr.yml` — no new pin to introduce or maintain.) + +8. **Run Semgrep (hard-fail gate)** — calls the same script, this time with the default (unset `SEMGREP_SARIF_OUTPUT`) path, i.e. its normal `--error` behavior: + ```yaml + - name: Run Semgrep (hard-fail gate) + run: bash scripts/pre-commit-hooks/semgrep-scan.sh + ``` + This is the literal `security-full` invocation (§2.1) — same script, same default full-repo targets, same `--error` flag, run a second time (this time without the SARIF env var) so failure here genuinely gates the job. If this step fails, the job fails, blocking the PR/branch — this is the CI-independent reproduction of the local "green" signal the feature exists to deliver. SARIF upload (step 7) has already completed by this point, so a gate failure here does not suppress the informational upload — order matters and is intentional. + +9. **Upload SARIF artifact** (retention, matches `security-pr.yml`'s `Upload scan artifacts` step): + ```yaml + - name: Upload SARIF artifact + if: always() && steps.semgrep_sarif_check.outputs.exists == 'true' + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v4.4.3 + with: + name: semgrep-sarif-${{ github.run_id }} + path: semgrep-results.sarif + retention-days: 14 + continue-on-error: true + ``` + +10. **Job summary** + ```yaml + - name: Create job summary + if: always() + run: | + { + echo "## Semgrep SAST Scan Results" + echo "" + echo "**Rulesets**: p/golang, p/javascript, p/typescript, p/react, p/secrets, p/dockerfile" + echo "**Severity Gate**: ERROR, WARNING (--error)" + if [ "${{ job.status }}" == "success" ]; then + echo "PASSED: no blocking Semgrep findings" + else + echo "FAILED: Semgrep reported blocking findings — see step logs and the Security tab" + fi + } >> "$GITHUB_STEP_SUMMARY" + ``` + +### 3.4 New file: `scripts/ci/check-semgrep-parity.sh` + +**Revised, smaller scope (per §2.7's design change)** — modeled on `scripts/ci/check-codeql-parity.sh`'s approach (grep/structural assertions, not full YAML parsing), but no longer needs to compare two independent config lists, because §3.0's design means there's only one config list in the whole repo (in `semgrep-scan.sh`) and the workflow only ever delegates to it. Exits non-zero with an `::error title=Semgrep parity drift::` annotation on any mismatch. + +**Checks performed:** + +1. Required files exist: `.github/workflows/semgrep.yml`, `scripts/pre-commit-hooks/semgrep-scan.sh`. +2. Assert `scripts/pre-commit-hooks/semgrep-scan.sh` still contains the string `SEMGREP_SARIF_OUTPUT` — the additive CI hook (§3.0) must not be silently removed by a future edit to the script that forgets CI depends on it. +3. Assert `.github/workflows/semgrep.yml` contains **two** distinct delegating calls to the real script, not a reimplemented/inlined `semgrep scan ...` invocation: + - a call with `SEMGREP_SARIF_OUTPUT` set (the reporting pass, step 5) — e.g. assert both the literal strings `SEMGREP_SARIF_OUTPUT` and `scripts/pre-commit-hooks/semgrep-scan.sh` appear within the same step block; + - a bare `bash scripts/pre-commit-hooks/semgrep-scan.sh` call with no env override (the gate pass, step 8). + - This is the direct analogue of `check-codeql-parity.sh`'s "shared blocking logic must live in exactly one place" check (that script's lines enforcing `SHARED_GATE_SCRIPT` usage) — applied here to prevent a future edit from "simplifying" either step by inlining `semgrep scan` directly, which would silently reintroduce the duplicated-config problem §2.7 eliminated. +4. Assert `.github/workflows/semgrep.yml`'s pinned image reference matches the pattern `semgrep/semgrep:[0-9]+\.[0-9]+\.[0-9]+@sha256:[0-9a-f]{64}` (tag + digest both present — catches an accidental un-pin, e.g. someone changing it to `semgrep/semgrep:latest` during a quick edit). +5. Assert `pull_request`/`push` trigger branches in `semgrep.yml` are `[main, nightly, development]`, reusing `check-codeql-parity.sh`'s existing `ensure_event_branches_semantic` helper pattern (§3.5). + +Note what this script **no longer does**, relative to the first draft: it does not enumerate or compare `--config`/`--exclude`/`--exclude-rule` values between two files, because after §3.0's change there is only one file that defines them. + +**Where it's invoked:** +- `.github/workflows/semgrep.yml` step 3 (§3.3), analogous to `codeql.yml`'s "Verify CodeQL parity guard" step. +- Not wired into `lefthook.yml` in this PR — consistent with existing precedent: `check-codeql-parity.sh` is also CI-only today, invoked directly from `codeql.yml` and not from any lefthook stage. Noted as a possible follow-up, not a gap introduced by this plan. + +### 3.5 Shared helper extraction (optional, DRY nicety — from §3.4 item 5) + +`check-semgrep-parity.sh` is now the second script needing the branch-list assertion logic (`ensure_event_branches` / `ensure_event_branches_with_yq` / `ensure_event_branches_semantic`) that currently lives only in `check-codeql-parity.sh`. Recommend extracting it into `scripts/ci/lib/workflow-yaml-asserts.sh`, sourced by both scripts via `source "$(dirname "${BASH_SOURCE[0]}")/lib/workflow-yaml-asserts.sh"`, per `CLAUDE.md`'s "consolidate after second occurrence" DRY guideline. This is a pure refactor of existing, already-tested logic — low risk. Unlike the first draft, this extraction is no longer load-bearing for anything (the config-parity story doesn't depend on it, since there's no config duplication left to compare) — it is a legitimate but strictly optional cleanup. If time-boxed out of this PR, note it as a follow-up rather than skipping silently. + +### 3.6 SARIF category naming + +`category: semgrep` for the `upload-sarif` step (§3.3 step 7) — single, flat category since (unlike CodeQL's per-language matrix) there is only one Semgrep job/run per commit, no need for a parameterized category string. + +### 3.7 Error handling / edge cases + +| Scenario | Behavior | +|---|---| +| Pinned image digest becomes invalid/removed from registry (rare, but Docker Hub retention policies exist) | Job fails at container-pull time with a clear GitHub Actions infra error, not a silent skip. Remediation: re-resolve digest, bump the pin — a normal dependency-bump PR, same as any other pinned SHA bump in this repo. | +| SARIF step (step 5) itself crashes (e.g. semgrep internal error, not a rule finding) | `continue-on-error: true` on step 5 means the job continues; step 6 explicitly checks for the SARIF file's existence and sets an output consumed by steps 7 and 9, so a missing file cleanly skips upload rather than `upload-sarif` failing opaquely on a missing path. | +| Hard-fail gate step (step 8) fails legitimately (real findings) | Job fails, PR shows a red check, `$GITHUB_STEP_SUMMARY` still renders (step 10 runs on `if: always()`), SARIF is still uploaded to the Security tab (step 7 already ran before step 8 — order is intentional: SARIF upload must happen *before* the blocking step so a gate failure doesn't skip the informational upload). | +| Renovate later proposes bumping the pinned `semgrep/semgrep` image tag/digest | Handled like any other Renovate-tracked pin, via `.github/renovate.json` — devops agent should confirm at implementation time whether Renovate's Docker-image datasource already picks up `container: image:` refs in workflow YAML by default, or needs an explicit entry added to `.github/renovate.json`; note as a follow-up if configuration is needed, not a blocker for this PR. | +| A future edit to `semgrep-scan.sh` removes the `SEMGREP_SARIF_OUTPUT` hook, or an edit to `semgrep.yml` reintroduces an inline `semgrep scan` call instead of delegating | `check-semgrep-parity.sh` fails CI on the very next PR that makes either change, per §3.4 items 2-3. | --- ## 4. Implementation Plan -### Phase 1 — E2E specs for new/changed behavior - -No net-new user-facing behavior is being introduced (this is a test-quality fix, not a feature), so there is no `test.fixme()` scaffolding phase in the usual sense. Instead, Phase 1 is: write the `getBackupsViaAPI` helper addition to `tests/utils/api-helpers.ts` (foundation for Commit 2's certificate tests) and confirm it compiles/type-checks against the existing `parseResponse`/`getAuthHeaders` pattern. - -### Phase 2 — Foundation (no behavior change) - -- Add `getBackupsViaAPI` to `tests/utils/api-helpers.ts`. -- No other foundation work required — this PR doesn't touch shared fixtures, `global-setup.ts`, or `playwright.config.js`. - -### Phase 3 — Backend +This is a CI/DevOps-only change plus one small, additive shell-script change. There is no Playwright/E2E surface (no user-facing behavior changes), no backend implementation, no frontend implementation. The phase structure below is adapted accordingly — **the `devops` agent implements this directly; no handoff to backend-dev, frontend-dev, or playwright-dev is needed.** -N/A — confirmed no backend code changes required (§2.3). +### Phase 1 — Foundation (script hook + parity guard + optional shared lib) +- Apply the additive `SEMGREP_SARIF_OUTPUT` change to `scripts/pre-commit-hooks/semgrep-scan.sh` (§3.0). +- Write `scripts/ci/check-semgrep-parity.sh` (§3.4). +- Optionally extract `scripts/ci/lib/workflow-yaml-asserts.sh` from `check-codeql-parity.sh` (§3.5); if done, refactor `check-codeql-parity.sh` to source it and verify it still passes unchanged. +- Validation gate: run the existing `semgrep` lefthook hooks locally (`lefthook run pre-commit` touching a Go/JS file, or `lefthook run security-full`) to confirm the script's default (`SEMGREP_SARIF_OUTPUT` unset) behavior is byte-identical to pre-change — this is the regression check for §3.0's edit. `shellcheck scripts/pre-commit-hooks/semgrep-scan.sh scripts/ci/check-semgrep-parity.sh` (+ `scripts/ci/lib/workflow-yaml-asserts.sh` if extracted). If the shared lib was extracted, `bash scripts/ci/check-codeql-parity.sh` still exits 0 (regression check on that refactor). -### Phase 4 — Frontend / test changes (the bulk of the work) +### Phase 2 — Workflow file +- Write `.github/workflows/semgrep.yml` per §3.1-§3.3 (10 steps, including the SARIF-existence-check as a first-class step, not an orphaned reference). +- Validation gate: `actionlint .github/workflows/semgrep.yml` (tool already required per `lefthook.yml`'s `actionlint` hook, §2 header comment listing required tools). `bash scripts/ci/check-semgrep-parity.sh` now passes against the real files. YAML syntax sanity via `yq eval '.' .github/workflows/semgrep.yml >/dev/null` or equivalent. +- **Live GitHub Actions execution cannot be validated locally** — the actual scan run (image pull, semgrep execution against the real repo, SARIF upload, gate pass/fail) is confirmed only once the PR opens and the workflow triggers on `pull_request`. Note this explicitly in the PR description as a manual verification step, not a local DoD gate. -- Sub-issue 1: unskip 5 files, delete the stale test in the 6th (§2.1). -- Sub-issue 2: fix all 59 tautologies per the file-by-file disposition in §2.2, including the certificate-deletion flow's larger rewrite (native-dialog → custom-modal interaction). +### Phase 3 — Documentation +- Update `SECURITY.md` per §2.8: add Semgrep row to the `### Automated Scanning` table, add a `**Semgrep SAST Scan**` paragraph to `### Scanning Workflows` describing `.github/workflows/semgrep.yml`'s trigger shape and what it covers. +- Update `ARCHITECTURE.md` per §2.8: the three call-outs at lines 166, 1376, and 1498-1501. +- Validation gate: `markdownlint SECURITY.md ARCHITECTURE.md` (tool already required per `lefthook.yml` header comment). -### Phase 5 — Hardening, full-suite validation, docs +### Phase 4 — Integration validation +- `lefthook run pre-commit` (full local hook suite, including the existing `actionlint` and `semgrep` hooks, to confirm nothing in this change breaks existing local gates). +- `bash scripts/ci/check-codeql-parity.sh` (if refactored) and `bash scripts/ci/check-semgrep-parity.sh` both green. +- Manual review of the rendered `semgrep.yml` against `codeql.yml`/`security-pr.yml` for pinning-comment consistency (every third-party `uses:` has a SHA + version comment; the container image has tag + digest inlined, not via `env`). -- Full Vitest suite run (not just the touched files) to catch regressions from unskipping. -- Full Playwright run across chromium/firefox/webkit, including the security-tests shard. -- Coverage checks (frontend + backend) at/above enforced thresholds. -- PR description scaffolding (§6). +### Phase 5 — PR & CI confirmation +- Open PR; confirm `semgrep.yml` actually triggers on the PR event, pulls the pinned image successfully, produces a SARIF upload visible under the repo's Security → Code scanning alerts (filtered by tool "Semgrep"), and that the hard-fail gate step correctly reflects the current repo's Semgrep cleanliness (expected: green, since this is the same ruleset the repo already passes locally today). +- If CI surfaces findings the local run didn't (e.g. a stale local semgrep binary/ruleset that had drifted below the pinned CI version), that is itself the feature working as intended — resolve findings on their merits, not by weakening the pin. --- -## 5. Commit Slicing Strategy - -Single PR, `test/issue-619-test-infra-debt` → `development`, ordered commits. Each commit is independently buildable/testable; later commits depend on earlier ones as noted. - -### Commit 1 — `test: add getBackupsViaAPI helper for E2E backup verification` -- **Scope**: Foundation. Add `getBackupsViaAPI(request, token?)` to `tests/utils/api-helpers.ts`, matching the existing `get*ViaAPI` pattern exactly (JSDoc block, `parseResponse`, `getAuthHeaders`). -- **Files**: `tests/utils/api-helpers.ts`. -- **Dependencies**: none. -- **Validation gate**: `cd frontend && npm run type-check` passes (the helper file is TS, checked as part of the frontend project); no test run needed yet (unused until Commit 3). - -### Commit 2 — `fix: unskip Security.* Vitest suites now that undici/jsdom WebSocket bug is fixed` -- **Scope**: Sub-issue 1. Remove `describe.skip` → `describe` in the 5 files; delete the stale `it.skip('should open notification settings modal...')` block (and its now-empty `describe('Notification Settings Modal', ...)` wrapper) from `Security.functional.test.tsx`. -- **Files**: the 6 files listed in §2.1. -- **Dependencies**: none (independent of Commits 1/3+). -- **Validation gate**: `npx vitest run src/pages/__tests__/Security.test.tsx src/pages/__tests__/Security.audit.test.tsx src/pages/__tests__/Security.errors.test.tsx src/pages/__tests__/Security.loading.test.tsx src/pages/__tests__/Security.dashboard.test.tsx src/pages/__tests__/Security.functional.test.tsx` — zero failures, zero unexpected skips. Then a **full** `npx vitest run` (not just these files) — zero regressions vs. the §2.1 baseline (263→268 passed test files, 3247→3330 passed tests, 88→4 skipped [only the out-of-scope CrowdSec ones remain]). Frontend coverage (`scripts/frontend-test-coverage.sh`) at/above 85%. - -### Commit 3 — `fix: replace tautological assertions in certificates.spec.ts with real backend-verified checks` -- **Scope**: Sub-issue 2, certificates file only (highest-complexity file — isolated to its own commit given the custom-modal rewrite). All 9 `|| true` lines in §2.2's certificates.spec.ts breakdown, plus the native-dialog → custom-modal rewrite for all 5 deletion tests in the "Certificate Deletion" block (`should show delete confirmation dialog`, `should warn if certificate is in use by proxy host`, `should cancel delete when confirmation dismissed`, `should create backup before deletion`, and `should show config reload overlay during deletion` — the last of which has no `|| true` to grep for but shares the identical broken `page.once('dialog', ...)` interaction model and currently asserts nothing). -- **Files**: `tests/core/certificates.spec.ts` (uses `getBackupsViaAPI` from Commit 1, `getCertificateViaAPI` already present). -- **Dependencies**: Commit 1. -- **Validation gate**: `npx playwright test tests/core/certificates.spec.ts --project=chromium` and `--project=firefox` both pass, zero flaky retries. Manual grep confirms zero `|| true).toBeTruthy()` remaining in this file. - -### Commit 4 — `fix: replace tautological assertions in proxy-hosts and access-lists E2E specs` -- **Scope**: Sub-issue 2, the two largest remaining CRUD-flow files. §2.2's `proxy-hosts.spec.ts` (8 lines) and `access-lists-crud.spec.ts` (13 lines) breakdowns. -- **Files**: `tests/core/proxy-hosts.spec.ts`, `tests/security-enforcement/zzz-security-ui/access-lists-crud.spec.ts`. -- **Dependencies**: none (uses `getProxyHostsViaAPI`/`getProxyHostViaAPI`/`getAccessListViaAPI`, all already present in `tests/utils/api-helpers.ts` prior to this PR — does not depend on Commit 1's `getBackupsViaAPI` addition). -- **Validation gate**: `npx playwright test tests/core/proxy-hosts.spec.ts --project=chromium --project=firefox` and the access-lists spec via the `security-tests` project (`npx playwright test tests/security-enforcement/zzz-security-ui/access-lists-crud.spec.ts --project=chromium` per the config's security-shard routing) both pass. Zero `|| true).toBeTruthy()` remaining in either file. - -### Commit 4b — `fix: correct Access List UUID usage and CGNAT warning i18n keys` (SCOPE ADDITION — real app bugs found during Commit 4) - -**Why this exists**: Commit 4's strengthened `access-lists-crud.spec.ts` assertions (no longer tautological) surfaced two genuine, previously-invisible production defects, confirmed via curl repro + Playwright network trace + source grep (not test artifacts): - -1. **Access List edit/update/delete/test-IP all 404 in production.** `frontend/src/pages/AccessLists.tsx`, `frontend/src/hooks/useAccessLists.ts`, `frontend/src/api/accessLists.ts`, and the ACL selector in `frontend/src/components/.../ProxyHostForm.tsx` all key mutations off `acl.id`. `backend/internal/models/access_list.go`'s `ID uint` has `json:"-"` — never serialized; only `uuid` is sent. Every edit/delete/rename request currently resolves to `PUT/DELETE /api/v1/access-lists/undefined` → `404`. `rowKey={(acl) => String(acl.id)}` also collides to `"undefined"` for every table row. `ProxyHosts.tsx` already uses the correct `.uuid` pattern — mirror it. -2. **CGNAT warning banner renders raw i18n keys to every user.** `AccessLists.tsx` calls `t('accessLists.cgnatWarningTitle')` etc. (flat keys) but `frontend/src/locales/en/translation.json` only defines the nested `accessLists.cgnatWarning.title/.message/.solutionsTitle/.solution1-5`. The rendered DOM literally shows concatenated raw key strings to users. Fix: correct the key paths to match the nested structure. - -This is a deliberate, narrow deviation from this plan's original §1.3 non-goal ("no production code changes") — made because leaving the 3 tests these bugs broke permanently `test.skip()`-ed would directly contradict sub-issue 2's entire purpose (replacing fake-always-pass checks with real ones that actually catch defects). Both fixes are small, isolated, high-confidence, and directly required for `access-lists-crud.spec.ts`'s already-committed real assertions to pass. This addition must be called out explicitly in the PR description as a scope note, separate from the planned test-infra-only work, so reviewers can evaluate it on its own merits. - -- **Scope**: `.id` → `.uuid` swap across the Access List frontend mutation path (param types `number` → `string` to match); i18n key path correction in the CGNAT warning block. -- **Files**: `frontend/src/pages/AccessLists.tsx`, `frontend/src/hooks/useAccessLists.ts`, `frontend/src/api/accessLists.ts`, and the ACL selector in the proxy-host form component (exact file to be confirmed at implementation time — grep for `.id` usage against access-list objects). No backend changes (backend already correctly omits `ID` from JSON; frontend must conform to the existing contract, not the other way around). -- **Dependencies**: Commit 4 (the tests that currently fail because of these bugs must already exist). -- **Validation gate**: `npx playwright test tests/security-enforcement/zzz-security-ui/access-lists-crud.spec.ts --project=security-tests` — full pass, all 45 tests, zero flaky retries (up from 42/45 after Commit 4). `cd frontend && npm run type-check` passes. `cd frontend && npx vitest run` — zero regressions (no unit tests should reference the old `.id` access-list field, but confirm). Manual smoke check: rename an ACL via the UI, confirm no `undefined` appears in any network request URL. - -### Commit 5 — `fix: replace tautological assertions in remaining security-UI and settings E2E specs` -- **Scope**: Sub-issue 2, remainder. §2.2's `encryption-management.spec.ts` (8), `system-security-settings.spec.ts` (7), `navigation.spec.ts` (4), `smtp-settings.spec.ts` (4), `dashboard.spec.ts` (3), `system-settings-feature-toggles.spec.ts` (1), `account-settings.spec.ts` (1), `manual-dns-provider.spec.ts` (1). -- **Files**: the 8 files above. -- **Dependencies**: none (none of these 8 files use `getBackupsViaAPI`). -- **Validation gate**: each file passes under its correct project (security-shard files via `security-tests`/chromium; the rest via chromium + firefox). Zero `|| true).toBeTruthy()` remaining anywhere under `tests/`, verified via `grep -rn "|| true" tests/ --include=*.spec.ts` returning empty. - -### Commit 5b — `fix: add aria-current to active navigation links` (SCOPE ADDITION — real app bug found during Commit 5) - -**Why this exists**: same pattern as Commit 4b. Commit 5's strengthened `navigation.spec.ts` assertions (`hasActiveCurrent || hasActiveClass`, `hasAriaCurrent || `, both converged onto "must signal active state via aria-current OR a discoverable active class") surfaced that neither exists: `frontend/src/components/Layout.tsx`'s primary sidebar nav ``s never set `aria-current`, and the active-state Tailwind classes (`bg-brand-700 text-content-primary`, `text-brand-500`, `bg-brand-500/10 text-brand-500`) contain no `"active"`/`"current"` substring an assistive-tech-oriented check (or a screen reader) could key off. Confirmed reproducible 100% across 3 runs, both chromium and firefox, by the implementing agent — not flakiness. This is a genuine, previously-hidden accessibility gap: there is no programmatic way for assistive tech to identify the current page in the primary nav today. - -- **Scope**: Add `aria-current="page"` to the active nav `` in `Layout.tsx`, conditioned on the existing active-route check already used to apply the active Tailwind classes (do not introduce a new route-matching mechanism — reuse whatever comparison already decides which link gets the active classes). -- **Files**: `frontend/src/components/Layout.tsx`. No backend changes. -- **Dependencies**: Commit 5 (the two navigation tests that currently fail because of this gap must already exist). -- **Validation gate**: `npx playwright test tests/core/navigation.spec.ts --project=chromium --project=firefox` — full pass, including `'should highlight active navigation item'` and `'should indicate current page with aria-current'`, zero flaky retries. `cd frontend && npm run type-check` passes. `cd frontend && npx vitest run` — zero regressions. - -### Commit 6 — `docs: close out issue #619 sub-issues 3-5 with coverage/config verification evidence` -- **Scope**: Hardening + docs. No source changes beyond capturing verification evidence. Update `docs/features.md` only if any test-visible behavior description changed (unlikely — confirm at implementation time; if nothing user-facing changed, skip the `docs/features.md` edit and note that explicitly in the PR description instead of forcing an edit for its own sake). -- **Files**: none required; optionally `docs/features.md` if applicable. -- **Dependencies**: Commits 2-5 (needs the final, real test suite to attach real evidence to). -- **Validation gate**: this commit's job *is* the Definition of Done run — see §5.1 below. All gates must be green before this commit closes the PR. - -### 5.1 Full DoD validation (runs once, after Commit 5, evidence captured in Commit 6 / PR description) - -Per `CLAUDE.md`'s Task Completion Protocol, in order: - -1. `npx playwright test --project=firefox` (full suite) — must pass. -2. `npx playwright test --project=chromium` (full suite, includes the `security-tests` shard) — must pass. -3. `npx playwright test --project=webkit` (full suite) — **this is sub-issue 5's outstanding confirming run.** If it fails in a way traceable to one of this PR's newly-real assertions (most likely candidate: the keyboard-focus-order ones flagged "(a) preferred, (b) fallback" in §2.2/§2.5), fix by falling back to the (b) disposition for that specific line with an accurate WebKit-specific skip reason — do not weaken back to `|| true`. If it fails for an unrelated, pre-existing reason, that is a **new finding** outside this PR's original scope and must be flagged back to the user/issue tracker rather than silently patched. -4. `bash scripts/local-patch-report.sh` — patch coverage evidence. -5. `lefthook run pre-commit` (CodeQL Go + JS, staticcheck, etc.) — zero high/critical findings. (No GORM-touching changes in this PR, so §1.5's conditional GORM scan is skipped — confirmed no `backend/internal/models/**` or migration changes.) -6. `make trivy` (or equivalent Trivy container/dependency scan) — zero Critical/High findings. Per `CLAUDE.md`'s Task Completion Protocol step 3, this is **mandatory, zero-tolerance, with no conditional exception** (unlike the GORM scan above, which is explicitly conditional on model/migration changes). This PR touches no dependencies, `go.mod`/`package.json`, or any `Dockerfile`, so no new findings are expected — run and capture as evidence rather than skipping it. -7. `scripts/go-test-coverage.sh` — confirm ≥85%, capturing the §2.3 numbers as evidence (no regressions expected since no backend files changed). -8. `scripts/frontend-test-coverage.sh` — confirm ≥85%, now including the ~84 newly-unskipped tests. -9. `cd frontend && npm run type-check`. -10. `cd backend && go build ./...` and `cd frontend && npm run build`. -11. Full `npx vitest run` — zero failures, zero unexpected skips (only the 4 out-of-scope CrowdSec skips remain). - -### 5.2 Rollback / contingency - -- Each commit is independently revertable without breaking `development` — none introduce cross-file coupling beyond Commit 1's helper (used only by Commit 3+). -- If the WebKit run (§5.1 step 3) surfaces a **pre-existing, unrelated** failure (not caused by this PR's changes), the contingency is: do not block this PR on it — capture the failure, note it explicitly in the PR description as a newly-discovered, out-of-scope finding, and open a follow-up issue (matching the precedent set by this same investigation's sibling fix, which filed `#1221` for an out-of-scope race condition rather than scope-creeping the original fix). -- If any single sub-issue-2 file proves substantially harder than estimated during implementation (most likely: `certificates.spec.ts`'s custom-modal rewrite), it is already isolated to its own commit (Commit 3) specifically so it can be iterated on without blocking Commits 4-5. -- If full-suite Vitest coverage drops below 85% after unskipping (unlikely, since unskipping only adds passing tests, never removes coverage), do not merge — investigate whether any of the newly-active tests are masking a real component defect (per Root Cause Analysis Protocol) rather than adjusting the threshold. - ---- - -## 6. PR Description Scaffolding - -```markdown -## Summary - -Closes #619 (Phase 3 Technical Debt Issues). Verifies and resolves all 5 bundled sub-issues: - -- **Sub-issue 1 (undici/WebSocket jsdom blocker)** — FIXED. Confirmed stale on jsdom@30.0.1/undici@8.10.0 - (upstream nodejs/undici#1671 long resolved). Unskipped 83 tests across 5 Security.*.test.tsx suites. - The 6th related skip (Security.functional.test.tsx notification-modal test) was not a WebSocket issue at - all — root-caused to stale test code describing a modal that was replaced by a router navigation; deleted - as dead code since equivalent, correct, passing coverage already exists in the same file. -- **Sub-issue 2 (weak/tautological E2E assertions)** — FIXED. 59 `expect(x || true).toBeTruthy()` occurrences - across 11 spec files replaced with real deterministic assertions, backend-state-verified checks (certificate - deletion in-use/backup/cancel flows, ACL rename, proxy-host creation), or explicit `test.skip()` calls with - accurate reasons where genuinely environment-dependent — reusing this repo's existing skip convention. - certificates.spec.ts's certificate-deletion tests additionally required a root-cause interaction-model fix: - they drove a native `window.confirm()` that the app no longer uses (replaced by a custom React modal), - meaning they were exercising almost none of the real delete flow. -- **Sub-issue 3 (backend coverage gaps)** — STALE, already resolved, no code changes. Re-verified: - internal/services 88.4% (target 85%), remotestorage 90.3%, backend/pkg/dnsprovider/builtin 91.8% - (target 50% incremental). -- **Sub-issue 4 (feature flag async propagation tests)** — STALE, already resolved, no code changes. - Re-verified: tests/security/system-settings-feature-toggles.spec.ts already uses - waitForFeatureFlagPropagation() at 9 call sites, zero .skip/.fixme. -- **Sub-issue 5 (WebKit E2E not executing)** — Config confirmed healthy (963 tests / 86 files discovered, - no webkit-specific exclusions or browserName-conditioned skips). Full passing run captured as this PR's - DoD evidence (see Test Plan). - -## Test Plan -- [ ] Full `npx vitest run` — zero failures, only the 4 out-of-scope CrowdSecBouncerKeyDisplay skips remain -- [ ] `npx playwright test --project=chromium` (incl. security-tests shard) — full pass -- [ ] `npx playwright test --project=firefox` — full pass -- [ ] `npx playwright test --project=webkit` — full pass (sub-issue 5 confirming run) -- [ ] `scripts/go-test-coverage.sh` ≥ 85% -- [ ] `scripts/frontend-test-coverage.sh` ≥ 85% -- [ ] `lefthook run pre-commit` — zero high/critical CodeQL findings -- [ ] `make trivy` (or equivalent) — zero Critical/High findings -- [ ] `grep -rn "|| true" tests/ --include=*.spec.ts` returns empty -``` +## 5. Acceptance Criteria + +1. `.github/workflows/semgrep.yml` exists, triggers on `pull_request`/`push` to `[main, nightly, development]`, `workflow_dispatch`, and a weekly `schedule`. +2. Semgrep runs inside a `container:` whose `image:` is the pinned string `semgrep/semgrep:@sha256:` inlined directly (no `env:` indirection) — no floating tag, no `@latest`. +3. `permissions:` is declared identically at both the workflow (top) level and the job level. +4. `scripts/pre-commit-hooks/semgrep-scan.sh` carries exactly the additive `SEMGREP_SARIF_OUTPUT` change described in §3.0 — its binary/version-resolution logic and its `--config`/`--exclude`/`--exclude-rule` values are otherwise unchanged, and every existing call site's behavior (`SEMGREP_SARIF_OUTPUT` unset) is byte-identical to pre-change. +5. `.github/workflows/semgrep.yml` invokes `scripts/pre-commit-hooks/semgrep-scan.sh` directly for **both** the SARIF-producing pass (with `SEMGREP_SARIF_OUTPUT` set) and the hard-fail gate pass (unset) — no independent/duplicated `semgrep scan ...` invocation exists anywhere in the workflow YAML. +6. `scripts/ci/check-semgrep-parity.sh` exists, passes against the merged state, and is invoked as a CI step in `semgrep.yml` before the scan runs. +7. SARIF results upload to the GitHub Security tab under category `semgrep`, using the same `github/codeql-action/upload-sarif` SHA pin already used in `security-pr.yml`, gated on an explicit SARIF-existence check step. +8. `SECURITY.md`'s `### Automated Scanning` table and `### Scanning Workflows` section mention Semgrep and `semgrep.yml`. +9. `ARCHITECTURE.md` mentions Semgrep at all three existing security-scanning call-out locations (tech-stack table, CI Jobs list, Container Scanning components list). +10. `.gitignore`, `.dockerignore`, `.codecov.yml`, and all Dockerfiles are confirmed unchanged (per §2.9 — no diff expected in this PR). +11. `actionlint`, `markdownlint`, `shellcheck`, and `lefthook run pre-commit` all pass locally on the final diff. +12. `docs/plans/current_spec.md` (this file) reflects the implemented state — no divergence between plan and shipped workflow at PR time (devops agent should update this file if implementation deviates from any spec section above, per standard plan-fidelity practice). --- -## 7. Acceptance Criteria - -1. Zero `describe.skip`/`it.skip` remain in the 6 sub-issue-1 files except the intentional deletion (not skip) of the stale notification-modal test. -2. `grep -rn "|| true).toBeTruthy()" tests/ --include=*.spec.ts` (or equivalent pattern check) returns **zero** matches. -3. Every occurrence converted to `test.skip()` includes a specific, accurate reason string (no generic "may not apply" left over from the tautology comments). -4. `certificates.spec.ts`'s 5 deletion tests interact with the real custom `DeleteCertificateDialog` modal, not a native `confirm()`. -5. `tests/utils/api-helpers.ts` gains exactly one new function (`getBackupsViaAPI`), matching existing conventions. -6. Full Vitest suite: 0 failures, coverage ≥ 85%. -7. Full Playwright suite on chromium, firefox, **and** webkit: 0 failures (or any webkit-specific failures are explicitly triaged per §5.2's contingency, not silently skipped). -8. Backend coverage unchanged and re-confirmed ≥ targets (no backend files touched). -9. `lefthook run pre-commit` clean. -10. PR description matches the §6 scaffolding, giving issue #619 a complete, accurate paper trail per sub-issue. -11. No changes to `.gitignore`, `.dockerignore`, `codecov.yml`, or any `Dockerfile`. +## 6. Commit Slicing Strategy + +**Decision:** single PR, one feature ("Semgrep CI coverage"), ordered logical commits. No cross-PR splitting per `CLAUDE.md`'s "One Feature = One PR" rule — this is a small, cohesive, CI-only change (plus one additive shell-script hook); splitting it further would violate that rule for no benefit (there's no independently-shippable sub-feature here — a workflow with no parity guard, or a parity guard with no workflow, are both incomplete on their own). Approved as-is by Supervisor round 2 — shape unchanged, contents updated below for §2.7's design revision and the ARCHITECTURE.md addition. + +### Commit 1 — Script hook + parity guard foundation +- **Scope:** Additive-only, no behavior change for any existing call site. Adds the `SEMGREP_SARIF_OUTPUT` hook to `semgrep-scan.sh`, the new (as-yet-unused-by-CI) parity script, and optionally the shared helper extraction. +- **Files:** `scripts/pre-commit-hooks/semgrep-scan.sh` (modified — additive only, per §3.0), `scripts/ci/check-semgrep-parity.sh` (new — will fail if run now, since `semgrep.yml` doesn't exist yet; not wired into any workflow in this commit), `scripts/ci/lib/workflow-yaml-asserts.sh` (new, optional) and `scripts/ci/check-codeql-parity.sh` (refactored to source it, optional, no behavioral change) if the extraction from §3.5 is included. +- **Dependencies:** none. +- **Validation gate:** `shellcheck` on all touched/new scripts; local `lefthook run pre-commit` / `lefthook run security-full` on a sample file confirms `semgrep-scan.sh`'s default behavior is unchanged; `bash scripts/ci/check-codeql-parity.sh` passes unchanged if the refactor is included (regression check). +- **Commit message:** `feat(security): add opt-in SARIF output mode to local Semgrep script and add CI parity guard` + +### Commit 2 — Semgrep CI workflow +- **Scope:** Adds the new workflow file, delegating both its SARIF and gate passes to the script from Commit 1, and wires the parity guard from Commit 1 into it. +- **Files:** `.github/workflows/semgrep.yml` (new). +- **Dependencies:** Commit 1 (the `SEMGREP_SARIF_OUTPUT` hook and the parity guard must exist for this workflow to reference real, working behavior). +- **Validation gate:** `actionlint .github/workflows/semgrep.yml`; `bash scripts/ci/check-semgrep-parity.sh` now passes (workflow file exists, delegates correctly, image pin format valid); `lefthook run pre-commit` clean on the diff. +- **Commit message:** `feat(security): add pinned Semgrep SAST scan to CI, mirroring local pre-commit/pre-push scan` + +### Commit 3 — Documentation +- **Scope:** `SECURITY.md` and `ARCHITECTURE.md` updates only, per §4 Phase 3. +- **Files:** `SECURITY.md`, `ARCHITECTURE.md`. +- **Dependencies:** Commit 2 (documents the workflow file that now exists). +- **Validation gate:** `markdownlint SECURITY.md ARCHITECTURE.md`. +- **Commit message:** `docs: document Semgrep CI scan in SECURITY.md and ARCHITECTURE.md` + +### Commit 4 — Hardening / fixups (conditional) +- **Scope:** Only if Phase 5 (opening the PR and observing the first real workflow run) surfaces something unfixable purely by inspection — e.g. the digest needs re-resolution, `actionlint`/a GitHub Actions schema quirk requires a syntax adjustment not visible from local linting alone, or the container's default shell needs an explicit `shell: bash` on a step. +- **Files:** `.github/workflows/semgrep.yml` and/or `scripts/ci/check-semgrep-parity.sh`, as needed. +- **Dependencies:** Commits 1-3, plus one observed CI run on the PR. +- **Validation gate:** the actual GitHub Actions run on the PR going green. +- **Commit message:** `fix: address Semgrep CI workflow issues found in first live run` (only created if needed — do not pre-author an empty placeholder commit). + +### Rollback / contingency + +- **Rollback:** revert the PR's merge commit. The change is additive-only (new files + one additive, backward-compatible shell-script hook + documentation sections); reverting it removes Semgrep CI coverage cleanly with no residual state — no DB migration, no data written, no schema changed. `git revert -m 1 ` is sufficient. +- **Contingency — pinned image becomes unpullable mid-development-cycle (e.g. registry outage, Docker Hub rate limiting on `ubuntu-latest` runners):** the job fails visibly (container pull failure is unambiguous in the Actions log, distinct from a scan failure), does not block other workflows (independent job, independent file), and does not gate merges any more strictly than any other required-check outage would — same failure mode and same operational response as a transient CodeQL or Trivy Action outage today. +- **Contingency — CI Semgrep surfaces findings that don't reproduce locally:** expected and desired (§4 Phase 5) — indicates local environment drift, not a CI bug. Do not suppress via `--exclude-rule` additions without documenting rationale (matching the existing precedent set by the one documented `gorm-empty-password` exclusion already in the script). +- **Contingency — parity guard is judged too strict/noisy after landing** (e.g. flags legitimate divergence that's actually fine): tune the specific assertion in `check-semgrep-parity.sh`, don't delete the guard wholesale — same operating principle already established for `check-codeql-parity.sh`, which has been iterated on rather than removed. diff --git a/docs/reports/qa_report.md b/docs/reports/qa_report.md index ae96a722d..46f5606ec 100644 --- a/docs/reports/qa_report.md +++ b/docs/reports/qa_report.md @@ -1,94 +1,108 @@ -# QA Report — Issue #619 Test-Infrastructure Debt Closeout +# QA Report — Semgrep CI Security Scan (Independent Verification) -**Branch**: `test/issue-619-test-infra-debt` -**Commits reviewed this pass**: `dac267f3`..`52bdc675` (19 commits — 6 parallel dev-agent fix rounds closing out the 52 E2E failures documented in the prior pass) +**Branch**: `development` +**Commits reviewed**: `6bf066f8`, `2fbecf07`, `7c6fb04f` **Reviewed by**: qa-security agent -**Date**: 2026-08-08 -**Plan reference**: `docs/plans/current_spec.md` +**Date**: 2026-08-14 +**Scope**: CI/CD-only feature — no application code, models, or frontend/UI surface touched. +**Prior review**: Supervisor code review — approved, no blocking issues. +**Purpose**: Independent functional/security verification per Phase 6 of the management pipeline, ahead of a final "done" determination. -## FINAL CLOSEOUT (added by Management after this report, same day) +## Summary Verdict: **PASS** — no blocking defects found. -Per the codified policy change (`75c63696`), no further local full-suite/multi-browser Playwright runs were performed. CI's next run on the PR is the authoritative full cross-browser confirmation. All other, non-Playwright Definition of Done gates were completed locally after this report (branch then gained one more commit, `716e26b2`, fixing the 2 new a11y findings in §1 below): +The Semgrep CI gate is functionally real (confirmed to fail on findings and pass when clean, via a positive-control test), the parity guard genuinely detects drift (confirmed via four separate intentional-break tests), the container image is correctly digest-pinned, and all local DoD-relevant checks scoped to a CI/shell-script-only change are clean. Two pre-existing environment/documentation gaps were identified and are explicitly **not** attributed to this feature (see §4 and §6). -| Gate | Result | +--- + +## 1. Functional Correctness of the Scan (PASS) + +Installed Semgrep 1.173.0 into a throwaway venv (`/tmp/.../scratchpad/semgrep-venv`, exact version match to the pinned CI image) and ran the actual wrapper script `scripts/pre-commit-hooks/semgrep-scan.sh` exactly as CI invokes it. + +| Check | Result | |---|---| -| Backend coverage (`go-test-coverage.sh`) | ✅ 89.2% line coverage (min 87%) | -| Frontend coverage (`frontend-test-coverage.sh`) | ✅ 90.84% line coverage (min 87%); Statements 89.63%, Branches 82.8%, Functions 87.27% | -| Local patch coverage (`local-patch-report.sh`) | ✅ 100% overall (18/18 changed lines), vs. 90% minimum | -| Lefthook `pre-commit` (targeted run against all 51 files changed vs. `development`, since the `actionlint` job hung on an environment issue unrelated to this PR — zero `.github/workflows/*.yml` files are touched by this PR, confirmed via `git diff --stat`, so `actionlint` has nothing to check here regardless) | ✅ go-vet, golangci-lint-fast, dockerfile-check, frontend-type-check, frontend-lint all pass. `shellcheck` re-run directly (bypassing a `{staged_files}` templating artifact) — 0 errors. `semgrep` — 424 rules / 973 files / 0 findings. | -| Lefthook `codeql` (Go + JS) | ✅ Go: 1 result, suppressed (pre-existing, documented in `codeql-suppressions.yml`, unrelated to this PR). JS: 0 findings. Parity check passed. | -| `gitleaks` (not part of the mandatory `pre-commit` stage, run directly for defense-in-depth per the "double-check for secrets" guidance) | ✅ 514 pre-existing findings across the broader repo (test fixtures/mock credentials, none introduced by this PR) — **0 findings in any of this PR's 51 changed files**, confirmed by cross-referencing the gitleaks JSON report against the changed-file list. | -| Trivy container/dependency scan | ➡️ Carried forward, not re-run — confirmed via `git diff --stat` that this PR changes zero `go.mod`/`go.sum`/`package.json`/`package-lock.json`/`Dockerfile*` files across all 30 commits vs. `development`, so the earlier confirmed result (0 new Critical/High, 1 pre-existing tracked HIGH documented in `SECURITY.md`) cannot have drifted. | -| Backend build (`go build ./...`) | ✅ Clean | -| Frontend build (`npm run build`) | ✅ Clean, `✓ built in 2.40s` | -| GORM security scan | N/A, correctly skipped — zero `.go` files changed anywhere in this PR | +| `SEMGREP_SARIF_OUTPUT= bash scripts/pre-commit-hooks/semgrep-scan.sh` (full repo, no targets) | Exit 0. Produced a valid SARIF file (`version`, `runs`, `results`, `$schema` present; parsed cleanly as JSON). | +| `bash scripts/pre-commit-hooks/semgrep-scan.sh` (no env var, full repo) | Exit 0. `--error` semantics confirmed live (see §1.1). | +| Repo clean under full scan | Reproduced: 974 files tracked by git, 160 rules run, **0 findings** — matches both prior QA/DevOps reports exactly. Two suppressed (`nosemgrep`-annotated) `websocket-missing-origin-check` findings appear in the SARIF's `results` array with `suppressions: [{kind: inSource}]` — this is correct SARIF behavior (audit trail for suppressed findings) and does not affect the "0 findings / 0 blocking" scan summary or exit code. | +| Runtime | ~45–48s per full-repo pass locally (single-threaded venv on this sandbox; CI's dedicated `semgrep/semgrep` container should be comparable or faster). | -**This is now a complete Definition of Done sweep for everything except full cross-browser/full-suite Playwright confirmation, which is CI's job per policy.** Recommend: push, open/update the PR, let CI run the full 3-browser matrix, and treat any CI-reported failure as a new finding to triage rather than assuming the local partial runs already covered it. +### 1.1 Positive-control test: does the gate actually gate? (Most important check — PASS) -## STATUS: Partial re-validation — stopped mid-run per updated process guidance. Not a final merge verdict. +Constructed a minimal Go file containing an unguarded `websocket.Upgrader{}.Upgrade()` call (the same rule ID, `go.gorilla.security.audit.websocket-missing-origin-check`, that appears — suppressed — in the real codebase), and ran it through the **actual, unmodified** wrapper script with a single-file target: -This pass was launched as a full Definition-of-Done re-validation (clean rebuild + all 3 browsers + backend/frontend coverage + lefthook + Trivy). **Mid-run, Management issued a process change, since codified in commit `75c63696`** (`CLAUDE.md`, `.claude/agents/qa-security.md`, `management.md`, `playwright-dev.md`): full-suite and multi-browser (`chromium`+`firefox`+`webkit` together) Playwright runs are **CI-only**, never local — not even as a "final" or "consolidated" validation pass. Locally, E2E scope is limited to targeted specs under a single browser (`--project=firefox`). This report captures exactly what was verified before that instruction fully landed, stops all further local full-suite/multi-browser E2E work in compliance with the now-codified policy, and hands back to Management for commit/push so CI can authoritatively confirm cross-browser health. **Do not treat the gates below as a complete DoD sweep** — several were not (re-)run this pass and are marked accordingly, distinct from gates that were actually executed with evidence in this session. No further local full-suite Playwright runs will follow this report. +``` +SEMGREP_SARIF_OUTPUT=out.sarif bash scripts/pre-commit-hooks/semgrep-scan.sh + → Findings: 1 (1 blocking) → exit 0 (SARIF mode does not hard-fail) + +bash scripts/pre-commit-hooks/semgrep-scan.sh + → Findings: 1 (1 blocking) → exit 1 (--error mode hard-fails) +``` + +This is the critical distinction the task flagged as the top risk: a gate that always exits 0 regardless of findings would be a silent no-op. **Confirmed not the case.** The `SEMGREP_SARIF_OUTPUT` toggle in `scripts/pre-commit-hooks/semgrep-scan.sh:42-46` genuinely swaps `--error` for `--sarif --output `, and only the `--error` invocation (the CI workflow's "hard-fail gate" step, `semgrep.yml:75-76`) enforces blocking. The SARIF-producing pass (`semgrep.yml:49-54`) is additionally wrapped in `continue-on-error: true` at the workflow level, which is defense-in-depth on top of the script's own non-blocking `--sarif` exit code — belt and suspenders, not a substitute for the real gate. --- -## Gate-by-Gate Status (this pass) - -| # | Gate | Status | Detail | -|---|------|--------|--------| -| 1 | E2E Docker image clean rebuild | ✅ **VERIFIED** | 3 separate clean rebuilds run this pass (one per browser cycle, matching prior methodology). Changelog-fixture injection confirmed working (`FIXTURE Injecting E2E changelog fixture` → build → `FIXTURE Reverting changelog fixture overwrite`, working tree left clean). Caddy proxy port auto-sync confirmed (`PLAYWRIGHT_CADDY_PROXY_PORT already set to 8180 in .env` on every rebuild). | -| 2 | Playwright — chromium + security-tests | ✅ **COMPLETED** (see note) | **1354 passed, 2 failed, 42 skipped** (fresh clean-rebuilt container). The 2 failures are **new findings**, not part of the previously-documented 52 — see §1 below. Neither is caused by the 19 commits in scope. **Note**: this was a full-suite run under one browser, executed before the CI-only policy (`75c63696`) was fully in effect for this session. Retained here as useful evidence since it already ran to completion, but this is the **last** local full-suite run this pass — not to be repeated. | -| 3 | Playwright — firefox | ⚠️ **STOPPED MID-RUN**, then **halted entirely per codified CI-only policy** | 673 of ~955 tests completed, **0 failures observed** before stop (all ✓ or expected skips). Includes `tests/settings/ntfy-notification-provider.spec.ts:564` (one of the two explicitly flagged uncertain items) — **passed**. Suite was killed cleanly (process group terminated, no orphaned processes left running) partway through `tests/settings/pushover-notification-provider.spec.ts`, once the process-change instruction landed. Not a completed, authoritative run — do not read "0 failures so far" as a clean bill of health for the untested remainder. **This run will not be resumed or repeated locally** — full-suite/multi-browser confirmation is CI's job per `75c63696`. | -| 4 | Playwright — webkit | ⛔ **NOT RUN, and will not be run locally** | Never started, per the now-codified CI-only policy for full-suite runs. `tests/core/caddy-import/caddy-import-webkit.spec.ts:173` (flagged, no root cause found/no fix applied) and the ImportSession per-user-scope race question remain **unverified**; CI's webkit job is now the only path to confirming or refuting them. | -| 5 | Backend coverage (`go-test-coverage.sh`) | ⛔ **NOT RUN this pass** | `backend/coverage.txt` on disk is stale (timestamped before this session). No fresh number to report. | -| 6 | Frontend coverage (`frontend-test-coverage.sh`) | ⛔ **NOT RUN this pass** | `frontend/coverage/lcov.info` absent. No fresh number to report. | -| 7 | Local patch coverage (`local-patch-report.sh`) | ⛔ **NOT RUN this pass** | `test-results/local-patch-report.{md,json}` absent. | -| 8 | Lefthook pre-commit (staticcheck, CodeQL Go+JS, semgrep) | ⛔ **NOT RUN this pass** | | -| 9 | Trivy scan | ⛔ **NOT RUN this pass** | Prior pass reported 0 new Critical/High with 1 pre-existing tracked HIGH (`CVE-2026-32286`, documented in `SECURITY.md`); not re-confirmed this session. No dependency changes in the 19-commit range (all `fix(test)`/`fix` commits touching `tests/`, `frontend/src/App.tsx`, `frontend/src/pages/Login.tsx`, `.env`/rebuild scripts — no `go.mod`/`go.sum`/`package.json` changes), so risk of drift is low but **not independently re-confirmed**. | -| 10 | `git diff dac267f3..HEAD --stat -- backend/ \| grep '\.go$'` (GORM scan applicability) | ✅ **VERIFIED** | Empty. Confirmed no `.go` files changed in the 19-commit range — GORM scan correctly skippable. | -| 11 | Frontend type-check / build / backend build | ⛔ **NOT RUN this pass** | | -| 12 | Full `npx vitest run` | ⛔ **NOT RUN this pass** | | -| 13 | Tautology grep (`grep -rn "\|\| true" tests/ --include=*.spec.ts`) | ✅ **VERIFIED** | 0 matches. Cheap, non-Playwright check — run to completion. | +## 2. Workflow YAML Structural Validity (PASS) + +- `actionlint .github/workflows/semgrep.yml` (installed via `go install github.com/rhysd/actionlint@latest` into a throwaway `GOBIN`): **0 findings, exit 0.** +- Container image resolution: `docker buildx imagetools inspect semgrep/semgrep:1.173.0@sha256:67319956da3dcb58baf5b322899c15458e3963e7018a86aeeb5cd224e69cb77a` (the exact digest read fresh from the committed file, `semgrep.yml:33`) resolved successfully against the registry, returning a multi-platform manifest list whose index digest matches the pinned digest exactly. The pin is real and correct, not a stale/copy-pasted digest. --- -## 1. New finding: 2 accessibility failures in chromium run (not part of the original 52, not caused by the 19 commits) +## 3. Parity Guards (PASS, and confirmed non-trivial) -**`tests/a11y/dns-providers.a11y.spec.ts:8`** and **`tests/a11y/security.a11y.spec.ts:59`** failed in the full chromium+security-tests run with genuine axe-core violations: +- `bash scripts/ci/check-semgrep-parity.sh` — exit 0. +- `bash scripts/ci/check-codeql-parity.sh` — exit 0 (unaffected by the refactor that extracted `scripts/ci/lib/workflow-yaml-asserts.sh`; this file was verified as a correct behavior-preserving extraction, not a modification of the CodeQL guard's assertions). -- **DNS Providers page** — `[CRITICAL] button-name`: 2 icon-only delete buttons (`Trash2` icon, no text/`aria-label`) have no accessible name. Root cause: `frontend/src/components/DNSProviderCard.tsx:187-191` — `` with no `aria-label`. -- **Security dashboard page** — `[CRITICAL] select-name`: 2 `` elements have no associated `