From 0c437cd6e84fa4b0cb91f6faa16a6eaf1562ab89 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 16:44:03 +0000 Subject: [PATCH 1/4] feat(ci): audit every dependency tree for known vulnerabilities govulncheck over both Go modules, npm audit over every lockfile, pip-audit over the Python packages, cargo audit over the Rust client and osv-scanner over the Java client each fail their ecosystem's job in the pull-request workflow (both tiers, gated by Changed areas like the job), in the CircleCI jobs that gate develop, main and release tags, and nightly over develop. The tools are pinned and SHA-256 verified by scripts/download-sca-tools.sh; make audit runs all five. Dependabot opens weekly grouped updates against develop, and the release procedure requires a clean audit before the release branch and before the tag, with a CISA KEV check for findings that cannot be fixed. Co-Authored-By: jason.han --- .circleci/config.yml | 28 +++++ .github/dependabot.yml | 100 ++++++++++++++++++ .github/workflows/nightly.yml | 53 ++++++++++ .github/workflows/pr.yml | 33 ++++++ CONTRIBUTING.md | 31 ++++++ Makefile | 33 +++++- changes/unreleased/dependency-audits.added.md | 1 + docs/project/nightly.md | 8 ++ docs/project/releasing.md | 27 ++++- scripts/ci-changed-areas-test.sh | 1 + scripts/ci-changed-areas.sh | 2 +- scripts/download-sca-tools.sh | 99 +++++++++++++++++ 12 files changed, 413 insertions(+), 3 deletions(-) create mode 100644 .github/dependabot.yml create mode 100644 changes/unreleased/dependency-audits.added.md create mode 100755 scripts/download-sca-tools.sh diff --git a/.circleci/config.yml b/.circleci/config.yml index 5e191c8f9c..d1e412e73b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -423,6 +423,10 @@ jobs: command: make lint no_output_timeout: 30m + - run: + name: Check Go dependencies for known vulnerabilities + command: make vulncheck + - run: name: Check grpc-go stays out of production code command: ./scripts/check-grpc-imports.sh @@ -1217,6 +1221,12 @@ jobs: pip install --only-binary :all: jupyter_client==8.10.0 build==1.6.1 make jupyter-kernel-install + - run: + name: Audit the Python packages' dependencies + command: | + scripts/download-sca-tools.sh pip-audit + make audit-python + # The integration tests connect to a service on the standard port with # auto_start=False, which is the explicit opt-in to one the client does not # manage; without it they skip, exercising nothing, so one runs here for @@ -1328,6 +1338,12 @@ jobs: name: Run Rust clippy command: cd client/rust && cargo clippy --all-targets -- -D warnings + - run: + name: Audit the Rust client's dependencies + command: | + scripts/download-sca-tools.sh cargo-audit + make audit-rust + - run: name: Run Rust tests command: | @@ -1486,6 +1502,12 @@ jobs: paths: - ~/.m2/repository + - run: + name: Audit the Java client's dependencies + command: | + scripts/download-sca-tools.sh osv-scanner + make audit-java + # opensysml.requireService turns "no binary, so skip" into a failure: the # workspace carries one, so its absence is the bug, not a pass. `install` # rather than `test`, so the sources and javadoc jars a release needs are @@ -1556,6 +1578,12 @@ jobs: npm install --ignore-scripts --no-audit --no-fund git diff --exit-code package-lock.json + # Every npm lockfile, the editors' included: this job runs on tags too, where + # editors-node-test does not, and the audit needs only the lockfiles. + - run: + name: Audit every npm lockfile's dependencies + command: make audit-npm + - run: name: Build the package command: | diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000000..0a44e21be1 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,100 @@ +# Weekly dependency updates, one grouped pull request per ecosystem and directory, +# against develop. The pull requests run the same CI as any other and a maintainer +# merges them (CONTRIBUTING.md § Dependabot pull requests). Dependabot alerts and +# security updates are repository settings, not configured here. +version: 2 +updates: + - package-ecosystem: gomod + directories: + - / + - /tools + schedule: + interval: weekly + day: monday + target-branch: develop + commit-message: + prefix: chore + include: scope + groups: + go-minor-and-patch: + patterns: ["*"] + update-types: [minor, patch] + + - package-ecosystem: npm + directories: + - /client/node + - /packaging/npm/sysml-mcp + - /editors/vscode + - /editors/jupyterlab + - /editors/syson/frontend + - /scripts/asciidoc + schedule: + interval: weekly + day: monday + target-branch: develop + commit-message: + prefix: chore + include: scope + groups: + npm-minor-and-patch: + patterns: ["*"] + update-types: [minor, patch] + + - package-ecosystem: pip + directories: + - /client/python + - /client/jupyter-kernel + - /packaging/pypi-pysysml + schedule: + interval: weekly + day: monday + target-branch: develop + commit-message: + prefix: chore + include: scope + groups: + pip-minor-and-patch: + patterns: ["*"] + update-types: [minor, patch] + + - package-ecosystem: cargo + directory: /client/rust + schedule: + interval: weekly + day: monday + target-branch: develop + commit-message: + prefix: chore + include: scope + groups: + cargo-minor-and-patch: + patterns: ["*"] + update-types: [minor, patch] + + - package-ecosystem: maven + directory: /client/java + schedule: + interval: weekly + day: monday + target-branch: develop + commit-message: + prefix: chore + include: scope + groups: + maven-minor-and-patch: + patterns: ["*"] + update-types: [minor, patch] + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + target-branch: develop + commit-message: + prefix: chore + include: scope + groups: + actions-minor-and-patch: + patterns: ["*"] + update-types: [minor, patch] diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 8d4087d15d..10f9595394 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -126,6 +126,59 @@ jobs: echo "Publishing $sha as $night and $TAG (previously ${published:-unpublished})." echo "publish=true" >> "$GITHUB_OUTPUT" + audit: + name: Audit the dependencies + # Independent of the snapshot: an advisory published against an unchanged develop + # fails this job the next night whether or not anything new was built. + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + steps: + - name: Check out develop + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + ref: ${{ env.BRANCH }} + + - name: Set up Go + uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0 + with: + go-version-file: go.mod + cache: true + + - name: Set up Node.js + uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0 + with: + node-version: '22' + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.11' + + - name: Provision the audit tools + run: scripts/download-sca-tools.sh + + # Each audit runs even when an earlier one failed, so one night reports every tree. + - name: Check Go dependencies for known vulnerabilities + run: make vulncheck + + - name: Audit every npm lockfile's dependencies + if: ${{ !cancelled() }} + run: make audit-npm + + - name: Audit the Python packages' dependencies + if: ${{ !cancelled() }} + run: make audit-python + + - name: Audit the Rust client's dependencies + if: ${{ !cancelled() }} + run: make audit-rust + + - name: Audit the Java client's dependencies + if: ${{ !cancelled() }} + run: make audit-java + publish: name: Build and publish the snapshot needs: select diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 1645eb4bf1..74cd8e05ff 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -313,6 +313,9 @@ jobs: make build-mcp scripts/npm-mcp-test.sh + - name: Audit the npm runner's and Asciidoctor's dependencies + run: make audit-npm NPM_LOCK_DIRS="packaging/npm/sysml-mcp scripts/asciidoc" + - name: Provision the pinned corpora if: needs.changes.outputs.tier == 'full' uses: ./.github/actions/corpora @@ -331,6 +334,9 @@ jobs: go vet ./... go vet -C tools ./... + - name: Check Go dependencies for known vulnerabilities + run: make vulncheck + - name: Vet and test the RE sysml build (-tags sysml_re) run: | go vet -tags sysml_re ./cmd/sysml/... ./internal/frontend/repl/... @@ -927,6 +933,9 @@ jobs: npm install --ignore-scripts --no-audit --no-fund git diff --exit-code package-lock.json + - name: Audit the extension's dependencies + run: make audit-npm NPM_LOCK_DIRS=editors/vscode + # Type-check, bundle and package. The generated TextMate grammars are # gated by go test ./editors/... in the job above. - name: Package the VS Code extension @@ -973,6 +982,9 @@ jobs: - name: Install JupyterLab run: pip install --only-binary ":all:" jupyterlab==4.6.4 + - name: Audit the extension's dependencies + run: make audit-npm NPM_LOCK_DIRS=editors/jupyterlab + - name: Test the tokenizer run: make jupyterlab-test @@ -1168,6 +1180,9 @@ jobs: npm install --ignore-scripts --no-audit --no-fund git diff --exit-code package-lock.json + - name: Audit the client's dependencies + run: make audit-npm NPM_LOCK_DIRS=client/node + - name: Build, typecheck and lint working-directory: client/node run: | @@ -1284,6 +1299,11 @@ jobs: pip install --only-binary :all: jupyter_client==8.10.0 build==1.6.1 make jupyter-kernel-install + - name: Audit the Python packages' dependencies + run: | + scripts/download-sca-tools.sh pip-audit + make audit-python + # The FMI runner test's FMUs, same pin as the Go gate's. - name: Cache the Reference-FMUs uses: actions/cache@v4 @@ -1384,6 +1404,11 @@ jobs: rustc --version cargo --version + - name: Audit the Rust client's dependencies + run: | + scripts/download-sca-tools.sh cargo-audit + make audit-rust + - name: Check Rust formatting working-directory: client/rust run: cargo fmt --all -- --check @@ -1605,6 +1630,11 @@ jobs: - name: Make the service binary executable run: chmod +x bin/sysml-grpc + - name: Audit the Java client's dependencies + run: | + scripts/download-sca-tools.sh osv-scanner + make audit-java + # opensysml.requireService turns "no binary, so skip" into a failure, and # `install` builds the sources and javadoc jars a release needs on every commit. - name: Test and install the client @@ -1676,6 +1706,9 @@ jobs: npm test npm run build + - name: Audit the frontend's dependencies + run: make audit-npm NPM_LOCK_DIRS=editors/syson/frontend + conformance-pkg: name: Conformance suite needs: changes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4f2e7ac8c9..b9cfad92a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -303,6 +303,36 @@ tier; `CI_TIER=quick` keeps the clients and docs to their own paths). A path no turns every area on, so a new directory is over-tested rather than untested — teach the script about it, and add a case to `scripts/ci-changed-areas-test.sh`, which the same job runs. +**Dependency audits** run in both tiers, each in the job whose ecosystem it covers and gated +by the same `Changed areas` output as that job: `make vulncheck` (govulncheck over both Go +modules, failing on a vulnerability the code reaches) in the Go static job, `make audit-npm` +(`npm audit --omit=dev --audit-level=high` over every `package-lock.json`) in each Node job, +`make audit-python` (`pip-audit --strict` over `client/python`, `client/jupyter-kernel` and +`packaging/pypi-pysysml`) in the Python job, `make audit-rust` (`cargo audit`) in the Rust job +and `make audit-java` (osv-scanner over the Maven trees) in the Java job. The tools come pinned +and SHA-256 verified from `scripts/download-sca-tools.sh` (`make sca-tools`); `make audit` runs +all five locally. A finding fails the job: fix it by bumping the dependency (a Dependabot pull +request may already exist), never by lowering a level or ignoring an advisory. The same audits +run every night over `develop` ([nightly snapshots](docs/project/nightly.md)), so an advisory +published against an unchanged tree is seen within a day, and before a release +([releasing](docs/project/releasing.md#before-tagging)). The npm level is `high` because +`editors/jupyterlab`'s pinned JupyterLab build tooling carries moderate advisories with no +upstream fix; lower it to `moderate` when `npm audit --omit=dev --audit-level=moderate` is clean +in every lockfile directory. + +### Dependabot pull requests + +[`.github/dependabot.yml`](.github/dependabot.yml) opens one grouped pull request a week per +ecosystem and directory (Go modules, every npm lockfile, the Python packages, the Rust and Java +clients, and the GitHub Actions the workflows pin), against `develop`, with minor and patch +updates in one group and a Conventional Commits title (`chore(deps): …`). Such a pull request is +handled like any other: it opens ready for review, so the full tier runs on it with `Changed +areas` scoping the jobs to the lockfile it touched, Devin Review and the dependency audits run on +it, and a maintainer merges it once green; rebase it with a `@dependabot rebase` comment, never +by hand. A security update Dependabot opens outside the weekly schedule follows the same path. +Dependabot alerts and security updates are repository settings a maintainer switches on; the +configuration file only schedules version updates. + ### CircleCI `.circleci/config.yml` runs after a merge and on tags, never on a pull request branch: @@ -331,6 +361,7 @@ the pull request is not a draft), which requires: - [ ] No race conditions - [ ] Code formatted (`gofmt`) - [ ] Static analysis clean (`make lint`: staticcheck and gosec) +- [ ] Dependency audits clean (`make audit`: govulncheck, npm audit, pip-audit, cargo audit, osv-scanner) - [ ] OMG corpus gates run (not skipped) and match their expectations - [ ] Protobuf schema lints and is wire-compatible with the base branch - [ ] Documentation checks pass (`make docs-check`, `make man-check`) and the site builds diff --git a/Makefile b/Makefile index 83ebf79166..fb32f8e295 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all build docker extension-libraries-check extension-libraries-sync build-sysml build-re build-wasm-re build-lsp build-grpc build-mcp build-engine build-core build-syntax build-sysml-wasm build-release-wasm build-wasm build-wasm-wasip1 build-wasm-js wasm-check static-check windows-versioninfo-check man man-check install-tree pgo-profile conformance conformance-pkg conformance-mcp conformance-rust conformance-julia conformance-matlab test test-shard coverage coverage-shard coverage-merge lint clean install help ontology-table ontology-table-check metaclass-graph metaclass-graph-check python-metamodel python-metamodel-check fuml-expected python-test python-coverage scripts-coverage node-coverage vscode-coverage jupyterlab-coverage mcp-npm-coverage python-install jupyter-kernel-install jupyter-kernel-test proto proto-buf python-proto proto-ts proto-rust proto-lint proto-breaking jupyterlab-build jupyterlab-test jupyterlab-install vscode-grammar vscode-build vscode-package docs docs-install docs-serve docs-engine-assets docs-landing-assets docs-counts diagnostics-catalogue diagnostics-check docs-check changelog-check changelog-render self-model mcp-registry-check +.PHONY: all build docker vulncheck sca-tools audit audit-npm audit-python audit-rust audit-java extension-libraries-check extension-libraries-sync build-sysml build-re build-wasm-re build-lsp build-grpc build-mcp build-engine build-core build-syntax build-sysml-wasm build-release-wasm build-wasm build-wasm-wasip1 build-wasm-js wasm-check static-check windows-versioninfo-check man man-check install-tree pgo-profile conformance conformance-pkg conformance-mcp conformance-rust conformance-julia conformance-matlab test test-shard coverage coverage-shard coverage-merge lint clean install help ontology-table ontology-table-check metaclass-graph metaclass-graph-check python-metamodel python-metamodel-check fuml-expected python-test python-coverage scripts-coverage node-coverage vscode-coverage jupyterlab-coverage mcp-npm-coverage python-install jupyter-kernel-install jupyter-kernel-test proto proto-buf python-proto proto-ts proto-rust proto-lint proto-breaking jupyterlab-build jupyterlab-test jupyterlab-install vscode-grammar vscode-build vscode-package docs docs-install docs-serve docs-engine-assets docs-landing-assets docs-counts diagnostics-catalogue diagnostics-check docs-check changelog-check changelog-render self-model mcp-registry-check # Version information # Only release tags describe a build; the moving `nightly` tag is not a version. @@ -25,6 +25,13 @@ STATICCHECK_VERSION := 2025.1.1 # executor kills it for; the soft limit holds the peak near 2 GiB for ~10% more GC time. STATICCHECK_ENV := GOMEMLIMIT=2GiB GOSEC_VERSION := v2.22.5 +# Dependency-audit tool versions: govulncheck runs through `go run`; the others come +# from scripts/download-sca-tools.sh, pinned and SHA-256 verified there. +GOVULNCHECK_VERSION := v1.9.0 +SCA_BIN := build/sca/bin +PIP_AUDIT := build/sca/venv/bin/pip-audit +# `npm audit` fails on production advisories at this severity and above, in every lockfile directory. +NPM_AUDIT_LEVEL := high BUF_VERSION := v1.57.2 GO_WINRES_VERSION := v0.3.3 @@ -58,6 +65,8 @@ PROTOC_GEN_ES := $(NODE_DIR)/node_modules/.bin/protoc-gen-es VSCODE_DIR := editors/vscode JUPYTERLAB_DIR := editors/jupyterlab MCP_NPM_DIR := packaging/npm/sysml-mcp +NPM_LOCK_DIRS := $(NODE_DIR) $(MCP_NPM_DIR) $(VSCODE_DIR) $(JUPYTERLAB_DIR) editors/syson/frontend scripts/asciidoc +PYTHON_PROJECT_DIRS := $(PYTHON_DIR) $(JUPYTER_KERNEL_DIR) packaging/pypi-pysysml PYTHON ?= python3 # api/proto/buf.gen.python.yaml starts the interpreter this names. export PYTHON @@ -400,6 +409,28 @@ lint: ## Run static analysis (staticcheck + gosec), as CI does module_go_version=$$(go list -C $(TOOLS_DIR) -m -f '{{.GoVersion}}') && GOSECGOVERSION=$$module_go_version go run -C $(TOOLS_DIR) github.com/securego/gosec/v2/cmd/gosec@$(GOSEC_VERSION) -quiet -exclude-generated ./... @echo "✓ Lint passed" +vulncheck: ## Fail on known vulnerabilities reachable from the Go modules (govulncheck), as CI does + go run golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION) ./... + go run -C $(TOOLS_DIR) golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION) ./... + +sca-tools: ## Provision the pinned cargo-audit, osv-scanner and pip-audit under build/sca + ./scripts/download-sca-tools.sh + +audit-npm: ## Fail on $(NPM_AUDIT_LEVEL)+ advisories against the production dependencies of every npm lockfile (NPM_LOCK_DIRS=dir... narrows) + @for dir in $(NPM_LOCK_DIRS); do echo "npm audit $$dir" && npm --prefix "$$dir" audit --omit=dev --audit-level=$(NPM_AUDIT_LEVEL) || exit 1; done + +audit-python: ## Fail on known vulnerabilities in the Python packages' dependencies (pip-audit --strict) + @for dir in $(PYTHON_PROJECT_DIRS); do echo "pip-audit $$dir" && $(PIP_AUDIT) --strict --progress-spinner off "$$dir" || exit 1; done + +audit-rust: ## Fail on RustSec advisories against client/rust/Cargo.lock (cargo audit) + $(SCA_BIN)/cargo-audit audit --file client/rust/Cargo.lock + +audit-java: ## Fail on OSV advisories against the Java client's Maven dependencies (osv-scanner) + $(SCA_BIN)/osv-scanner scan source --recursive client/java + +audit: vulncheck audit-npm audit-python audit-rust audit-java ## Run every dependency audit, as CI and the release procedure do + @echo "✓ Dependency audits passed" + test-short: ## Run Go tests without race detection @echo "Running Go tests without race detection..." go test -v ./... diff --git a/changes/unreleased/dependency-audits.added.md b/changes/unreleased/dependency-audits.added.md new file mode 100644 index 0000000000..fee9151daf --- /dev/null +++ b/changes/unreleased/dependency-audits.added.md @@ -0,0 +1 @@ +- **CI audits every dependency tree for known vulnerabilities and fails on a finding.** `make vulncheck` (govulncheck over both Go modules, reachable vulnerabilities only), `make audit-npm` (`npm audit --omit=dev --audit-level=high` in every `package-lock.json` directory), `make audit-python` (`pip-audit --strict` over the three Python packages), `make audit-rust` (`cargo audit`) and `make audit-java` (osv-scanner over the Maven trees) run in the pull-request workflow's quick and full tiers, in the CircleCI jobs that gate `develop`, `main` and release tags, and every night over `develop`; `make audit` runs all five and the release procedure requires it clean before the release branch and again before the tag, with unfixable findings checked against the CISA Known Exploited Vulnerabilities catalogue. The tools come pinned and SHA-256 verified from `scripts/download-sca-tools.sh`, and `.github/dependabot.yml` opens weekly grouped update pull requests against `develop` for every ecosystem. diff --git a/docs/project/nightly.md b/docs/project/nightly.md index dee2d34d9c..1812e08e97 100644 --- a/docs/project/nightly.md +++ b/docs/project/nightly.md @@ -209,6 +209,14 @@ re-published under its name. ## How it is produced +The same workflow runs the dependency audits over `develop` every night, in a job of its +own that needs no snapshot: `make vulncheck`, `make audit-npm`, `make audit-python`, +`make audit-rust` and `make audit-java`, the five `make audit` runs and every pull request's +CI runs on the paths it changes (see +[CONTRIBUTING.md § CI/CD](../../CONTRIBUTING.md#cicd)). An advisory published against an +unchanged `develop` therefore fails the next night's run, and each audit runs even when an +earlier one failed, so one night reports every tree. + [`.github/workflows/nightly.yml`](../../.github/workflows/nightly.yml) runs at 03:23 UTC and on demand (`workflow_dispatch`, with a `force` input that publishes the same commit again as a new night, under today's date — for instance after the workflow diff --git a/docs/project/releasing.md b/docs/project/releasing.md index 0426dd8ab8..1129746c37 100644 --- a/docs/project/releasing.md +++ b/docs/project/releasing.md @@ -54,10 +54,20 @@ gofmt -l . # must print nothing go build ./... go vet ./... make lint # staticcheck + gosec, as CircleCI runs +make sca-tools audit # govulncheck, npm audit, pip-audit, cargo audit, osv-scanner, as CircleCI runs go test -race -count=1 ./... go test -run TestStdlibConformance ./internal/workspace/libs ``` +`make audit` must report no finding: every advisory it prints is fixed by moving the +dependency before the tag, since the same audits run in the release workflow's jobs and fail +it. A finding no release of the dependency fixes yet is looked up in the +[CISA Known Exploited Vulnerabilities catalogue](https://www.cisa.gov/known-exploited-vulnerabilities-catalog): +a listed vulnerability blocks the release until the dependency is replaced or the code stops +using it; for an unlisted one, record the advisory, the catalogue check and why the release +proceeds in the release pull request, and open an issue to track the fix. The audit level and +the ignore list are not changed to make a release pass. + Run the Python client the way CircleCI's `python-test` job does, since a release gates on the same suite: @@ -156,10 +166,19 @@ Day-to-day work merges into `develop`; `main` carries releases only (see [CONTRIBUTING.md § Branches](../../CONTRIBUTING.md#branches)). A release is a branch that moves the integration state onto `main`: -1. Cut `release/x.y.z` from `develop`: +1. Audit the dependencies of `develop` before cutting the branch, so the release branch + starts without a known vulnerability, and treat each finding as + [Before tagging](#before-tagging) describes (fix, or check the CISA KEV catalogue and + record the decision in the release pull request): ```bash git checkout develop && git pull + make sca-tools audit + ``` + + Then cut `release/x.y.z`: + + ```bash git checkout -b release/0.0.5 ``` @@ -274,8 +293,14 @@ The tag is the version: CircleCI passes `CIRCLE_TAG` to the build as `VERSION`, so `sysml --version` reports it. Rehearse first — see [Rehearsing the release](#rehearsing-the-release). +Audit the dependencies once more on the commit being tagged — advisories published since +the branch was cut count — and tag only when `make audit` is clean or every remaining +finding has its CISA KEV check recorded in the release pull request, as +[Before tagging](#before-tagging) describes: + ```bash git checkout main && git pull +make sca-tools audit git tag -a v0.0.5 -m "v0.0.5" git push origin v0.0.5 ``` diff --git a/scripts/ci-changed-areas-test.sh b/scripts/ci-changed-areas-test.sh index 12ec09329b..03eb539680 100755 --- a/scripts/ci-changed-areas-test.sh +++ b/scripts/ci-changed-areas-test.sh @@ -97,6 +97,7 @@ case_ install-script-windows docs,go,java,julia,jupyterlab,matlab,mdk,node,pytho case_ action docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vscode action.yml case_ conformance docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vscode conformance/scenarios/01-server-info.json case_ workflow docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vscode .github/workflows/pr.yml +case_ dependabot docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vscode .github/dependabot.yml # The Go hygiene suite caps every tracked fixture and test file, so these run it too. case_ client-fixture go,python client/python/tests/testdata/large.json case_ client-golden go,node client/node/test/render.golden.json diff --git a/scripts/ci-changed-areas.sh b/scripts/ci-changed-areas.sh index bc2d81a085..be90563ee0 100755 --- a/scripts/ci-changed-areas.sh +++ b/scripts/ci-changed-areas.sh @@ -22,7 +22,7 @@ changed=$(git diff --name-only "$(git merge-base "$base" "$head")" "$head") # The editors' grammar generator and its committed outputs (the VS Code # grammars and the JupyterLab syntax table) are here too: the test that holds # them together is a Go test, run by the Go suite. -service_pattern='^(api/proto/|cmd/|internal/|tools/|client/opensysml/|client/release-digests\.json$|conformance/|tests/|scripts/|examples/|editors/vscode/tools/|editors/vscode/syntaxes/|editors/jupyterlab/src/syntax\.json$|Makefile$|go\.mod$|go\.sum$|install\.sh$|install\.ps1$|action\.yml$|\.github/workflows/|\.circleci/)' +service_pattern='^(api/proto/|cmd/|internal/|tools/|client/opensysml/|client/release-digests\.json$|conformance/|tests/|scripts/|examples/|editors/vscode/tools/|editors/vscode/syntaxes/|editors/jupyterlab/src/syntax\.json$|Makefile$|go\.mod$|go\.sum$|install\.sh$|install\.ps1$|action\.yml$|\.github/workflows/|\.github/dependabot\.yml$|\.circleci/)' docs_pattern='^(docs/|overrides/|packaging/man/|mkdocs\.yml$|README\.md$|CHANGELOG\.md$|CONTRIBUTING\.md$|AGENTS\.md$|.*\.md$)' node_pattern='^client/node/' python_pattern='^client/python/' diff --git a/scripts/download-sca-tools.sh b/scripts/download-sca-tools.sh new file mode 100755 index 0000000000..e24fdbe8f7 --- /dev/null +++ b/scripts/download-sca-tools.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash +# Provision the dependency-audit tools `make audit` runs into build/sca/: cargo-audit +# (client/rust), osv-scanner (client/java's Maven trees) and pip-audit (the Python +# packages, in a venv). govulncheck and npm audit need nothing from here: `make vulncheck` +# runs govulncheck through `go run` at a pinned version, and npm ships `npm audit`. +# Every download is pinned and SHA-256 verified; a tool already present at the pinned +# version is left alone. Pass tool names to provision a subset (cargo-audit, osv-scanner, +# pip-audit); with no arguments all three are provisioned. +# Needs: curl, tar, sha256sum, and python3 with venv for pip-audit. +set -euo pipefail +HTTPS_ONLY="=https" + +CARGO_AUDIT_VERSION="0.22.2" +CARGO_AUDIT_SHA256_LINUX_X86_64="7fb9497f8594b389e5fce5ef9b92db08432996895b2e0c5a0167a69ed445c428" +CARGO_AUDIT_SHA256_LINUX_AARCH64="c6603814ddaa45e51263dafd31c0ac98808f688d26f7395804f9670b0fd599dd" +CARGO_AUDIT_SHA256_DARWIN_X86_64="847831323de932155b226ab60ee4a180e13e5d007a019f0d4b7b4d89a6de2ab2" +CARGO_AUDIT_SHA256_DARWIN_AARCH64="ec7ca4263769593df4d909be85b94a6b79efa2897be5d2bb8ebd516e823175af" +OSV_SCANNER_VERSION="2.7.0" +OSV_SCANNER_SHA256_LINUX_X86_64="160c6253e2922e9e0a6a9771c5c4c40a4b3c6090b34a3cdd5e80c96cc2e4218b" +OSV_SCANNER_SHA256_LINUX_AARCH64="4061ef4c021ed3c47f203b818f337e351336cc5cd67c0274fc36bd1dfc728dd7" +OSV_SCANNER_SHA256_DARWIN_X86_64="e82c5df7ab0a42dff29a1cd0660711447b56b4c0483ff66ad8634df10fd81a45" +OSV_SCANNER_SHA256_DARWIN_AARCH64="d4926ef8da134332543be276dff157e4c170f99ad91fc48e89448a8816bdf70b" +PIP_AUDIT_VERSION="2.10.1" + +root=$(cd "$(dirname "$0")/.." && pwd) +dest="$root/build/sca" +bin="$dest/bin" +venv="$dest/venv" +mkdir -p "$bin" + +os=$(uname -s) +arch=$(uname -m) +case "$os/$arch" in + Linux/x86_64) platform=LINUX_X86_64; cargo_target=x86_64-unknown-linux-musl; osv_asset=linux_amd64 ;; + Linux/aarch64 | Linux/arm64) platform=LINUX_AARCH64; cargo_target=aarch64-unknown-linux-gnu; osv_asset=linux_arm64 ;; + Darwin/x86_64) platform=DARWIN_X86_64; cargo_target=x86_64-apple-darwin; osv_asset=darwin_amd64 ;; + Darwin/arm64) platform=DARWIN_AARCH64; cargo_target=aarch64-apple-darwin; osv_asset=darwin_arm64 ;; + *) echo "download-sca-tools.sh: no pinned cargo-audit/osv-scanner build for $os/$arch" >&2; exit 1 ;; +esac + +fetch() { # fetch + curl -fsSL --proto "$HTTPS_ONLY" --proto-redir "$HTTPS_ONLY" -o "$3" "$1" + echo "$2 $3" | sha256sum --check --strict --quiet +} + +install_cargo_audit() { + if [[ -x "$bin/cargo-audit" ]] && [[ "$("$bin/cargo-audit" --version 2>/dev/null)" == "cargo-audit $CARGO_AUDIT_VERSION" ]]; then + echo "cargo-audit $CARGO_AUDIT_VERSION already installed" + return + fi + echo "Installing cargo-audit $CARGO_AUDIT_VERSION ..." + local sha asset tmp + sha=CARGO_AUDIT_SHA256_$platform + asset="cargo-audit-$cargo_target-v$CARGO_AUDIT_VERSION" + tmp=$(mktemp -d) + fetch "https://github.com/rustsec/rustsec/releases/download/cargo-audit/v$CARGO_AUDIT_VERSION/$asset.tgz" "${!sha}" "$tmp/$asset.tgz" + tar -xzf "$tmp/$asset.tgz" -C "$tmp" "$asset/cargo-audit" + install -m 0755 "$tmp/$asset/cargo-audit" "$bin/cargo-audit" + rm -rf "$tmp" + [[ "$("$bin/cargo-audit" --version)" == "cargo-audit $CARGO_AUDIT_VERSION" ]] +} + +install_osv_scanner() { + if [[ -x "$bin/osv-scanner" ]] && "$bin/osv-scanner" --version 2>/dev/null | grep -qx "osv-scanner version: $OSV_SCANNER_VERSION"; then + echo "osv-scanner $OSV_SCANNER_VERSION already installed" + return + fi + echo "Installing osv-scanner $OSV_SCANNER_VERSION ..." + local sha tmp + sha=OSV_SCANNER_SHA256_$platform + tmp=$(mktemp) + fetch "https://github.com/google/osv-scanner/releases/download/v$OSV_SCANNER_VERSION/osv-scanner_$osv_asset" "${!sha}" "$tmp" + install -m 0755 "$tmp" "$bin/osv-scanner" + rm -f "$tmp" + "$bin/osv-scanner" --version | grep -qx "osv-scanner version: $OSV_SCANNER_VERSION" +} + +install_pip_audit() { + if [[ -x "$venv/bin/pip-audit" ]] && [[ "$("$venv/bin/pip-audit" --version 2>/dev/null)" == "pip-audit $PIP_AUDIT_VERSION" ]]; then + echo "pip-audit $PIP_AUDIT_VERSION already installed" + return + fi + echo "Installing pip-audit $PIP_AUDIT_VERSION ..." + python3 -m venv "$venv" + "$venv/bin/python" -m pip install --quiet --only-binary :all: "pip-audit==$PIP_AUDIT_VERSION" + [[ "$("$venv/bin/pip-audit" --version)" == "pip-audit $PIP_AUDIT_VERSION" ]] +} + +tools=("$@") +[[ ${#tools[@]} -eq 0 ]] && tools=(cargo-audit osv-scanner pip-audit) +for tool in "${tools[@]}"; do + case "$tool" in + cargo-audit) install_cargo_audit ;; + osv-scanner) install_osv_scanner ;; + pip-audit) install_pip_audit ;; + *) echo "download-sca-tools.sh: unknown tool $tool (cargo-audit, osv-scanner, pip-audit)" >&2; exit 2 ;; + esac +done +echo "SCA tools under $dest" From 3c6aeaf2991ab762c4cd1646f631fde5ebc175da Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:23:21 +0000 Subject: [PATCH 2/4] feat(ci): audit the SysON and MDK Maven trees and the docs toolchain pins make audit-java now scans a CycloneDX SBOM of each Maven tree (client/java, editors/syson, editors/mdk) with osv-scanner, since osv-scanner's own pom resolver cannot follow reactor modules; the SysON and MDK jobs and the nightly audit run it, and Dependabot watches the two editor trees. make audit-python also audits docs-requirements.txt. Co-Authored-By: jason.han --- .circleci/config.yml | 14 +++++++++++++- .github/dependabot.yml | 5 ++++- .github/workflows/nightly.yml | 11 +++++++++++ .github/workflows/pr.yml | 12 +++++++++++- CONTRIBUTING.md | 12 +++++++----- Makefile | 11 ++++++++--- changes/unreleased/dependency-audits.added.md | 2 +- 7 files changed, 55 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bb56935dda..660598e562 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1506,7 +1506,7 @@ jobs: name: Audit the Java client's dependencies command: | scripts/download-sca-tools.sh osv-scanner - make audit-java + make audit-java JAVA_PROJECT_DIRS=client/java # opensysml.requireService turns "no binary, so skip" into a failure: the # workspace carries one, so its absence is the bug, not a pass. `install` @@ -1710,6 +1710,12 @@ jobs: name: Test and install the SysON backend command: mvn -B -f editors/syson/pom.xml install -Dopensysml.requireService=true + - run: + name: Audit the backend's dependencies + command: | + scripts/download-sca-tools.sh osv-scanner + make audit-java JAVA_PROJECT_DIRS=editors/syson + - save_cache: key: maven-syson-v1-{{ checksum "client/java/pom.xml" }}-{{ checksum "client/java/opensysml-client/pom.xml" }}-{{ checksum "editors/syson/pom.xml" }}-{{ checksum "editors/syson/backend/pom.xml" }} paths: @@ -1751,6 +1757,12 @@ jobs: name: Build and test the plugin command: mvn -B -f editors/mdk/pom.xml verify -Dopensysml.requireService=true + - run: + name: Audit the plugin's dependencies + command: | + scripts/download-sca-tools.sh osv-scanner + make audit-java JAVA_PROJECT_DIRS=editors/mdk + - save_cache: key: maven-mdk-v1-{{ checksum "client/java/pom.xml" }}-{{ checksum "client/java/opensysml-client/pom.xml" }}-{{ checksum "editors/mdk/pom.xml" }}-{{ checksum "editors/mdk/plugin/pom.xml" }} paths: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 0a44e21be1..b54bf6c676 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -72,7 +72,10 @@ updates: update-types: [minor, patch] - package-ecosystem: maven - directory: /client/java + directories: + - /client/java + - /editors/syson + - /editors/mdk schedule: interval: weekly day: monday diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 10f9595394..77a61d90c9 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -156,6 +156,17 @@ jobs: with: python-version: '3.11' + - name: Set up Java + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + with: + distribution: temurin + java-version: '17' + cache: maven + + # The editors depend on the Java client at the tree's version, which only a local install provides before its release. + - name: Install the Java client the editors build against + run: mvn -B -q -f client/java/pom.xml -pl :opensysml -am install -DskipTests -Dmaven.javadoc.skip=true + - name: Provision the audit tools run: scripts/download-sca-tools.sh diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 74cd8e05ff..d927e35cd8 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1032,6 +1032,11 @@ jobs: - name: Build and test the plugin run: mvn -B -f editors/mdk/pom.xml verify -Dopensysml.requireService=true + - name: Audit the plugin's dependencies + run: | + scripts/download-sca-tools.sh osv-scanner + make audit-java JAVA_PROJECT_DIRS=editors/mdk + docs: name: Documentation site needs: changes @@ -1633,7 +1638,7 @@ jobs: - name: Audit the Java client's dependencies run: | scripts/download-sca-tools.sh osv-scanner - make audit-java + make audit-java JAVA_PROJECT_DIRS=client/java # opensysml.requireService turns "no binary, so skip" into a failure, and # `install` builds the sources and javadoc jars a release needs on every commit. @@ -1690,6 +1695,11 @@ jobs: - name: Test and install the SysON backend run: mvn -B -f editors/syson/pom.xml install -Dopensysml.requireService=true + - name: Audit the backend's dependencies + run: | + scripts/download-sca-tools.sh osv-scanner + make audit-java JAVA_PROJECT_DIRS=editors/syson + - name: Set up Node.js uses: actions/setup-node@v4 with: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 53273ea43c..f4a19c83df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -307,9 +307,11 @@ about it, and add a case to `scripts/ci-changed-areas-test.sh`, which the same j by the same `Changed areas` output as that job: `make vulncheck` (govulncheck over both Go modules, failing on a vulnerability the code reaches) in the Go static job, `make audit-npm` (`npm audit --omit=dev --audit-level=high` over every `package-lock.json`) in each Node job, -`make audit-python` (`pip-audit --strict` over `client/python`, `client/jupyter-kernel` and -`packaging/pypi-pysysml`) in the Python job, `make audit-rust` (`cargo audit`) in the Rust job -and `make audit-java` (osv-scanner over the Maven trees) in the Java job. The tools come pinned +`make audit-python` (`pip-audit --strict` over `client/python`, `client/jupyter-kernel`, +`packaging/pypi-pysysml` and `docs-requirements.txt`) in the Python job, `make audit-rust` +(`cargo audit`) in the Rust job and `make audit-java` (a CycloneDX SBOM of each Maven tree — +`client/java`, `editors/syson`, `editors/mdk` — scanned by osv-scanner, test scope excluded) in +the Java, SysON and MDK jobs. The tools come pinned and SHA-256 verified from `scripts/download-sca-tools.sh` (`make sca-tools`); `make audit` runs all five locally. A finding fails the job: fix it by bumping the dependency (a Dependabot pull request may already exist), never by lowering a level or ignoring an advisory. The same audits @@ -323,8 +325,8 @@ in every lockfile directory. ### Dependabot pull requests [`.github/dependabot.yml`](.github/dependabot.yml) opens one grouped pull request a week per -ecosystem and directory (Go modules, every npm lockfile, the Python packages, the Rust and Java -clients, and the GitHub Actions the workflows pin), against `develop`, with minor and patch +ecosystem and directory (both Go modules, every npm lockfile, the Python packages, the Rust +client, the three Maven trees, and the GitHub Actions the workflows pin), against `develop`, with minor and patch updates in one group and a Conventional Commits title (`chore(deps): …`). Such a pull request is handled like any other: it opens ready for review, so the full tier runs on it with `Changed areas` scoping the jobs to the lockfile it touched, Devin Review and the dependency audits run on diff --git a/Makefile b/Makefile index fb32f8e295..8469529441 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,7 @@ GOSEC_VERSION := v2.22.5 # Dependency-audit tool versions: govulncheck runs through `go run`; the others come # from scripts/download-sca-tools.sh, pinned and SHA-256 verified there. GOVULNCHECK_VERSION := v1.9.0 +CYCLONEDX_MAVEN_VERSION := 2.9.1 SCA_BIN := build/sca/bin PIP_AUDIT := build/sca/venv/bin/pip-audit # `npm audit` fails on production advisories at this severity and above, in every lockfile directory. @@ -67,6 +68,8 @@ JUPYTERLAB_DIR := editors/jupyterlab MCP_NPM_DIR := packaging/npm/sysml-mcp NPM_LOCK_DIRS := $(NODE_DIR) $(MCP_NPM_DIR) $(VSCODE_DIR) $(JUPYTERLAB_DIR) editors/syson/frontend scripts/asciidoc PYTHON_PROJECT_DIRS := $(PYTHON_DIR) $(JUPYTER_KERNEL_DIR) packaging/pypi-pysysml +PYTHON_REQUIREMENTS := docs-requirements.txt +JAVA_PROJECT_DIRS := client/java editors/syson editors/mdk PYTHON ?= python3 # api/proto/buf.gen.python.yaml starts the interpreter this names. export PYTHON @@ -419,14 +422,16 @@ sca-tools: ## Provision the pinned cargo-audit, osv-scanner and pip-audit under audit-npm: ## Fail on $(NPM_AUDIT_LEVEL)+ advisories against the production dependencies of every npm lockfile (NPM_LOCK_DIRS=dir... narrows) @for dir in $(NPM_LOCK_DIRS); do echo "npm audit $$dir" && npm --prefix "$$dir" audit --omit=dev --audit-level=$(NPM_AUDIT_LEVEL) || exit 1; done -audit-python: ## Fail on known vulnerabilities in the Python packages' dependencies (pip-audit --strict) +audit-python: ## Fail on known vulnerabilities in the Python packages' dependencies and the pinned requirements files (pip-audit --strict) @for dir in $(PYTHON_PROJECT_DIRS); do echo "pip-audit $$dir" && $(PIP_AUDIT) --strict --progress-spinner off "$$dir" || exit 1; done + @for file in $(PYTHON_REQUIREMENTS); do echo "pip-audit -r $$file" && $(PIP_AUDIT) --strict --progress-spinner off -r "$$file" || exit 1; done audit-rust: ## Fail on RustSec advisories against client/rust/Cargo.lock (cargo audit) $(SCA_BIN)/cargo-audit audit --file client/rust/Cargo.lock -audit-java: ## Fail on OSV advisories against the Java client's Maven dependencies (osv-scanner) - $(SCA_BIN)/osv-scanner scan source --recursive client/java +audit-java: ## Fail on OSV advisories against the Maven trees' compile and runtime dependencies (JAVA_PROJECT_DIRS=dir... narrows) + @# A CycloneDX SBOM carries the versions Maven resolved, reactor modules included, which osv-scanner's own pom resolver cannot follow. + @for dir in $(JAVA_PROJECT_DIRS); do echo "osv-scanner $$dir" && mvn -B -q -f "$$dir/pom.xml" org.cyclonedx:cyclonedx-maven-plugin:$(CYCLONEDX_MAVEN_VERSION):makeAggregateBom -DoutputFormat=json -DoutputName=bom -DincludeTestScope=false && $(SCA_BIN)/osv-scanner scan source -L "$$dir/target/bom.json" || exit 1; done audit: vulncheck audit-npm audit-python audit-rust audit-java ## Run every dependency audit, as CI and the release procedure do @echo "✓ Dependency audits passed" diff --git a/changes/unreleased/dependency-audits.added.md b/changes/unreleased/dependency-audits.added.md index fee9151daf..22a90813fb 100644 --- a/changes/unreleased/dependency-audits.added.md +++ b/changes/unreleased/dependency-audits.added.md @@ -1 +1 @@ -- **CI audits every dependency tree for known vulnerabilities and fails on a finding.** `make vulncheck` (govulncheck over both Go modules, reachable vulnerabilities only), `make audit-npm` (`npm audit --omit=dev --audit-level=high` in every `package-lock.json` directory), `make audit-python` (`pip-audit --strict` over the three Python packages), `make audit-rust` (`cargo audit`) and `make audit-java` (osv-scanner over the Maven trees) run in the pull-request workflow's quick and full tiers, in the CircleCI jobs that gate `develop`, `main` and release tags, and every night over `develop`; `make audit` runs all five and the release procedure requires it clean before the release branch and again before the tag, with unfixable findings checked against the CISA Known Exploited Vulnerabilities catalogue. The tools come pinned and SHA-256 verified from `scripts/download-sca-tools.sh`, and `.github/dependabot.yml` opens weekly grouped update pull requests against `develop` for every ecosystem. +- **CI audits every dependency tree for known vulnerabilities and fails on a finding.** `make vulncheck` (govulncheck over both Go modules, reachable vulnerabilities only), `make audit-npm` (`npm audit --omit=dev --audit-level=high` in every `package-lock.json` directory), `make audit-python` (`pip-audit --strict` over the three Python packages and the pinned docs toolchain), `make audit-rust` (`cargo audit`) and `make audit-java` (osv-scanner over a CycloneDX SBOM of each Maven tree: the Java client, the SysON plugin and the MDK plugin) run in the pull-request workflow's quick and full tiers, in the CircleCI jobs that gate `develop`, `main` and release tags, and every night over `develop`; `make audit` runs all five and the release procedure requires it clean before the release branch and again before the tag, with unfixable findings checked against the CISA Known Exploited Vulnerabilities catalogue. The tools come pinned and SHA-256 verified from `scripts/download-sca-tools.sh`, and `.github/dependabot.yml` opens weekly grouped update pull requests against `develop` for every ecosystem. From 7856446edf3e56635dd2fa3967570aa892d480f5 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:26:59 +0000 Subject: [PATCH 3/4] feat(ci): audit the npm lockfiles at moderate severity Every lockfile is clean at moderate once the vulnerable pins moved, so the stricter level costs nothing. Co-Authored-By: jason.han --- CONTRIBUTING.md | 9 ++++----- Makefile | 2 +- changes/unreleased/dependency-audits.added.md | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f4a19c83df..8c5c059137 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -306,7 +306,7 @@ about it, and add a case to `scripts/ci-changed-areas-test.sh`, which the same j **Dependency audits** run in both tiers, each in the job whose ecosystem it covers and gated by the same `Changed areas` output as that job: `make vulncheck` (govulncheck over both Go modules, failing on a vulnerability the code reaches) in the Go static job, `make audit-npm` -(`npm audit --omit=dev --audit-level=high` over every `package-lock.json`) in each Node job, +(`npm audit --omit=dev --audit-level=moderate` over every `package-lock.json`) in each Node job, `make audit-python` (`pip-audit --strict` over `client/python`, `client/jupyter-kernel`, `packaging/pypi-pysysml` and `docs-requirements.txt`) in the Python job, `make audit-rust` (`cargo audit`) in the Rust job and `make audit-java` (a CycloneDX SBOM of each Maven tree — @@ -317,10 +317,9 @@ all five locally. A finding fails the job: fix it by bumping the dependency (a D request may already exist), never by lowering a level or ignoring an advisory. The same audits run every night over `develop` ([nightly snapshots](docs/project/nightly.md)), so an advisory published against an unchanged tree is seen within a day, and before a release -([releasing](docs/project/releasing.md#before-tagging)). The npm level is `high` because -`editors/jupyterlab`'s pinned JupyterLab build tooling carries moderate advisories with no -upstream fix; lower it to `moderate` when `npm audit --omit=dev --audit-level=moderate` is clean -in every lockfile directory. +([releasing](docs/project/releasing.md#before-tagging)). The npm level is `moderate`, the +lowest `npm audit` ranks a real advisory at; `low` is not used because npm reports it for +advisories without a fix. ### Dependabot pull requests diff --git a/Makefile b/Makefile index 8469529441..14929c9214 100644 --- a/Makefile +++ b/Makefile @@ -32,7 +32,7 @@ CYCLONEDX_MAVEN_VERSION := 2.9.1 SCA_BIN := build/sca/bin PIP_AUDIT := build/sca/venv/bin/pip-audit # `npm audit` fails on production advisories at this severity and above, in every lockfile directory. -NPM_AUDIT_LEVEL := high +NPM_AUDIT_LEVEL := moderate BUF_VERSION := v1.57.2 GO_WINRES_VERSION := v0.3.3 diff --git a/changes/unreleased/dependency-audits.added.md b/changes/unreleased/dependency-audits.added.md index 22a90813fb..aeab48ac22 100644 --- a/changes/unreleased/dependency-audits.added.md +++ b/changes/unreleased/dependency-audits.added.md @@ -1 +1 @@ -- **CI audits every dependency tree for known vulnerabilities and fails on a finding.** `make vulncheck` (govulncheck over both Go modules, reachable vulnerabilities only), `make audit-npm` (`npm audit --omit=dev --audit-level=high` in every `package-lock.json` directory), `make audit-python` (`pip-audit --strict` over the three Python packages and the pinned docs toolchain), `make audit-rust` (`cargo audit`) and `make audit-java` (osv-scanner over a CycloneDX SBOM of each Maven tree: the Java client, the SysON plugin and the MDK plugin) run in the pull-request workflow's quick and full tiers, in the CircleCI jobs that gate `develop`, `main` and release tags, and every night over `develop`; `make audit` runs all five and the release procedure requires it clean before the release branch and again before the tag, with unfixable findings checked against the CISA Known Exploited Vulnerabilities catalogue. The tools come pinned and SHA-256 verified from `scripts/download-sca-tools.sh`, and `.github/dependabot.yml` opens weekly grouped update pull requests against `develop` for every ecosystem. +- **CI audits every dependency tree for known vulnerabilities and fails on a finding.** `make vulncheck` (govulncheck over both Go modules, reachable vulnerabilities only), `make audit-npm` (`npm audit --omit=dev --audit-level=moderate` in every `package-lock.json` directory), `make audit-python` (`pip-audit --strict` over the three Python packages and the pinned docs toolchain), `make audit-rust` (`cargo audit`) and `make audit-java` (osv-scanner over a CycloneDX SBOM of each Maven tree: the Java client, the SysON plugin and the MDK plugin) run in the pull-request workflow's quick and full tiers, in the CircleCI jobs that gate `develop`, `main` and release tags, and every night over `develop`; `make audit` runs all five and the release procedure requires it clean before the release branch and again before the tag, with unfixable findings checked against the CISA Known Exploited Vulnerabilities catalogue. The tools come pinned and SHA-256 verified from `scripts/download-sca-tools.sh`, and `.github/dependabot.yml` opens weekly grouped update pull requests against `develop` for every ecosystem. From a811ab8dc644b4c6ce9740e2a0c9910004f64814 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 27 Oct 2026 17:33:43 +0000 Subject: [PATCH 4/4] fix(ci): gate the nightly snapshot on the audit and audit lockfile-only packaging changes The nightly publish job needs the audit job, so a night whose audit fails ships nothing. packaging/npm/ is a service path and packaging/pypi-pysysml/ turns the Python area on, so a pull request changing only one of those manifests still runs its audit. Co-Authored-By: jason.han --- .github/workflows/nightly.yml | 3 ++- CONTRIBUTING.md | 3 ++- docs/project/nightly.md | 3 ++- scripts/ci-changed-areas-test.sh | 5 +++++ scripts/ci-changed-areas.sh | 4 ++-- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 77a61d90c9..229a4afb3b 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -192,7 +192,8 @@ jobs: publish: name: Build and publish the snapshot - needs: select + # A failed audit skips the night's publication: a snapshot never ships a known vulnerability. + needs: [select, audit] if: needs.select.outputs.publish == 'true' runs-on: ubuntu-latest timeout-minutes: 60 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8c5c059137..31888120ea 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -316,7 +316,8 @@ and SHA-256 verified from `scripts/download-sca-tools.sh` (`make sca-tools`); `m all five locally. A finding fails the job: fix it by bumping the dependency (a Dependabot pull request may already exist), never by lowering a level or ignoring an advisory. The same audits run every night over `develop` ([nightly snapshots](docs/project/nightly.md)), so an advisory -published against an unchanged tree is seen within a day, and before a release +published against an unchanged tree is seen within a day and no snapshot is published over a +failed audit, and before a release ([releasing](docs/project/releasing.md#before-tagging)). The npm level is `moderate`, the lowest `npm audit` ranks a real advisory at; `low` is not used because npm reports it for advisories without a fix. diff --git a/docs/project/nightly.md b/docs/project/nightly.md index 1812e08e97..9493c15983 100644 --- a/docs/project/nightly.md +++ b/docs/project/nightly.md @@ -215,7 +215,8 @@ own that needs no snapshot: `make vulncheck`, `make audit-npm`, `make audit-pyth CI runs on the paths it changes (see [CONTRIBUTING.md § CI/CD](../../CONTRIBUTING.md#cicd)). An advisory published against an unchanged `develop` therefore fails the next night's run, and each audit runs even when an -earlier one failed, so one night reports every tree. +earlier one failed, so one night reports every tree. The publish job needs the audit job, so a +night whose audit fails publishes nothing: no snapshot ships a known vulnerability. [`.github/workflows/nightly.yml`](../../.github/workflows/nightly.yml) runs at 03:23 UTC and on demand (`workflow_dispatch`, with a `force` input that publishes diff --git a/scripts/ci-changed-areas-test.sh b/scripts/ci-changed-areas-test.sh index 03eb539680..0c9356a5cd 100755 --- a/scripts/ci-changed-areas-test.sh +++ b/scripts/ci-changed-areas-test.sh @@ -98,6 +98,11 @@ case_ action docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vsc case_ conformance docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vscode conformance/scenarios/01-server-info.json case_ workflow docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vscode .github/workflows/pr.yml case_ dependabot docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vscode .github/dependabot.yml +# The npm runner is tested and its lockfile audited in the Go static job, so its package is a service path. +case_ mcp-lockfile docs,go,java,julia,jupyterlab,matlab,mdk,node,python,rust,syson,vscode packaging/npm/sysml-mcp/package-lock.json +quick_case_ mcp-lockfile go packaging/npm/sysml-mcp/package-lock.json +# The pysysml stub is audited in the Python job. +case_ pysysml python packaging/pypi-pysysml/pyproject.toml # The Go hygiene suite caps every tracked fixture and test file, so these run it too. case_ client-fixture go,python client/python/tests/testdata/large.json case_ client-golden go,node client/node/test/render.golden.json diff --git a/scripts/ci-changed-areas.sh b/scripts/ci-changed-areas.sh index be90563ee0..4a53717f99 100755 --- a/scripts/ci-changed-areas.sh +++ b/scripts/ci-changed-areas.sh @@ -22,7 +22,7 @@ changed=$(git diff --name-only "$(git merge-base "$base" "$head")" "$head") # The editors' grammar generator and its committed outputs (the VS Code # grammars and the JupyterLab syntax table) are here too: the test that holds # them together is a Go test, run by the Go suite. -service_pattern='^(api/proto/|cmd/|internal/|tools/|client/opensysml/|client/release-digests\.json$|conformance/|tests/|scripts/|examples/|editors/vscode/tools/|editors/vscode/syntaxes/|editors/jupyterlab/src/syntax\.json$|Makefile$|go\.mod$|go\.sum$|install\.sh$|install\.ps1$|action\.yml$|\.github/workflows/|\.github/dependabot\.yml$|\.circleci/)' +service_pattern='^(api/proto/|cmd/|internal/|tools/|client/opensysml/|client/release-digests\.json$|conformance/|tests/|scripts/|examples/|editors/vscode/tools/|editors/vscode/syntaxes/|editors/jupyterlab/src/syntax\.json$|Makefile$|go\.mod$|go\.sum$|install\.sh$|install\.ps1$|action\.yml$|\.github/workflows/|\.github/dependabot\.yml$|\.circleci/|packaging/npm/)' docs_pattern='^(docs/|overrides/|packaging/man/|mkdocs\.yml$|README\.md$|CHANGELOG\.md$|CONTRIBUTING\.md$|AGENTS\.md$|.*\.md$)' node_pattern='^client/node/' python_pattern='^client/python/' @@ -68,7 +68,7 @@ emit node "$( { [[ "$fanout" = true ]] || matches "$node_pattern"; } && echo tru # so the Node, Java and Rust manifests, every editor manifest # check_version.py --editors reads, and the editors' client references have # to run it too. -emit python "$( { [[ "$fanout" = true ]] || matches "$python_pattern" || matches '^client/node/package\.json$' || matches '^client/java/pom\.xml$' || matches '^client/rust/opensysml/Cargo\.toml$' || matches '^client/rust/Cargo\.lock$' || matches '^editors/(mdk|syson(/backend)?)/pom\.xml$' || matches '^editors/(mdk/(plugin|tools|openapi-stubs|mdk-api-stubs|mdk-bridge|dist)|syson/syson-api-stubs)/pom\.xml$' || matches '^editors/(vscode|jupyterlab|syson/frontend)/package(-lock)?\.json$'; } && echo true || echo false)" +emit python "$( { [[ "$fanout" = true ]] || matches "$python_pattern" || matches '^packaging/pypi-pysysml/' || matches '^client/node/package\.json$' || matches '^client/java/pom\.xml$' || matches '^client/rust/opensysml/Cargo\.toml$' || matches '^client/rust/Cargo\.lock$' || matches '^editors/(mdk|syson(/backend)?)/pom\.xml$' || matches '^editors/(mdk/(plugin|tools|openapi-stubs|mdk-api-stubs|mdk-bridge|dist)|syson/syson-api-stubs)/pom\.xml$' || matches '^editors/(vscode|jupyterlab|syson/frontend)/package(-lock)?\.json$'; } && echo true || echo false)" emit java "$( { [[ "$fanout" = true ]] || matches "$java_pattern"; } && echo true || echo false)" emit rust "$( { [[ "$fanout" = true ]] || matches "$rust_pattern"; } && echo true || echo false)" emit julia "$( { [[ "$fanout" = true ]] || matches "$julia_pattern"; } && echo true || echo false)"