diff --git a/.github/trivy-no-exceptions.txt b/.github/trivy-no-exceptions.txt new file mode 100644 index 000000000..86146d1f2 --- /dev/null +++ b/.github/trivy-no-exceptions.txt @@ -0,0 +1,2 @@ +# No accepted image vulnerability exceptions. +# Bare advisory IDs are intentionally absent. diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 90a333265..c573424e9 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -126,11 +126,14 @@ jobs: snapshot-ref: ${{ github.event_name == 'pull_request' && format('refs/heads/{0}', github.event.pull_request.head.ref) || github.ref }} - name: Dependency Review + id: review if: >- ${{ github.event_name == 'pull_request' && steps.dependency-changes.outputs.changed == 'true' }} - uses: actions/dependency-review-action@v5 + uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 with: + base-ref: ${{ github.event.pull_request.base.sha }} + head-ref: ${{ github.event.pull_request.head.sha }} fail-on-severity: moderate # The dependency graph API can briefly report "no snapshots found for the # head SHA" when a PR's dependency snapshot is submitted asynchronously and @@ -191,6 +194,63 @@ jobs: pkg:npm/@img/sharp-win32-x64 comment-summary-in-pr: always + # Preserve the graph comparison even when a composite license is rejected. + # An empty/missing action output stays empty; it is not a clean result. + - name: Record dependency review evidence + if: >- + ${{ always() && github.event_name == 'pull_request' && + steps.dependency-changes.outputs.changed == 'true' }} + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + REVIEW_OUTCOME: ${{ steps.review.outcome }} + DEPENDENCY_CHANGES: ${{ steps.review.outputs.dependency-changes }} + INVALID_LICENSE_CHANGES: ${{ steps.review.outputs.invalid-license-changes }} + VULNERABLE_CHANGES: ${{ steps.review.outputs.vulnerable-changes }} + DENIED_CHANGES: ${{ steps.review.outputs.denied-changes }} + run: | + python3 - <<'PY' + import json + import os + from pathlib import Path + + evidence = { + "schema_version": 1, + "base_sha": os.environ["BASE_SHA"], + "head_sha": os.environ["HEAD_SHA"], + "run_id": os.environ["GITHUB_RUN_ID"], + "run_attempt": os.environ["GITHUB_RUN_ATTEMPT"], + "review_outcome": os.environ["REVIEW_OUTCOME"], + # Retain the exact raw JSON strings, including duplicate entries, + # full SPDX expressions, and empty outputs from incomplete reviews. + "raw_outputs": { + name: os.environ[variable] + for name, variable in { + "dependency-changes": "DEPENDENCY_CHANGES", + "invalid-license-changes": "INVALID_LICENSE_CHANGES", + "vulnerable-changes": "VULNERABLE_CHANGES", + "denied-changes": "DENIED_CHANGES", + }.items() + }, + } + destination = Path("dependency-review-evidence") + destination.mkdir(exist_ok=True) + (destination / "review.json").write_text( + json.dumps(evidence, indent=2) + "\n", encoding="utf-8" + ) + PY + + - name: Upload dependency review evidence + if: >- + ${{ always() && github.event_name == 'pull_request' && + steps.dependency-changes.outputs.changed == 'true' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: dependency-review-${{ github.event.pull_request.head.sha }}-${{ github.run_attempt }} + path: dependency-review-evidence/review.json + if-no-files-found: error + retention-days: 30 + - name: Record no dependency delta if: >- ${{ github.event_name == 'pull_request' && diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index e55a7c446..7b4f77fd4 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -95,6 +95,8 @@ jobs: timeout-minutes: 10 # Prevent long-running jobs steps: - uses: actions/checkout@v7 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Cache Trivy DB uses: actions/cache@v6 with: @@ -104,31 +106,48 @@ jobs: trivy-db- continue-on-error: true - name: Build image for scanning - run: docker build -t eventrelay:test -f Dockerfile . - - name: Run Trivy vulnerability scanner + run: | + docker build -t eventrelay:test -f Dockerfile . + docker image inspect eventrelay:test > image-identity.json + git rev-parse HEAD > image-source-commit.txt + # Bootstrap #2425 ships dependency repairs separately from the final + # zero-HIGH/CRITICAL gate. Findings remain unresolved in #2396; success + # here proves scan completion, not an accepted baseline or a clean image. + - name: Report Trivy vulnerabilities (enforcement deferred) uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 with: image-ref: 'eventrelay:test' format: 'sarif' output: 'trivy-results.sarif' exit-code: '0' - ignore-unfixed: true + ignore-unfixed: false vuln-type: 'os,library' severity: 'CRITICAL,HIGH' - trivyignores: '.trivyignore' + limit-severities-for-sarif: true + trivyignores: '.github/trivy-no-exceptions.txt' - name: Upload Trivy results to GitHub Security tab uses: github/codeql-action/upload-sarif@v4 - if: hashFiles('trivy-results.sarif') != '' + if: always() && hashFiles('trivy-results.sarif') != '' with: sarif_file: 'trivy-results.sarif' - name: Generate human-readable report uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 - if: success() + if: always() with: image-ref: 'eventrelay:test' format: 'table' exit-code: '0' - ignore-unfixed: true + ignore-unfixed: false vuln-type: 'os,library' severity: 'CRITICAL,HIGH' - trivyignores: '.trivyignore' + trivyignores: '.github/trivy-no-exceptions.txt' + - name: Retain exact image scan evidence + uses: actions/upload-artifact@v7 + if: always() + with: + name: trivy-image-evidence + path: | + trivy-results.sarif + image-identity.json + image-source-commit.txt + if-no-files-found: error diff --git a/Dockerfile b/Dockerfile index 04e206c1b..ac57df9a0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* # Copy dependency manifests -COPY pyproject.toml requirements.txt* ./ +COPY pyproject.toml requirements.txt* constraints.txt ./ COPY package.json package-lock.json ./ COPY apps/web/package.json ./apps/web/ @@ -32,8 +32,12 @@ COPY apps/web/package.json ./apps/web/ COPY apps/web/scripts ./apps/web/scripts # Install Python dependencies -RUN pip install --no-cache-dir --upgrade pip && \ - (pip install --no-cache-dir -r requirements.txt || pip install --no-cache-dir -e .) +RUN pip install --no-cache-dir --upgrade \ + pip \ + "setuptools>=80.10.2" \ + "wheel>=0.46.2" && \ + (pip install --no-cache-dir -c constraints.txt -r requirements.txt || \ + pip install --no-cache-dir -c constraints.txt -e .) # Install Node.js dependencies for the web app # Using workspace to ensure proper hoisting and dependency resolution @@ -53,6 +57,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ && curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \ && apt-get install -y nodejs \ + && npm install --global npm@12.2.0 \ && rm -rf /var/lib/apt/lists/* # Create non-root user @@ -60,6 +65,10 @@ RUN groupadd --gid 1000 appuser && \ useradd --uid 1000 --gid appuser --shell /bin/bash --create-home appuser # Copy installed Python packages from builder +# The base image ships packaging metadata of its own. Clear it before copying +# the builder environment so stale vulnerable dist-info/vendor records cannot +# survive the directory overlay into the runtime image. +RUN rm -rf /usr/local/lib/python3.11/site-packages/* COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY --from=builder /usr/local/bin /usr/local/bin diff --git a/MERGE_POLICY.md b/MERGE_POLICY.md index 9be3771ce..9448f1181 100644 --- a/MERGE_POLICY.md +++ b/MERGE_POLICY.md @@ -113,27 +113,34 @@ strand a change permanently pending. v1 listed `Security` among its required six, and dropping every security scan out of the required set would be a weakening introduced by the rewrite that set out to make this gate precise. -> **What requiring these does and does not buy you.** Four of the six cannot -> fail on findings, by construction, in `.github/workflows/security.yml`: -> -> | Job | Why it always reports `success` | -> | --- | --- | -> | `npm-audit` | `continue-on-error: true` | -> | `python-safety` | `\|\| true` **and** `continue-on-error: true` | -> | `bandit` | `\|\| true` | -> | `trivy` | `exit-code: '0'` on both invocations | -> -> `Security Scan - python` and `Security Scan - javascript` are CodeQL matrix -> jobs; they report that analysis completed and upload alerts to the Security -> tab. Whether an alert blocks a merge is a code-scanning check-failure setting, -> not something the workflow decides. -> -> So gate 2 requires these security scan jobs (check-runs) to **run and complete** — -> which catches a scan that crashed, timed out, or was silently dropped from the -> pipeline. It is not a findings gate, and this policy should not be read as -> claiming vulnerability enforcement. Making findings actually block is a change to -> `security.yml`, with its own diff and its own blast radius; see *What is -> deliberately not here*. +`trivy` is temporarily a required **scan-completion gate** for the dependency +repair bootstrap (#2425, PR #2409). Image build or scanner execution errors +still fail the job, but reported vulnerabilities do not (`exit-code: '0'`). +The HIGH/CRITICAL SARIF includes findings without a fix; SARIF, exact source +commit and image identity are retained even when the job fails. A green `trivy` +or uppercase `Trivy` SARIF-ingestion check does not establish a clean image. + +The dependency repairs are separated from final strict enforcement. This is +partial remediation, with no accepted baseline and no risk acceptance for the +remaining findings. Parent #2396 stays open until a re-fetched exact image or +separately reviewed applicability/VEX evidence accounts for every finding. +See [the bootstrap evidence and remaining gates](docs/security/image-remediation-bootstrap.md). +Enable the final `exit-code: '1'` HIGH/CRITICAL gate in a separately reviewed +change after that accounting, or introduce an explicitly reviewed delta policy +with package/version/path identity and regression tests. Keep +`limit-severities-for-sarif: true` so the pinned action honors the threshold. + +No exceptions are accepted by the current image scan. Existing bare IDs in +`.trivyignore` are not evidence of approved risk acceptance and are not applied. +Any future exception must be introduced in a separately reviewed change with +an exact advisory/package/version/image scope, applicability evidence, +compensating controls, an accountable approver, linked issue, expiry, and an +enforced expiry check. A missing advisory or unproven exposure remains unresolved. + +The other security jobs also remain completion gates: `npm-audit`, `python-safety`, +and `bandit` still tolerate findings. CodeQL check failure also depends on the +repository's code-scanning settings. Their green conclusions do not establish +absence of vulnerabilities. > **`trivy` is lowercase.** Two distinct check-runs exist on the same head — > `trivy` reports `success`, `Trivy` reports `neutral`, confirmed on both heads @@ -252,16 +259,13 @@ laundering itself as enforcement. - **No evidence ledger requirement.** v1 mandated a receipt comment on #898 for every gate evaluation. Nothing consumed those receipts. Git history and check runs are the ledger. -- **No change to what the security scans enforce.** Gate 2 requires the six - security checks to run and complete; four of them cannot fail on findings, as - documented there. Turning `npm-audit`, `python-safety`, `bandit`, and `trivy` - into blocking gates means removing their `continue-on-error` / `|| true` / - `exit-code: '0'` escapes in `security.yml`, which will fail pull requests on - the existing backlog of findings the moment it lands. That is a deliberate - decision about tolerance for known vulnerabilities, not a documentation fix, - so it is left to its own pull request. Requiring the checks first is still - worth doing: it makes a scan that crashes or quietly disappears from the - pipeline visible, which is the failure this policy can actually close. +- **Strict image enforcement is deferred explicitly.** The #2425 bootstrap + keeps `trivy` required for scan completion and evidence retention while #2396 + owns the unresolved findings and final enforcement. This is not a clean-image + claim or an approved exception list. Do not automatically demote `trivy` or + suppress findings for a high failure rate; the 50% demotion rule applies to + broken execution, not to valid security findings. Final enforcement requires + the separate review and finding accounting described in gate 2. ## Review clock diff --git a/apps/web/package.json b/apps/web/package.json index 043f67e9a..55a2e8546 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -48,7 +48,7 @@ "flags": "^4.3.1", "jpeg-js": "^0.4.4", "lucide-react": "^1.47.0", - "next": "^16.3.3", + "next": "16.3.8", "next-auth": "^4.24.15", "openai": "^7.23.0", "react": "^19", diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 000000000..e3ab0a4c1 --- /dev/null +++ b/constraints.txt @@ -0,0 +1,2 @@ +# Security floors for transitive Python packages copied into the runtime image. +urllib3>=2.8.0 diff --git a/docs/security/image-remediation-bootstrap.md b/docs/security/image-remediation-bootstrap.md new file mode 100644 index 000000000..e23fb70b7 --- /dev/null +++ b/docs/security/image-remediation-bootstrap.md @@ -0,0 +1,102 @@ +# Image dependency remediation bootstrap + +PR #2409 implements the bounded child [#2425](https://github.com/groupthinking/EventRelay/issues/2425). +The parent [#2396](https://github.com/groupthinking/EventRelay/issues/2396) remains +the unresolved image-security backlog. This is partial remediation; neither +scanner success nor a smaller report establishes a clean image or production +remediation. + +## Bootstrap decision + +Ship the already-verified application dependency repairs separately from final +strict Trivy enforcement. The required lowercase `trivy` job builds and scans +the exact PR source and retains image identity, source SHA and HIGH/CRITICAL +SARIF. Findings, including unfixed findings, are reported with `exit-code: '0'`. +Build and scanner execution failures still fail the job. No `continue-on-error` +is added to either scan, and the empty `.github/trivy-no-exceptions.txt` remains +the only ignore input. Existing `.trivyignore` IDs are not applied. + +This is an explicit split, not a baseline-delta gate or accepted risk. It does +not block newly introduced findings. Review each new exact-image report before +merge; do not infer clearance from the scan-completion check. A future delta +gate would need separate review of advisory/package/version/path identity, +severity and occurrence regressions, missing/malformed evidence, and baseline +changes. A total-count cap alone is insufficient. + +`limit-severities-for-sarif: true` fixes the review finding that the pinned action +otherwise clears `TRIVY_SEVERITY` for SARIF output. The historical SARIF files +below retain all originally generated results; future reports deliberately +honor the configured HIGH/CRITICAL threshold. See the +[pinned action implementation](https://github.com/aquasecurity/trivy-action/blob/ed142fd0673e97e23eac54620cfb913e5ce36c25/entrypoint.sh#L69-L76). + +## Retained image evidence, inspected 2026-10-16 + +These are two historical snapshots. A new commit requires new hosted evidence. +Both images are linux/amd64; neither has a published `RepoDigest` in the retained +Docker inspect output. The following SHA256 values identify local image +contents, not registry publication. + +| Field | Original retained scan | Latest inspected PR head | +| --- | --- | --- | +| Source commit | `1d9f72d7c40bff34aa43b8107cb817ad01e8efd5` | `64d2562dce7f2d870b32e3982ffe8888bbdcd808` | +| Run / job | [38258616455 / 114843053097](https://github.com/groupthinking/EventRelay/actions/runs/38258616455/job/114843053097) | [38706968204 / 116328658621](https://github.com/groupthinking/EventRelay/actions/runs/38706968204/job/116328658621) | +| Artifact ID | `11751896119` | `11958184738` | +| Image ID | `sha256:3e479db974698dc0bf11eec7fbce12acee901a33fe8179570fe4418a6b2ec34e` | `sha256:58c3934b3b50d5e9086a822bfd87f185839ff61f8302b0d165f1960acf839201` | +| HIGH / CRITICAL occurrences | **259** (258 / 1) | **252** (251 / 1) | +| OS / global npm / Python occurrences | 251 / 4 / 4 | 244 / 4 / 4 | +| Unique HIGH/CRITICAL advisories | 68 | 63 | +| All original SARIF results | 905 | 934 | + +Artifact ZIP SHA256: + +- Original: `545f1565a67c41fe5a9d641854e74f28bbb7050aa3247e537ca27d4fcb976459`. +- Latest inspected: `1ff1b9ca9a058b14f536a585bbe9e29e8dc0dbd3d079940878b0bd6422275217`. + +Original `trivy-results.sarif` SHA256: + +- Original: `3b32c283e60868c70b2929bc936d38c08e5f4f410d3e21ecad76ec10c95c9c37`. +- Latest inspected: `ebc2fa5c7bb192d8d523ed1395944cb2d2c3a780e905d9baf61763bfd67ed6f6`. + +Both downloaded ZIPs were checked against provider artifact digests. Each +contains `image-source-commit.txt`, `image-identity.json` and the original +`trivy-results.sarif`; all original package paths and finding occurrences remain +in those artifacts. Local retained copies and occurrence-by-occurrence +reconciliation are under `work/pr2409-evidence/`, including +`scan-comparison.json`. Do not rewrite the 259-occurrence snapshot as a 252-row +baseline. + +The source changes between these two heads are a merge from `main`; the image +Dockerfile, dependency inputs and scan workflow are unchanged. Seven +advisory/package/path occurrences disappeared from the later report: +`CVE-2026-8286` and `CVE-2026-8927` in curl/libcurl4t64, +`CVE-2026-88647` and `CVE-2026-88648` in libgnutls30t64, and +`CVE-2026-96889` in librsvg2-2. Curl/libcurl moved from +`8.14.1-2+deb13u5` to `8.14.1-2+deb13u7`; four continuing findings therefore +have changed version-bearing keys. Different image builds and scanner databases +can change reports. These disappearances are observations, not independently +accounted remediations. + +## Remaining gates and ownership + +All 244 OS occurrences in the latest inspected report have no fixed version +populated. The npm and pip vendor findings remain unresolved. Keep package +paths and runtime reachability in the #2396 ledger: application constraints +cannot replace private npm/pip vendor bundles, and removal of runtime tools is +a compatibility change requiring its own review. + +Before closing #2425, obtain CI, Security Scan, Dependency Review, E2E, Coverage, +CodeQL, secret scan and applicable reviews for one exact implementation head. +Keep the PR draft while intentional required checks fail. The inspected +`64d2562…` head was open and **not** draft despite the stale description; it is +not evidence that these new changes passed hosted checks. + +Before final strict enforcement, a re-fetched exact image or separately reviewed +applicability/VEX evidence must account for every remaining finding in #2396. +Enable `exit-code: '1'` with the same unfixed-finding and severity settings in a +separately reviewed change. Any exception needs the exact scope, applicability +evidence, owner, linked issue, compensating controls, expiry and enforced expiry +validation required by `MERGE_POLICY.md`. No merge, deployment, clean-image +claim or closure of #2396 follows from this bootstrap. + +See [NumPy license investigation](numpy-license-review.md) for the independent +license gate. The license allowlist is unchanged. diff --git a/docs/security/numpy-license-review.md b/docs/security/numpy-license-review.md new file mode 100644 index 000000000..a05dcbaed --- /dev/null +++ b/docs/security/numpy-license-review.md @@ -0,0 +1,31 @@ +# NumPy license review for PR #2409 + +Reviewed on 2026-10-16. This records the license-check diagnosis for [PR #2409](https://github.com/groupthinking/EventRelay/pull/2409) and [bootstrap issue #2425](https://github.com/groupthinking/EventRelay/issues/2425). The remaining security backlog stays in [#2396](https://github.com/groupthinking/EventRelay/issues/2396). + +## Historical rejection + +[Dependency Review run 38258616716, job 114843054032](https://github.com/groupthinking/EventRelay/actions/runs/38258616716/job/114843054032) failed on 2026-10-12 while comparing base `bd0f437180b994d8916fcba22ada610bf2fbae0a` with head `1d9f72d7c40bff34aa43b8107cb817ad01e8efd5`. It reported NumPy `2.5.3` to `2.5.4` as a dependency graph change, with six incompatible-license entries across three manifests (each appears twice): + +- `requirements.txt` +- `docs/knowledge_prototypes/universal-automation-service/requirements.txt` +- `docs/knowledge_prototypes/mcp-servers/mcp-alexnet/requirements.txt` + +The complete expression was `BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0`. Both [NumPy 2.5.3 metadata](https://pypi.org/pypi/numpy/2.5.3/json) and [NumPy 2.5.4 metadata](https://pypi.org/pypi/numpy/2.5.4/json) declare this same expression. NumPy's bundled [libdivide license](https://github.com/numpy/numpy/blob/v2.5.4/numpy/_core/include/numpy/libdivide/LICENSE.txt) contains the Zlib terms. Dropping that component or replacing the expression with NumPy's primary BSD license would lose material license information. + +This was a valid policy rejection, not an AND parser failure. The action used `a1d282b36b6f3519aa1f3fc636f609c47dddb294`. Its [license check](https://github.com/actions/dependency-review-action/blob/a1d282b36b6f3519aa1f3fc636f609c47dddb294/src/licenses.ts) passes the full expression to its [SPDX evaluator](https://github.com/actions/dependency-review-action/blob/a1d282b36b6f3519aa1f3fc636f609c47dddb294/src/spdx.ts). Zlib is absent from this repository's allowlist. A focused check with the action's locked `spdx-satisfies@6.0.0` version rejects the NumPy expression, accepts an AND expression containing only the already approved components, and rejects Zlib alone. + +## Current head evidence and limits + +[Dependency Review run 38707004697, job 116329188139](https://github.com/groupthinking/EventRelay/actions/runs/38707004697/job/116329188139) succeeded on 2026-10-15 for base `002b697c3c5c6cb208c24e1b3413f13f1a2a8794` and head `64d2562dce7f2d870b32e3982ffe8888bbdcd808`. Dependency detection, snapshot submission, and Dependency Review all executed successfully; this was not the no-dependency-delta skip path. Its complete Dependency Changes section has no NumPy addition, removal, or license failure. + +The action SHA, license allowlist, and package exclusions were identical in these two runs. The three affected requirement files, `pyproject.toml`, `uv.lock`, and dependency-review workflow also have no source diff between those heads. The comparison's base changed, and the action submitted fresh resolver-generated snapshots. The observable change is in the dependency graph delta. The retained logs do not expose the complete base and head snapshots, so they do not establish which snapshot change removed the NumPy delta. There is no evidence of a NumPy license correction or policy approval. + +The current passing comparison resolves the historical PR check blocker for that exact head only. It does not approve NumPy's Zlib component, establish a compliant full dependency inventory, or prove a future head will pass. A future NumPy addition with this expression must still fail under the present policy. This work adds no Zlib permission, NumPy exclusion, warning-only mode, or license-check disablement. + +## Reviewable evidence going forward + +The dependency-review workflow now pins the same action implementation observed in both runs and names the exact PR base and head SHAs explicitly. After an attempted comparison, it saves the action outcome and all four raw dependency review outputs in `review.json`, retaining duplicate occurrences and complete SPDX expressions. Missing outputs stay empty rather than being converted to a clean empty array. The artifact name includes the PR head SHA and run attempt, and upload runs after a failed review as well as a successful review. The original review failure remains blocking. + +The workflow regression tests execute the evidence-writing step with an incompatible composite expression, duplicate occurrences, incomplete outputs, and hostile strings supplied as data. They also guard the unchanged license allowlist, enabled blocking license check, and absence of a NumPy exclusion. These tests verify local workflow behavior and policy preservation; they do not replace a future exact-head hosted Dependency Review run. + +The image remediation remains partial. This license diagnosis does not account for or dismiss the retained 259 HIGH/CRITICAL image occurrences, approve strict enforcement completion, close #2396, or establish production remediation. diff --git a/package-lock.json b/package-lock.json index f3050cca4..2784aabd0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "@modelcontextprotocol/sdk": "^1.30.1", "brace-expansion": "^5.0.12", "eslint": "^9.39.5", - "next": "^16.3.3", + "next": "16.3.8", "turbo": "^2.11.3", "typescript": "6.0.3", "undici": "^8.11.0", @@ -75,7 +75,7 @@ "flags": "^4.3.1", "jpeg-js": "^0.4.4", "lucide-react": "^1.47.0", - "next": "^16.3.3", + "next": "16.3.8", "next-auth": "^4.24.15", "openai": "^7.23.0", "react": "^19", @@ -2317,9 +2317,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", - "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", "cpu": [ "arm64" ], @@ -2335,13 +2335,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.3" + "@img/sharp-libvips-darwin-arm64": "1.3.4" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", - "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", "cpu": [ "x64" ], @@ -2357,20 +2357,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.3" + "@img/sharp-libvips-darwin-x64": "1.3.4" } }, "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", - "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", "license": "Apache-2.0", "optional": true, "os": [ "freebsd" ], "dependencies": { - "@img/sharp-wasm32": "0.35.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -2380,9 +2380,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", - "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", "cpu": [ "arm64" ], @@ -2396,9 +2396,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", - "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", "cpu": [ "x64" ], @@ -2412,9 +2412,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", - "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", "cpu": [ "arm" ], @@ -2428,9 +2428,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", - "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", "cpu": [ "arm64" ], @@ -2444,9 +2444,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", - "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", "cpu": [ "ppc64" ], @@ -2460,9 +2460,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", - "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", "cpu": [ "riscv64" ], @@ -2476,9 +2476,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", - "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", "cpu": [ "s390x" ], @@ -2492,9 +2492,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", - "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", "cpu": [ "x64" ], @@ -2508,9 +2508,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", - "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", "cpu": [ "arm64" ], @@ -2524,9 +2524,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", - "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", "cpu": [ "x64" ], @@ -2540,9 +2540,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", - "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", "cpu": [ "arm" ], @@ -2558,13 +2558,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.3" + "@img/sharp-libvips-linux-arm": "1.3.4" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", - "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", "cpu": [ "arm64" ], @@ -2580,13 +2580,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.3" + "@img/sharp-libvips-linux-arm64": "1.3.4" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", - "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", "cpu": [ "ppc64" ], @@ -2602,13 +2602,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.3" + "@img/sharp-libvips-linux-ppc64": "1.3.4" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", - "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", "cpu": [ "riscv64" ], @@ -2624,13 +2624,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.3" + "@img/sharp-libvips-linux-riscv64": "1.3.4" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", - "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", "cpu": [ "s390x" ], @@ -2646,13 +2646,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.3" + "@img/sharp-libvips-linux-s390x": "1.3.4" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", - "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", "cpu": [ "x64" ], @@ -2668,13 +2668,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.3" + "@img/sharp-libvips-linux-x64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", - "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", "cpu": [ "arm64" ], @@ -2690,13 +2690,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", - "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", "cpu": [ "x64" ], @@ -2712,13 +2712,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.3" + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", - "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { @@ -2732,16 +2732,16 @@ } }, "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", - "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", "cpu": [ "wasm32" ], "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.4" + "@img/sharp-wasm32": "0.35.5" }, "engines": { "node": ">=20.9.0" @@ -2751,9 +2751,9 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", - "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", "cpu": [ "arm64" ], @@ -2770,9 +2770,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", - "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", "cpu": [ "ia32" ], @@ -2789,9 +2789,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", - "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", "cpu": [ "x64" ], @@ -2923,9 +2923,9 @@ } }, "node_modules/@next/env": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.5.tgz", - "integrity": "sha512-NWEXVDMqoEo0ktmU6u0sE2Vg0LOcsD7NnOTJNo3/fEaTfsg+F1bMIxuDmQbda4e3yTIQwVdUREF2yIuMOusKtg==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.8.tgz", + "integrity": "sha512-Al9zqHVV7TJv0eFuOU4U7Lvv74PTih4Ch63sk2xCIpSTkE3udFnaOcnzP2lQVymiL7yS9Cj2iClUXlR3EQ5sEw==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { @@ -2940,9 +2940,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.5.tgz", - "integrity": "sha512-pMmGgETfKvElucLHtVaeiMRbp2zUbvKx7b1yGko0liBz3cw1mKSggWN/Rp/wPz8z+E1O82u3r4L1Co+ZS5hokQ==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.8.tgz", + "integrity": "sha512-2JPRMh2nmQG5CiL7cXGL9AGwnPWJQ//cTtAUCT+w511QHk79SYz3LGv/pc5X643B/WEO0rvu3Yww0hqwt3kgeA==", "cpu": [ "arm64" ], @@ -2956,9 +2956,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.5.tgz", - "integrity": "sha512-76VaGYvf6HPa5/w12yLkE3dXTn9AfdEviI79oEL3aZoAmRLc9rWitjWqyjViVysK/ht/y9YKzFkBrUdi/wGkow==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.8.tgz", + "integrity": "sha512-GZtCCOBKJ4leVIT/Th0llWKhD1ca92lzbQiS5R5ON9QkoiFnilFsebDae1JU2a3HWoKMEmEZWGs1AGLavVM72Q==", "cpu": [ "x64" ], @@ -2972,9 +2972,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.5.tgz", - "integrity": "sha512-zKDELJ5jSQMHeO/hmXUQsAzagX4bQD4OiMi3pQ5FbUj+yK506oLVHnKA2YXMlbg1EHHqJYtyePOgByIDXD1lqw==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.8.tgz", + "integrity": "sha512-O659ygeQYqneJ1fBKMpFxIFqYkYswu8IAS1OCKK/4f3ZgJJm1dRz4fVJZRi/kLLWjnBKnebOePA4WNv+sV1pVA==", "cpu": [ "arm64" ], @@ -2988,9 +2988,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.5.tgz", - "integrity": "sha512-7Vql0pgzCoHagv6+FNOZoqmJqA52c6zeVbhtS/47qFozO1MSx4ms7x7GHiciY8R5CDsSMKMQjJEryoJLcsBIbA==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.8.tgz", + "integrity": "sha512-dSjKSyWpzxoO1d3DIZZcP4XJcNaKeLmxQMFOiYl5vuBRMmweIqnAhty8tAmRsvTss779cK1FtYnDMj40e4TQlg==", "cpu": [ "arm64" ], @@ -3004,9 +3004,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.5.tgz", - "integrity": "sha512-NH/xzehyHEFWE2nlcZon7TB/0+H4shfWCi7S1zka815XCOhJDYZhoeJtOYy0dh0WVRWACVXSyGNFFytoMxUhRg==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.8.tgz", + "integrity": "sha512-lbqOuz3RPRcv+o9msNsJw5x4+Y1ZwPTs6vmL6DCf7i0fZfvng/F59wyeDwqHIvV0mK//RBy/jJkZ+nCKsSMXjQ==", "cpu": [ "x64" ], @@ -3020,9 +3020,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.5.tgz", - "integrity": "sha512-lV4+EhWMfS8jcC+EH2nn/Cm5cn6XsgbE07bU9tMH8fCo0tNAqhyzi1b5wQ/Tn6NGFTvKDY65w3ZH95EjwBRAnQ==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.8.tgz", + "integrity": "sha512-+316WswI8ScVgZeUd+1KGaXkHhaYQzCjvH/05TZSpJ8zBizb1a4G7DtO7F12jcBIqMOtsz9ji1t48fmKtzqsGA==", "cpu": [ "x64" ], @@ -3036,9 +3036,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.5.tgz", - "integrity": "sha512-/wKzAREX2RF++MhicjDbg8tGn2AiBIM0+EFeTFKoUEUbW5D6amCJehd5Z5G1H5/gxNdgnwoXMcHz24H/c2tGkQ==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.8.tgz", + "integrity": "sha512-ji0gd4kMYUxO+1fJBIbiBVRCjzG/lloiyCccnlebvb1ZJ5qXCPZqYg4Jl1DrrixnWNMKylzgpmMWx0yNDYXlzw==", "cpu": [ "arm64" ], @@ -3052,9 +3052,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.5.tgz", - "integrity": "sha512-LNdCHzgLFc+UeqMS84LzXPaeBRKyqDN9OMyFAr1OrB0XrNw78IRrEVtZvvA7245W/HsaoeVOQX9jPjPk8jojwA==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.8.tgz", + "integrity": "sha512-WcTlaKt/TWkh5kUjdJcUmB1XgZ+1c6fz4Y9fDHL73YNSdGaUWjceeWrrlwF0nv19iABYWC4iAq1oX1w4Bn0vfg==", "cpu": [ "x64" ], @@ -9474,9 +9474,9 @@ } }, "node_modules/devalue": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.2.tgz", - "integrity": "sha512-po4PAY5c53tw5XMocSnf8A/5OHhbbUftpr93aEN6BBoAdntUmK7vu7wOATqvt7cXO7m1Cl4gMVn6p7n6n4mj0w==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.3.tgz", + "integrity": "sha512-xRumYOCUZN/EesqHEU3WOXanOZNvfZFZ/o1AHVFDX1yI0UAkZkOgDXt341CzKoBVIkgQba55/+DjGBKrIoKcHw==", "license": "MIT" }, "node_modules/doctrine": { @@ -13299,12 +13299,12 @@ } }, "node_modules/next": { - "version": "16.3.5", - "resolved": "https://registry.npmjs.org/next/-/next-16.3.5.tgz", - "integrity": "sha512-MdtsTgzyfCPRLC6uJ1mN8ao7lyJ4BB0U6Inhnx3gta1UcCIdHK3yxLG0E8OWQteWD8/Q0qb8A5o7wJaL8M9y2w==", + "version": "16.3.8", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.8.tgz", + "integrity": "sha512-U7QEZaTini6wKrb8A8hqLLqYQyCetegKjCpJOyxk642vWoMoU1x5PyZCJFvgYgiptA8xc5j/9xYlZFO7w9Sjmw==", "license": "MIT", "dependencies": { - "@next/env": "16.3.5", + "@next/env": "16.3.8", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -13318,14 +13318,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.3.5", - "@next/swc-darwin-x64": "16.3.5", - "@next/swc-linux-arm64-gnu": "16.3.5", - "@next/swc-linux-arm64-musl": "16.3.5", - "@next/swc-linux-x64-gnu": "16.3.5", - "@next/swc-linux-x64-musl": "16.3.5", - "@next/swc-win32-arm64-msvc": "16.3.5", - "@next/swc-win32-x64-msvc": "16.3.5", + "@next/swc-darwin-arm64": "16.3.8", + "@next/swc-darwin-x64": "16.3.8", + "@next/swc-linux-arm64-gnu": "16.3.8", + "@next/swc-linux-arm64-musl": "16.3.8", + "@next/swc-linux-x64-gnu": "16.3.8", + "@next/swc-linux-x64-musl": "16.3.8", + "@next/swc-win32-arm64-msvc": "16.3.8", + "@next/swc-win32-x64-msvc": "16.3.8", "sharp": "^0.35.4" }, "peerDependencies": { @@ -14311,9 +14311,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -14321,6 +14321,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/punycode": { @@ -15027,9 +15031,9 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.35.4", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", - "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -15044,31 +15048,31 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.4", - "@img/sharp-darwin-x64": "0.35.4", - "@img/sharp-freebsd-wasm32": "0.35.4", - "@img/sharp-libvips-darwin-arm64": "1.3.3", - "@img/sharp-libvips-darwin-x64": "1.3.3", - "@img/sharp-libvips-linux-arm": "1.3.3", - "@img/sharp-libvips-linux-arm64": "1.3.3", - "@img/sharp-libvips-linux-ppc64": "1.3.3", - "@img/sharp-libvips-linux-riscv64": "1.3.3", - "@img/sharp-libvips-linux-s390x": "1.3.3", - "@img/sharp-libvips-linux-x64": "1.3.3", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", - "@img/sharp-libvips-linuxmusl-x64": "1.3.3", - "@img/sharp-linux-arm": "0.35.4", - "@img/sharp-linux-arm64": "0.35.4", - "@img/sharp-linux-ppc64": "0.35.4", - "@img/sharp-linux-riscv64": "0.35.4", - "@img/sharp-linux-s390x": "0.35.4", - "@img/sharp-linux-x64": "0.35.4", - "@img/sharp-linuxmusl-arm64": "0.35.4", - "@img/sharp-linuxmusl-x64": "0.35.4", - "@img/sharp-webcontainers-wasm32": "0.35.4", - "@img/sharp-win32-arm64": "0.35.4", - "@img/sharp-win32-ia32": "0.35.4", - "@img/sharp-win32-x64": "0.35.4" + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" }, "peerDependenciesMeta": { "@types/node": { @@ -15189,9 +15193,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" diff --git a/package.json b/package.json index 76d6ec270..057f6b4a9 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@modelcontextprotocol/sdk": "^1.30.1", "brace-expansion": "^5.0.12", "eslint": "^9.39.5", - "next": "^16.3.3", + "next": "16.3.8", "turbo": "^2.11.3", "typescript": "6.0.3", "undici": "^8.11.0", @@ -33,7 +33,7 @@ "typescript": "6.0.3", "react": "^19", "react-dom": "^19", - "next": "^16.3.3", + "next": "16.3.8", "nth-check": "^2.0.1", "postcss": "^8.5.18", "@workflow/world-local": { @@ -51,11 +51,14 @@ "vitest": "^4.1.11", "picomatch": "^4.0.5", "protobufjs": "^7.6.5", - "sharp": "^0.35.4", + "sharp": "^0.35.5", + "proxy-addr": "^2.0.8", + "source-map-js": "^1.2.2", "@hono/node-server": "^2.0.5", "nanoid@^3.0.0": "3.3.18", "@workflow/core": { - "nanoid": "5.1.16" + "nanoid": "5.1.16", + "devalue": "5.9.3" }, "minimatch@3.1.5": { "brace-expansion": "^1.1.17" diff --git a/tests/unit/test_dependency_review_workflow.py b/tests/unit/test_dependency_review_workflow.py index 58b8f9cfb..d096188f3 100644 --- a/tests/unit/test_dependency_review_workflow.py +++ b/tests/unit/test_dependency_review_workflow.py @@ -1,10 +1,17 @@ from __future__ import annotations +import json +import os +import subprocess +import sys from pathlib import Path +import pytest import yaml -WORKFLOW_PATH = Path(__file__).resolve().parents[2] / ".github/workflows/dependency-review.yml" +WORKFLOW_PATH = ( + Path(__file__).resolve().parents[2] / ".github/workflows/dependency-review.yml" +) def _load_workflow() -> dict: @@ -81,6 +88,106 @@ def test_dependency_review_submits_pr_head_snapshot_and_retries_warnings() -> No assert "steps.dependency-changes.outputs.changed == 'true'" in review["if"] assert review["with"]["retry-on-snapshot-warnings"] is True assert review["with"]["retry-on-snapshot-warnings-timeout"] == 300 + assert review["with"]["base-ref"] == "${{ github.event.pull_request.base.sha }}" + assert review["with"]["head-ref"] == "${{ github.event.pull_request.head.sha }}" - no_delta = next(step for step in steps if step["name"] == "Record no dependency delta") + no_delta = next( + step for step in steps if step["name"] == "Record no dependency delta" + ) assert "steps.dependency-changes.outputs.changed == 'false'" in no_delta["if"] + + +def test_numpy_remains_subject_to_the_unchanged_license_policy() -> None: + steps = _load_workflow()["jobs"]["dependency-review"]["steps"] + review = next(step for step in steps if step["name"] == "Dependency Review") + settings = review["with"] + + # NumPy's AND expression legitimately fails because Zlib is not approved. + # Passing the latest graph delta must not become a package or license bypass. + assert {item.strip() for item in settings["allow-licenses"].split(",")} == { + "MIT", + "Apache-2.0", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Python-2.0", + "BlueOak-1.0.0", + "MPL-2.0", + "CC0-1.0", + "0BSD", + } + exclusions = settings["allow-dependencies-licenses"].split(",") + assert not any( + item.strip().split("@", 1)[0] == "pkg:pypi/numpy" for item in exclusions + ) + assert settings.get("license-check", True) is True + assert settings.get("warn-only", False) is False + assert "continue-on-error" not in review + assert review["uses"] == ( + "actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294" + ) + + +@pytest.mark.parametrize( + "review_outcome,raw_output", [("failure", None), ("success", "")] +) +def test_dependency_review_evidence_preserves_composite_license_and_unknown_outputs( + tmp_path: Path, review_outcome: str, raw_output: str | None +) -> None: + steps = _load_workflow()["jobs"]["dependency-review"]["steps"] + review = next(step for step in steps if step["name"] == "Dependency Review") + record = next( + step for step in steps if step["name"] == "Record dependency review evidence" + ) + upload = next( + step for step in steps if step["name"] == "Upload dependency review evidence" + ) + assert review["id"] == "review" + for step in (record, upload): + assert "always()" in step["if"] + assert "github.event_name == 'pull_request'" in step["if"] + assert "steps.dependency-changes.outputs.changed == 'true'" in step["if"] + assert record["env"]["INVALID_LICENSE_CHANGES"] == ( + "${{ steps.review.outputs.invalid-license-changes }}" + ) + assert "github.event.pull_request.head.sha" in upload["with"]["name"] + assert upload["with"]["path"] == "dependency-review-evidence/review.json" + assert upload["with"]["if-no-files-found"] == "error" + + expression = "BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0" + finding = {"name": "numpy", "version": "2.5.4", "license": expression} + # Keep duplicated occurrences, the full AND expression, and hostile text as data. + invalid = json.dumps( + {"forbidden": [finding, finding], "unresolved": [], "unlicensed": []} + ) + dependencies = json.dumps([finding, {"name": "$(touch injected)"}]) + environment = { + # A failing subprocess may print its environment in pytest's traceback. + # Pass only this test's inputs, never credentials from the caller. + "PATH": os.pathsep.join((str(Path(sys.executable).parent), os.defpath)), + "BASE_SHA": "a" * 40, + "HEAD_SHA": "b" * 40, + "GITHUB_RUN_ID": "123", + "GITHUB_RUN_ATTEMPT": "2", + "REVIEW_OUTCOME": review_outcome, + "DEPENDENCY_CHANGES": dependencies if raw_output is None else raw_output, + "INVALID_LICENSE_CHANGES": invalid if raw_output is None else raw_output, + "VULNERABLE_CHANGES": "[]" if raw_output is None else raw_output, + "DENIED_CHANGES": "", + } + subprocess.run( + ["bash", "-e", "-c", record["run"]], cwd=tmp_path, env=environment, check=True + ) + saved = json.loads((tmp_path / upload["with"]["path"]).read_text()) + assert saved["base_sha"] == "a" * 40 + assert saved["head_sha"] == "b" * 40 + assert saved["run_id"] == "123" + assert saved["run_attempt"] == "2" + assert saved["review_outcome"] == review_outcome + assert saved["raw_outputs"] == { + "dependency-changes": environment["DEPENDENCY_CHANGES"], + "invalid-license-changes": environment["INVALID_LICENSE_CHANGES"], + "vulnerable-changes": environment["VULNERABLE_CHANGES"], + "denied-changes": "", + } + assert not (tmp_path / "injected").exists() diff --git a/tests/unit/test_merge_policy_security_scan_wording.py b/tests/unit/test_merge_policy_security_scan_wording.py index 8bad97279..363779196 100644 --- a/tests/unit/test_merge_policy_security_scan_wording.py +++ b/tests/unit/test_merge_policy_security_scan_wording.py @@ -1,14 +1,105 @@ -import re +import json +import unittest from pathlib import Path +import yaml -def test_gate_2_states_security_jobs_are_completion_gates() -> None: - policy_path = Path(__file__).resolve().parents[2] / "MERGE_POLICY.md" - policy = policy_path.read_text() - assert re.search( - r"gate 2 requires these security scan jobs \(check-runs\) to .*run and complete", - policy, - re.IGNORECASE, - ) - assert "It is not a findings gate" in policy +class ImageGatePolicyTests(unittest.TestCase): + def test_bootstrap_separates_repairs_from_strict_enforcement(self): + root = Path(__file__).resolve().parents[2] + policy = (root / "MERGE_POLICY.md").read_text() + workflow = yaml.safe_load((root / ".github/workflows/security.yml").read_text()) + job = workflow["jobs"]["trivy"] + self.assertFalse(job.get("continue-on-error", False)) + scans = [ + step for step in job["steps"] if "trivy-action@" in step.get("uses", "") + ] + self.assertEqual(len(scans), 2) + for scan in scans: + self.assertEqual(scan["with"]["exit-code"], "0") + self.assertFalse(scan.get("continue-on-error", False)) + self.assertIs(scan["with"]["ignore-unfixed"], False) + self.assertEqual(scan["with"]["severity"], "CRITICAL,HIGH") + self.assertEqual( + scan["with"]["trivyignores"], ".github/trivy-no-exceptions.txt" + ) + sarif = next(scan for scan in scans if scan["with"]["format"] == "sarif") + self.assertIs(sarif["with"]["limit-severities-for-sarif"], True) + self.assertIn("scan-completion gate", policy) + self.assertIn("Parent #2396 stays open", policy) + self.assertIn("no accepted baseline", policy) + self.assertIn("separately reviewed", policy) + self.assertIn("No exceptions are accepted", policy) + self.assertIn("Do not automatically demote `trivy`", policy) + exceptions = (root / ".github/trivy-no-exceptions.txt").read_text() + self.assertFalse( + [ + line + for line in exceptions.splitlines() + if line.strip() and not line.startswith("#") + ] + ) + + def test_failed_scan_evidence_is_retained(self): + root = Path(__file__).resolve().parents[2] + workflow = yaml.safe_load((root / ".github/workflows/security.yml").read_text()) + steps = workflow["jobs"]["trivy"]["steps"] + self.assertEqual( + steps[0]["with"]["ref"], + "${{ github.event.pull_request.head.sha || github.sha }}", + ) + build = next( + step for step in steps if step.get("name") == "Build image for scanning" + ) + self.assertIn( + "docker image inspect eventrelay:test > image-identity.json", build["run"] + ) + self.assertIn("git rev-parse HEAD > image-source-commit.txt", build["run"]) + upload = next(step for step in steps if "upload-sarif@" in step.get("uses", "")) + self.assertEqual( + upload["if"], "always() && hashFiles('trivy-results.sarif') != ''" + ) + retention = next( + step + for step in steps + if step.get("name") == "Retain exact image scan evidence" + ) + self.assertEqual(retention["if"], "always()") + self.assertEqual(retention["with"]["if-no-files-found"], "error") + self.assertEqual( + set(retention["with"]["path"].splitlines()), + { + "image-identity.json", + "image-source-commit.txt", + "trivy-results.sarif", + }, + ) + + def test_scanner_reported_runtime_dependency_floors_are_locked(self): + root = Path(__file__).resolve().parents[2] + manifest = json.loads((root / "package.json").read_text()) + lock = json.loads((root / "package-lock.json").read_text()) + packages = lock["packages"] + + self.assertEqual(manifest["overrides"]["@workflow/core"]["devalue"], "5.9.3") + self.assertEqual(manifest["overrides"]["proxy-addr"], "^2.0.8") + self.assertEqual(manifest["overrides"]["source-map-js"], "^1.2.2") + self.assertEqual(packages["node_modules/devalue"]["version"], "5.9.3") + self.assertEqual(packages["node_modules/proxy-addr"]["version"], "2.0.8") + self.assertEqual(packages["node_modules/source-map-js"]["version"], "1.2.2") + + def test_image_build_constrains_python_and_runtime_npm(self): + root = Path(__file__).resolve().parents[2] + constraints = (root / "constraints.txt").read_text() + dockerfile = (root / "Dockerfile").read_text() + + self.assertIn("urllib3>=2.8.0", constraints) + self.assertIn('"setuptools>=80.10.2"', dockerfile) + self.assertIn('"wheel>=0.46.2"', dockerfile) + self.assertIn("npm install --global npm@12.2.0", dockerfile) + self.assertIn("rm -rf /usr/local/lib/python3.11/site-packages/*", dockerfile) + + +if __name__ == "__main__": + unittest.main()