fix(review): address #175 audit findings #504
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Static Analysis | |
| # Supply-chain + lint gates that don't need a full build matrix. | |
| # Runs on every PR and on develop/main pushes. Kept in a separate | |
| # workflow from `ci.yml` so a slow `cargo deny` advisory-db fetch | |
| # doesn't block the build matrix. | |
| on: | |
| push: | |
| branches: [main, develop] | |
| pull_request: | |
| branches: [main, develop] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_TOOLCHAIN: "1.91" | |
| jobs: | |
| changes: | |
| name: Classify changes | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| contracts: ${{ steps.decide.outputs.contracts }} | |
| rust: ${{ steps.decide.outputs.rust }} | |
| full: ${{ steps.decide.outputs.full }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Collect changed files | |
| id: changed | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BEFORE_SHA: ${{ github.event.before || '' }} | |
| BASE_REF: ${{ github.base_ref || '' }} | |
| run: | | |
| set -euo pipefail | |
| changed_file_list="$(mktemp)" | |
| if [ "$EVENT_NAME" = "pull_request" ]; then | |
| git fetch --no-tags --depth=1 origin "$BASE_REF:refs/remotes/origin/$BASE_REF" | |
| git diff --name-only "origin/$BASE_REF" "$GITHUB_SHA" > "$changed_file_list" | |
| elif [ -n "$BEFORE_SHA" ] && ! [[ "$BEFORE_SHA" =~ ^0+$ ]]; then | |
| git diff --name-only "$BEFORE_SHA" "$GITHUB_SHA" > "$changed_file_list" | |
| else | |
| git diff-tree --no-commit-id --name-only -r "$GITHUB_SHA" > "$changed_file_list" | |
| fi | |
| { | |
| echo 'files<<EOF' | |
| cat "$changed_file_list" | |
| echo 'EOF' | |
| } >> "$GITHUB_OUTPUT" | |
| - name: Decide static lanes | |
| id: decide | |
| env: | |
| CHANGED_FILES: ${{ steps.changed.outputs.files || '' }} | |
| run: | | |
| set -euo pipefail | |
| full=false | |
| contracts=false | |
| rust=false | |
| static=false | |
| while IFS= read -r file; do | |
| [ -n "$file" ] || continue | |
| case "$file" in | |
| contracts/*|deployments/*|foundry.toml|slither.config.json) | |
| contracts=true | |
| ;; | |
| esac | |
| case "$file" in | |
| .cargo/*|Cargo.toml|Cargo.lock|deny.toml|patches/*|scripts/*|trading-*/*) | |
| rust=true | |
| ;; | |
| esac | |
| case "$file" in | |
| .cargo/audit.toml|.github/workflows/static-analysis.yml|deny.toml|slither.config.json) | |
| static=true | |
| ;; | |
| esac | |
| done <<< "$CHANGED_FILES" | |
| if [ "$static" = "true" ]; then | |
| full=true | |
| contracts=true | |
| rust=true | |
| fi | |
| echo "full=$full" >> "$GITHUB_OUTPUT" | |
| echo "contracts=$contracts" >> "$GITHUB_OUTPUT" | |
| echo "rust=$rust" >> "$GITHUB_OUTPUT" | |
| cargo-deny: | |
| name: Cargo Deny (advisories + licences + sources + bans) | |
| needs: changes | |
| if: needs.changes.outputs.rust == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@stable | |
| # The action wraps `cargo install cargo-deny --locked` and | |
| # caches the binary across runs. `command: check` runs all four | |
| # sections (advisories, bans, licenses, sources) — same as a | |
| # local `cargo deny check`. | |
| # `deny.toml` is auto-detected from the repo root, so we don't pass | |
| # `--config` explicitly — older cargo-deny CLIs only accept it as a | |
| # subcommand flag and the action injects `arguments` at the top | |
| # level. Keeping the call minimal keeps us version-portable. | |
| - uses: EmbarkStudios/cargo-deny-action@v2 | |
| with: | |
| log-level: warn | |
| command: check | |
| cargo-audit: | |
| name: Cargo Audit (RustSec advisory DB) | |
| needs: changes | |
| if: needs.changes.outputs.rust == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN }} | |
| - uses: Swatinem/rust-cache@v2 | |
| - name: Install cargo-audit | |
| run: cargo install cargo-audit --locked | |
| # Reads .cargo/audit.toml for the ignore list. Every entry there | |
| # is cross-referenced in deny.toml's [advisories].ignore and in | |
| # audits/static-analysis-triage.md. `-D warnings` denies on | |
| # unmaintained / unsound / yanked; whitelisted IDs cover known | |
| # transitive deps (substrate / solana-sdk / ethers-v2). | |
| - name: Run cargo audit | |
| run: cargo audit -D warnings | |
| slither: | |
| name: Slither (Solidity static analysis) | |
| needs: changes | |
| if: needs.changes.outputs.contracts == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.11" | |
| - name: Install slither + solc-select | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install slither-analyzer solc-select | |
| solc-select install 0.8.20 | |
| solc-select use 0.8.20 | |
| - uses: foundry-rs/foundry-toolchain@v1 | |
| with: | |
| version: v1.5.1 | |
| - name: Install Soldeer deps | |
| run: forge soldeer install | |
| # slither.config.json filters dependencies/, contracts/test/, | |
| # contracts/script/ and excludes already-triaged informational | |
| # detectors. `--fail-pedantic` treats any new HIGH/MEDIUM as a CI | |
| # failure. Inline `slither-disable-next-line` comments document | |
| # any FP suppression, with rationale traceable to | |
| # audits/static-analysis-triage.md. | |
| - name: Run slither on production contracts | |
| run: | | |
| set -e | |
| for f in TradeValidator TradingVault PolicyEngine FeeDistributor StrategyRegistry VaultDeployer VaultFactory VaultShare VaultShareDeployer ChainlinkUsdValuator WrappedAssetValuator UniswapV3TwapValuator; do | |
| echo "=== slither contracts/src/$f.sol ===" | |
| slither contracts/src/$f.sol --config-file slither.config.json | |
| done | |
| static-analysis-gate: | |
| name: Static Analysis Gate | |
| needs: [changes, cargo-deny, cargo-audit, slither] | |
| if: always() | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Check required static lanes | |
| env: | |
| CHANGES_RESULT: ${{ needs.changes.result }} | |
| CONTRACTS_NEEDED: ${{ needs.changes.outputs.contracts }} | |
| RUST_NEEDED: ${{ needs.changes.outputs.rust }} | |
| CARGO_DENY_RESULT: ${{ needs.cargo-deny.result }} | |
| CARGO_AUDIT_RESULT: ${{ needs.cargo-audit.result }} | |
| SLITHER_RESULT: ${{ needs.slither.result }} | |
| run: | | |
| set -euo pipefail | |
| failed=false | |
| if [ "$CHANGES_RESULT" != "success" ]; then | |
| echo "::error::Change classification failed with result '$CHANGES_RESULT'" | |
| exit 1 | |
| fi | |
| require_success() { | |
| local label="$1" | |
| local needed="$2" | |
| local result="$3" | |
| if [ "$needed" = "true" ] && [ "$result" != "success" ]; then | |
| echo "::error::$label required but finished with result '$result'" | |
| failed=true | |
| fi | |
| } | |
| require_success "Cargo Deny" "$RUST_NEEDED" "$CARGO_DENY_RESULT" | |
| require_success "Cargo Audit" "$RUST_NEEDED" "$CARGO_AUDIT_RESULT" | |
| require_success "Slither" "$CONTRACTS_NEEDED" "$SLITHER_RESULT" | |
| if [ "$CONTRACTS_NEEDED" != "true" ] && [ "$RUST_NEEDED" != "true" ]; then | |
| echo "No Rust or Solidity surfaces changed; static analysis gate is green." | |
| fi | |
| if [ "$failed" = "true" ]; then | |
| exit 1 | |
| fi |