Skip to content

ci: scheduled security audit across every SDK (Mon + Wed, osv-scanner + native auditors) - #382

Merged
Nic-dorman merged 8 commits into
mainfrom
nic/v2-1582-security-audit
Oct 20, 2026
Merged

Nic-dorman merged 8 commits into
mainfrom
nic/v2-1582-security-audit

Conversation

@Nic-dorman

@Nic-dorman Nic-dorman commented Oct 20, 2026 •

Copy link
Copy Markdown
Member

The advisory fixes this audit depends on have all landed: Rust lockfiles (#366), gRPC (#380), npm (#381), SwiftNIO floors (#383). This PR now changes only the four files below.

Summary

A scheduled dependency-advisory audit covering every SDK in the repo (V2-1582 §3). Until now only Rust (cargo audit) and Go (main-only, continue-on-error govulncheck) were scanned, and only when someone touched those paths.

When it runs: Monday + Wednesday at 05:17 UTC, ahead of the release-decision meetings; on any PR or main push that changes a manifest or lockfile; and on demand (workflow_dispatch).

Job Covers Fails on
cargo audit ×4 antd, antd-rust, ffi/rust, ffi/node Cargo.lock (honours .cargo/audit.toml) RustSec vulnerabilities (warnings don't fail)
govulncheck antd-go vulnerabilities in code antd-go actually calls
npm audit ×2 antd-js, ffi/node package-lock.json any advisory, dev included
NuGet audit antd-csharp + ffi/csharp, transitive any advisory; Examples report-only
resolve ×6 generates the lockfiles the SDKs deliberately don't commit: Dart (pub get), PHP (composer update --no-install), Ruby (bundle lock), Python (uv pip compile; antd-py, antd-mcp, ant-dev), Gradle (Java, Kotlin, Android, via an init script) and Swift (package resolve) resolution errors
osv-scanner every committed and generated lockfile any finding outside examples/ not accepted in osv-scanner.toml

osv-scanner also reads GitHub advisories that RustSec doesn't carry. That's how the openpgp/openssl findings fixed in #366 were found while cargo audit reported 0.

Policy

  • Blocking: everything except examples/, dev and test dependencies included. Most ecosystems can't separate dev from runtime in a lockfile, and dev tooling runs in CI next to publishing credentials.

  • Report-only: examples/ / Examples/ (web3j's jackson/netty/bouncycastle in the Java and Kotlin examples; Newtonsoft.Json in the C# examples). Shown in the summary with a warning, never fails.

  • Accepted findings live in osv-scanner.toml, each with a reason, tracking issue and ignoreUntil date. When the date passes the finding fails the run again, which forces a re-review. The initial list (14 entries):

    IDs Until Why
    RUSTSEC-2026-0367/0368/0369 (zip) 2026-11-16 extract() never called; mirrors ci(deps): ignore zip extract() advisories until ant-core moves to zip 9 (RUSTSEC-2026-0367, -0368, -0369) #372; V2-1634
    RUSTSEC-2026-0253 (lru) 2027-01-18 fix needs lru 0.18 via alloy-provider upstream
    8 RustSec "unmaintained" notices (incl. basic-toml, build-time only via uniffi) 2027-01-18 informational; cargo audit only warns; osv reports them as findings
    GO-2026-5932 (x/crypto openpgp) 2027-01-18 no fix exists; not imported
    EEF-CVE-2026-43966 (cowlib via gun; covers its alias GHSA-w4f7-4cxr-rv3c) 2026-12-14 unreachable per fix(antd-elixir): move to grpc 1.0.5 past the critical Hex advisory, commit mix.lock (V2-1574) #375 review; V2-1637
  • Ecosystems without a committed lockfile are scanned as resolved on the day, so a run can go red with no code change. That's what the schedule is for.

  • Not scanned: Lua, Zig, C++ (no usable advisory database).

Files

  • .github/workflows/security-audit.yml: the workflow
  • osv-scanner.toml: accepted findings (reason, tracking issue, ignoreUntil for each)
  • .github/security-audit/lock-all.init.gradle: Gradle dependency locking from outside the build (no build-file changes); -PauditConfigurations limits ffi/android to releaseRuntimeClasspath
  • .github/security-audit/collect-lockfiles.sh: copies each resolve job's generated lockfiles into the artifact

Swift

Swift resolves in its own job in the official swift:6.4 image, not setup-swift, which lags behind Swift releases. The toolchain decides what gets resolved: the first run used Swift 6.0, resolved swift-nio 2.97.1 / -http2 1.42.0 / -extras 1.33.0 and correctly reported 5 SwiftNIO advisories whose fixes need Swift 6.1. That finding led to #383 (antd-swift now requires those fixes, Swift 6.1+). On 6.4 it resolves 2.104.0 / 1.46.0 / 1.35.1.

Output

The job summary carries the osv table for the blocking pass, a collapsed list of accepted findings with their reasons, and the examples report. Blocking results are also uploaded as SARIF (category osv-scanner), so they appear in the Security tab and on PRs. Scanner versions are pinned (cargo-audit 0.22.2, govulncheck v1.9.0, osv-scanner 2.7.0 with its SHA-256 checked, uv 0.13.3).

Test plan

  • Full resolve + scan pipeline run locally on a scratch worktree of this branch (Dart, PHP, Ruby, Python, Gradle Java + Kotlin; Swift and Android are CI-only):
    • blocking pass: exit 0, all accepted findings filtered with their reasons printed
    • examples pass: 114 advisories across 16 packages, all web3j's dependencies in the Java/Kotlin examples, report-only
    • composer's own audit: no PHP advisories; pip-audit-equivalent: Python clean; Dart, Ruby clean
  • --x-exclude form checked: g:**/examples works, g:**/examples/** silently doesn't
  • actionlint 1.7.12 (the version lint-workflows.yml uses): clean
  • Fixes from this PR's own runs: (1) exit codes are now captured with || code=$?, because Actions' bash -e ended the first scan step before it wrote the summary; (2) Swift resolves under 6.4 (above); (3) safe.directory for git inside the Swift container
  • Run at 49b21c2: 40 checks passed, 2 skipped (Go race/security, which run only on pushes to main). Blocking scan passed, SARIF upload accepted, examples report ran; 0 open osv-scanner code-scanning alerts on the PR
  • Run after merging main (fix(antd-go, antd-java, antd-kotlin): bump gRPC (and Kotlin protobuf) past open advisories #380, fix(antd-js, ffi/node): clear npm dev-dependency advisories (vitest 4.1.12, tsup 9, js-yaml) #381, fix(antd-swift): require SwiftNIO releases with the security fixes (Swift 6.1+) #383) caught a newly published notice, RUSTSEC-2026-0374 (basic-toml unmaintained, build-time only via uniffi), present on main too. Accepted as above
  • Run with the updated osv-scanner.toml (7dcc4b4): 17/17 checks pass, including the blocking scan, SARIF upload and examples report
  • After merge: one workflow_dispatch run on main to seed the Security tab

Refs V2-1582.

🤖 Generated with Claude Code

Nic-dorman and others added 3 commits October 20, 2026 11:07
… + native auditors)

Adds .github/workflows/security-audit.yml (V2-1582 §3). It runs at 05:17
UTC on Mondays and Wednesdays, ahead of the release-decision meetings, on
any PR or main push that changes a manifest or lockfile, and on demand.

- cargo-audit: every Cargo.lock (antd, antd-rust, ffi/rust, ffi/node),
  honouring each .cargo/audit.toml.
- govulncheck: antd-go, failing only on reachable vulnerabilities.
- npm-audit: antd-js and ffi/node, dev dependencies included.
- dotnet-audit: NuGet, transitive included; Examples is report-only.
- resolve: generates the lockfiles the SDKs deliberately don't commit
  (Dart, PHP, Ruby, Python via uv, Gradle for Java/Kotlin/Android via an
  init script, Swift) so they can be scanned as a fresh install would
  resolve them today.
- osv-scanner: every committed and generated lockfile. Blocking for
  everything except examples/; examples get a report-only pass with a
  warning. Results go to the job summary and to code scanning as SARIF.

osv-scanner.toml holds the accepted findings, each with a reason,
tracking issue and ignoreUntil date: zip (until 2026-11-16, matching
#372), lru, the RustSec "unmaintained" notices, x/crypto openpgp and the
Elixir cowlib advisory (V2-1637). Scanner versions are pinned.

.github/security-audit/lock-all.init.gradle enables Gradle dependency
locking from outside the build, so no build file changes.

Refs V2-1582.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Nic-dorman and others added 2 commits October 20, 2026 11:27
…h a current toolchain

First run on #382 found two problems in the workflow itself:

- Actions runs `run:` steps with `bash -e`, and `set -uo pipefail` does not
  turn that off. The first osv-scanner call (the SARIF pass) exited 1 on
  findings and ended the step before the job summary, the findings table
  or the ::error:: line were written. The examples step, meant never to
  fail, died the same way. Exit codes are now captured with `|| code=$?`.

- Swift resolved under Swift 6.0, which can't use SwiftNIO's security fixes
  (swift-nio >= 2.100.0, swift-nio-http2 >= 1.44.0, swift-nio-extras >=
  1.34.1 all need swift-tools-version 6.1). So it scanned swift-nio 2.97.1,
  -http2 1.42.0 and -extras 1.33.0 and reported 5 advisories that a current
  toolchain doesn't resolve to. Swift now resolves in its own job in the
  official swift:6.4 image (setup-swift lags behind Swift releases). antd-swift's
  constraints are all `from:`, so 6.1+ resolves the fixed releases.

The lockfile collection moves to .github/security-audit/collect-lockfiles.sh,
shared by both resolve jobs.

Refs V2-1582.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ainer

The swift:6.4 container runs as a different user from the one that owns
the checked-out workspace, so git refused it ("detected dubious
ownership") and collect-lockfiles.sh failed after a successful resolve.

Refs V2-1582.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Nic-dorman and others added 2 commits October 20, 2026 12:17
…dant GHSA ignore

RUSTSEC-2026-0374 (basic-toml unmaintained, informational, no fix) now
appears in osv's data for ffi/rust and ffi/node. basic-toml is build-time
only (uniffi -> uniffi_bindgen -> askama_derive, a proc-macro), so it is
not in the shipped ant-ffi library. cargo audit only warns on it. It is
accepted with the same quarterly review date as the other unmaintained
notices.

GHSA-w4f7-4cxr-rv3c is removed: osv-scanner treats it as an alias of
EEF-CVE-2026-43966, which already covers it, and reported the separate
entry as unused.

Refs V2-1582.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nic-dorman
Nic-dorman marked this pull request as ready for review October 20, 2026 11:34

@dirvine dirvine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — two P2 false-negative defects

Reviewed all four added files (596 lines) in WithAutonomi/ant-sdk at 7dcc4b4cc1a2508723de0344929039c98e1ddcad. The exact-head CI is green, but the following gaps can produce a successful audit without the claimed coverage.

  1. NuGet command errors can pass as a clean audit (.github/workflows/security-audit.yml:191–199). Calling check through || disables Bash errexit inside the function. Restore and package-list failures are lost; the final negated grep can return success. The inline finding includes the reproduced exit codes and the minimum fix.
  2. The standalone FFI Kotlin SDK is never resolved or scanned (.github/workflows/security-audit.yml:288–291). It has its own Gradle build and dependencies, but no committed/generated lockfile. Neither the other Gradle roots nor recursive OSV scanning resolves it. This is a coverage defect, not a claim of a known vulnerability in its current dependencies.

Verified evidence

  • Exact-head security workflow: 15/15 jobs succeeded; the PR rollup contains 17 successful checks. Inspected NuGet, Gradle, Swift and OSV logs, including actual scanned lockfile paths.
  • Ran the actual NuGet shell block with controlled dotnet responses, changing only its scratch-output path. Clean output exits 0; vulnerable output exits 1; restore failure (17), package-list failure (23), and both failing all incorrectly exit 0. A scratch-only control with explicit || return $? guards fails all three error cases. These are shell-control tests under macOS Bash 3.2, not a claim that hosted NuGet failed.
  • Checked each Gradle root's settings.gradle.kts: none of the three invoked builds includes ffi/kotlin. The pinned OSV 2.7.0 scanner returns 128, No package sources found, when recursively scanning that directory with --offline --no-ignore; exact-head hosted logs also show no FFI Kotlin lockfile.
  • actionlint 1.7.12, Bash syntax, and git diff --check passed. Local actionlint ran without shellcheck; hosted workflow lint is separately green.
  • Collector fixture passed for ignored/untracked lockfiles, six recognised lockfile types and paths with spaces; excluded build/cache paths stayed excluded and an empty selection failed.
  • A separate seeded OSV fixture confirmed that a future ignoreUntil suppresses findings (exit 0) and an expired date restores them (exit 1). The fixture is not a repository dependency finding.

Panel coverage and adjudication

4/6 reviewed — partial panel; 2/3 named models. GLM 5.3 (z-ai/glm-5.3) and Qwen 3.8 (qwen38-27b) each completed correctness and adversarial reviews. All four substantive opinions request changes. Qwen confirms the NuGet defect; GLM confirms both defects. Both findings were checked independently against source and execution evidence. There is no unresolved material disagreement among the responding reviewers.

Missing: DS4 correctness and adversarial. DS4 (deepseek-v4-flash) produced no usable correctness verdict: the original and one narrowed same-model retry both ended at the 8,192-token cap (finish_reason=length). Its subsequently launched focused adversarial role was stopped after that recovery failed; it is not counted as a review. No fallback was counted as DS4. Full-panel consensus is not claimed. This verdict uses the permitted partial-panel quorum, not a claim that all six roles completed.

Tag-pinned Actions, temporary-file hardening and speculative SARIF robustness concerns are not promoted to blockers. A failed/missing SARIF upload would fail that job, not silently prove a clean scan. The FFI Python package has no runtime dependencies, so it is not asserted as a third runtime-coverage gap.

Scope limits / next steps

I did not rerun all ecosystem resolvers or the full SDK build matrix locally; the successful hosted run is the integration evidence. No repository files were changed. Fix the NuGet status propagation, add FFI Kotlin lockfile generation and confirm its runtime/test dependencies appear in the blocking scan. The planned post-merge workflow_dispatch run on main remains unrun. No merge or deployment was performed.

Comment thread .github/workflows/security-audit.yml Outdated
Comment on lines +191 to +194
check() {
dotnet restore "$1" >/dev/null
dotnet list "$1" package --vulnerable --include-transitive | tee /tmp/nuget.txt
! grep -q "has the following vulnerable packages" /tmp/nuget.txt

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Propagate restore and package-list errors from the NuGet check

check is called as the left side of || at line 199, so Bash disables errexit throughout the function. A failed restore or dotnet list ... | tee therefore falls through to the final negated grep. If the error output does not contain the vulnerable-package heading, that grep returns success and all four blocking projects can pass without a successful audit. I executed this exact run block with controlled dotnet responses: restore exit 17, listing exit 23, and both failing each produced workflow exit 0; clean and advisory-output controls produced 0 and 1 respectively. Explicitly propagate both restore and pipeline failures (for example, || return $?) before checking advisory output, and add regression tests for these failure paths. A scratch-only control with those guards failed all three error cases as expected. These are shell-control probes, not a claim that the hosted NuGet run failed.

Comment on lines +288 to +291
(cd antd-java && ./gradlew --no-daemon --console=plain --init-script "$INIT" resolveAndLockAll --write-locks)
(cd antd-kotlin && ../antd-java/gradlew --no-daemon --console=plain -p . --init-script "$INIT" resolveAndLockAll --write-locks)
(cd ffi/android && gradle --no-daemon --console=plain --init-script "$INIT" resolveAndLockAll --write-locks \
-PauditConfigurations=releaseRuntimeClasspath)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Generate a lockfile for the standalone FFI Kotlin SDK

The Gradle job resolves antd-java, antd-kotlin and ffi/android, but never ffi/kotlin. The separate ffi/kotlin/AntFfi/build.gradle.kts project declares JVM JNA 5.17.0, coroutines 1.8.1 and test dependencies, has no committed Gradle lockfile, and is not included by any of those three Gradle builds. The collector only copies lockfiles; it cannot resolve this manifest. Exact-head OSV job logs contain no ffi/kotlin lockfile, and running the pinned OSV 2.7.0 scanner recursively over that directory with --no-ignore --offline returns 128, “No package sources found”. Thus a dependency advisory confined to that SDK will not affect this supposedly every-SDK blocking audit. Add standalone FFI Kotlin dependency resolution with a suitable Gradle runtime, upload its generated lockfile, and verify its runtime/test packages appear in the blocking scan. This is a coverage defect, not a claim that its current dependencies have a known vulnerability.

…lin; check lockfile coverage

Review fixes for #382 (dirvine, both P2: ways the audit could pass without
the claimed coverage):

- NuGet: `check` was called as `check || ...`, and bash disables errexit
  inside a function called that way. A failed `dotnet restore` or
  `dotnet list` fell through to the final negated grep and passed as
  clean (reproduced: restore exit 17 -> script exit 0). The logic moves to
  .github/security-audit/nuget-audit.sh, which checks every exit
  explicitly: 0 clean, 1 vulnerable, 2 could not run. Both 1 and 2 fail
  for blocking projects; examples only warn.
  .github/security-audit/test-nuget-audit.sh runs it against a stub dotnet
  on every run (8 cases: clean, vulnerable, restore/list/both failing,
  list failing with advisories printed, two report-only cases).
- ffi/kotlin: the standalone JVM UniFFI binding (JNA 5.17.0, coroutines
  1.8.1, kotlin-test) is its own Gradle build, not included by any of the
  three that were locked, so it was never resolved or scanned. It is now
  locked too (36 packages locally, osv-clean), and the Gradle step fails
  if any of its four expected lockfiles is missing.
- The osv-scanner job now checks that all 19 expected lockfiles (committed
  + generated) are present before scanning, listing them in the job
  summary, so a resolver that quietly produces less fails the run instead
  of leaving an SDK unscanned.

The workflow header now says why ffi/python and ffi/swift have nothing to
resolve: they declare no package dependencies, and their native library is
ffi/rust, which is scanned.

Refs V2-1582.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@dirvine dirvine left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review: APPROVE (provisional: partial review panel, not full consensus)

Reviewed the complete current PR (six added files) at c277b00c2326801036133bfd3b1edfbe59d0b082, including the changes since 7dcc4b4cc1a2508723de0344929039c98e1ddcad. Both findings from review 5584736686 are fixed. No remaining demonstrated blocker was found in this scope.

Previous findings

  • P2 NuGet fail-open: fixed. .github/security-audit/nuget-audit.sh:44–56 explicitly handles restore/list failures. The blocking-project loop at lines 64–72 converts both operational errors and vulnerability reports into a nonzero job result. Report-only failures still warn without blocking, as intended. The production script is called directly by the workflow and the regression tests.
  • P2 standalone Kotlin FFI omission: fixed. .github/workflows/security-audit.yml:276–284 resolves the separate ffi/kotlin Gradle root and requires all four expected Gradle lockfiles to be non-empty. The scanner coverage check also requires ffi/kotlin/AntFfi/gradle.lockfile. Exact-head CI generated that file and OSV scanned its 36 packages. I downloaded the exact-head resolved-gradle artifact (12262889296) and verified the lockfile contains JNA 5.17.0 and coroutines 1.8.1.

Verification

  • Hosted security-audit run 39420175946: all 15 jobs succeeded at the reviewed head. The OSV log accounts for all 19 expected SDK lockfiles.
  • Local: all 8 repository regression cases passed; 13 additional NuGet control-flow cases passed; 2 mutation checks proved the regressions reject removal of either failure-propagation branch; 3 coverage-guard cases passed, including missing/empty Kotlin FFI lockfiles. Bash syntax and actionlint passed (actionlint shellcheck integration disabled).
  • Evidence limit: local NuGet failure-path tests use a stub executable, not a live vulnerable NuGet package. Hosted CI supplies real .NET/Gradle/OSV execution. These are separate forms of evidence, not interchangeable claims of end-to-end vulnerability injection.

Panel: 5/6 roles complete

  • z-ai/glm-5.3 — correctness: APPROVE; adversarial: APPROVE.
  • qwen38-27b — correctness: APPROVE; adversarial: APPROVE.
  • deepseek-v4-flash — correctness: APPROVE; adversarial: unavailable after bounded attempts (Timed out waiting for the model lock queue; output truncated (finish_reason=length)).

The completed roles agree on approval. Missing roles are not agreement. This is a provisional partial-panel result; no substitute model or historical vote is counted.

Non-blocking watch-out

NuGet vulnerability detection still depends on the current English dotnet list output phrase. A structured-output check or a fixture captured from a vulnerable package would reduce that format risk. No failure was demonstrated with the configured .NET 8 SDK, so this is follow-up hardening, not a new blocker.

Review only. No merge or deployment performed.

@Nic-dorman
Nic-dorman merged commit ffd207a into main Oct 20, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants