Repository navigation
feat(security): supply-chain security - SBOM, dependency pinning and provenance verification #1001
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
343b081
6002bfb
dfdfff1
451a41c
be21772
8aa6174
865e120
3d5c48e
fe06b00
d3039c9
ccd7123
2d326cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,9 +26,9 @@ jobs: | |
| contracts: ${{ steps.filter.outputs.contracts }} | ||
| shared: ${{ steps.filter.outputs.shared }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4.2.2 | ||
|
|
||
| - uses: dorny/paths-filter@v3 | ||
| - uses: dorny/paths-filter@b41dfa943b1939b9b646f67753bfe35cf6e4de03 # v3.0.2 | ||
| id: filter | ||
| with: | ||
| # On push events, compare against the previous commit so the first | ||
|
|
@@ -62,11 +62,11 @@ jobs: | |
| if: needs.changes.outputs.website == 'true' || needs.changes.outputs.shared == 'true' | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4.2.2 | ||
|
|
||
| - uses: pnpm/action-setup@v4 | ||
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v4.1.0 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| - uses: actions/setup-node@e51e5fe84fc33b4c73ebe40526b2694712b5b858 # v4.3.0 | ||
| with: | ||
| node-version: 20 | ||
| cache: pnpm | ||
|
|
@@ -90,9 +90,9 @@ jobs: | |
| run: | ||
| working-directory: apps/intelligence | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4.2.2 | ||
|
|
||
| - uses: actions/setup-python@v5 | ||
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v5.5.0 | ||
| with: | ||
| python-version: "3.12" | ||
|
|
||
|
|
@@ -117,11 +117,11 @@ jobs: | |
| if: needs.changes.outputs.dapp == 'true' || needs.changes.outputs.shared == 'true' | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4.2.2 | ||
|
|
||
| - uses: pnpm/action-setup@v4 | ||
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v4.1.0 | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| - uses: actions/setup-node@e51e5fe84fc33b4c73ebe40526b2694712b5b858 # v4.3.0 | ||
| with: | ||
| node-version: 22 | ||
| cache: pnpm | ||
|
|
@@ -148,7 +148,7 @@ jobs: | |
| needs: changes | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4.2.2 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
|
|
@@ -161,7 +161,7 @@ jobs: | |
| - name: Run gitleaks | ||
| run: gitleaks detect --source . --no-git --redact --no-banner --config .gitleaks.toml | ||
|
|
||
| - uses: dtolnay/rust-toolchain@stable | ||
| - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable | ||
| if: needs.changes.outputs.contracts == 'true' || needs.changes.outputs.shared == 'true' | ||
|
|
||
| - name: Install cargo-audit | ||
|
|
@@ -174,7 +174,7 @@ jobs: | |
| working-directory: packages/contracts | ||
| continue-on-error: true | ||
|
|
||
| - uses: actions/setup-go@v5 | ||
| - uses: actions/setup-go@5064d95ba18d4a9dda7c4cc67adeb03aea1a2a14 # v5.3.0 | ||
| if: needs.changes.outputs.api == 'true' || needs.changes.outputs.shared == 'true' | ||
| with: | ||
| go-version-file: apps/api/go.mod | ||
|
|
@@ -219,10 +219,10 @@ jobs: | |
| working-directory: apps/api | ||
| continue-on-error: true | ||
|
|
||
| - uses: pnpm/action-setup@v4 | ||
| - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v4.1.0 | ||
| if: needs.changes.outputs.dapp == 'true' || needs.changes.outputs.shared == 'true' | ||
|
|
||
| - uses: actions/setup-node@v4 | ||
| - uses: actions/setup-node@e51e5fe84fc33b4c73ebe40526b2694712b5b858 # v4.3.0 | ||
| if: needs.changes.outputs.dapp == 'true' || needs.changes.outputs.shared == 'true' | ||
| with: | ||
| node-version: 22 | ||
|
|
@@ -238,7 +238,7 @@ jobs: | |
| working-directory: apps/dapp/frontend | ||
| continue-on-error: true | ||
|
|
||
| - uses: actions/setup-python@v5 | ||
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v5.5.0 | ||
| if: needs.changes.outputs.intelligence == 'true' || needs.changes.outputs.shared == 'true' | ||
| with: | ||
| python-version: "3.12" | ||
|
|
@@ -261,7 +261,7 @@ jobs: | |
|
|
||
| - name: Semgrep SAST (TypeScript/Next.js) | ||
| if: needs.changes.outputs.dapp == 'true' || needs.changes.outputs.shared == 'true' | ||
| uses: semgrep/semgrep-action@v1 | ||
| uses: semgrep/semgrep-action@713efdd345f3035192eaa63f56867b88e63e4e5d # v1 | ||
| with: | ||
| config: >- | ||
| p/typescript | ||
|
|
@@ -271,6 +271,66 @@ jobs: | |
| env: | ||
| SEMGREP_APP_TOKEN: ${{ secrets.SEMGREP_APP_TOKEN }} | ||
|
|
||
| - name: Typosquat detection (new npm dependencies) | ||
| if: needs.changes.outputs.dapp == 'true' || needs.changes.outputs.shared == 'true' | ||
| run: | | ||
| # Check for typosquatting patterns in package.json dependencies | ||
| # Uses npm package name similarity to detect potential typosquats | ||
| python3 -c " | ||
| import json, sys, re | ||
| from pathlib import Path | ||
|
|
||
| pkg = json.loads(Path('apps/dapp/frontend/package.json').read_text()) | ||
| known_packages = {'react', 'react-dom', 'next', 'next/router', 'next/navigation', | ||
| '@tanstack/react-query', 'framer-motion', 'recharts', 'zod', 'react-hook-form', | ||
| 'zustand', 'uuid', 'date-fns', 'ethers', 'viem', 'wagmi', 'axios', 'lodash', | ||
| '@radix-ui/react-dialog', '@radix-ui/react-dropdown-menu', '@radix-ui/react-tabs', | ||
| '@radix-ui/react-tooltip', '@radix-ui/react-popover', '@radix-ui/react-select', | ||
| 'tailwindcss', 'postcss', 'autoprefixer', 'typescript', 'vitest', 'eslint', | ||
| '@typescript-eslint/eslint-plugin', '@typescript-eslint/parser', 'prettier', | ||
| 'clsx', 'tailwind-merge', 'lucide-react', 'class-variance-authority', | ||
| '@hookform/resolvers', 'sonner', 'react-hot-toast', 'next-themes', | ||
| '@starknet-react/core', '@starknet-react/chains', 'starknet', | ||
| '@stellar/stellar-sdk', '@blend-capital/blend-sdk'} | ||
|
|
||
| def levenshtein(a, b): | ||
| m, n = len(a), len(b) | ||
| dp = [[0]*(n+1) for _ in range(m+1)] | ||
| for i in range(m+1): dp[i][0] = i | ||
| for j in range(n+1): dp[0][j] = j | ||
| for i in range(1, m+1): | ||
| for j in range(1, n+1): | ||
| dp[i][j] = min(dp[i-1][j]+1, dp[i][j-1]+1, | ||
| dp[i-1][j-1] + (a[i-1] != b[j-1])) | ||
| return dp[m][n] | ||
|
|
||
| suspicious = [] | ||
| for dep in pkg.get('dependencies', {}): | ||
| name = dep.split('/')[-1] if '/' in dep else dep | ||
| for known in known_packages: | ||
| kname = known.split('/')[-1] if '/' in known else known | ||
| if name != kname and len(name) > 3 and len(kname) > 3: | ||
| d = levenshtein(name.lower(), kname.lower()) | ||
| if d <= 2 and d > 0: | ||
| suspicious.append((dep, known, d)) | ||
|
|
||
| for dep in pkg.get('devDependencies', {}): | ||
| name = dep.split('/')[-1] if '/' in dep else dep | ||
| for known in known_packages: | ||
| kname = known.split('/')[-1] if '/' in known else known | ||
| if name != kname and len(name) > 3 and len(kname) > 3: | ||
| d = levenshtein(name.lower(), kname.lower()) | ||
| if d <= 2 and d > 0: | ||
| suspicious.append((dep, known, d)) | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| if suspicious: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Separate from the YAML problem above, this Python does not compile. The print(f' {dep} (edit distance {dist} from {known})'
else:
This step has no Worth noting the description also lists "Typosquat Detection" via
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @0xDeon fixed: the script now compiles (closing paren added) and has been extracted into |
||
| print('::warning::Potential typosquatted dependencies detected:') | ||
| for dep, known, dist in suspicious: | ||
| print(f' {dep} (edit distance {dist} from {known})' | ||
| else: | ||
| print('No potential typosquats detected') | ||
| " | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| api: | ||
| name: API (Go) | ||
| needs: changes | ||
|
|
@@ -312,9 +372,9 @@ jobs: | |
| # silently skips in CI even though the redis service below is running. | ||
| REDIS_ADDR: localhost:6379 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4.2.2 | ||
|
|
||
| - uses: actions/setup-go@v5 | ||
| - uses: actions/setup-go@5064d95ba18d4a9dda7c4cc67adeb03aea1a2a14 # v5.3.0 | ||
| with: | ||
| go-version-file: apps/api/go.mod | ||
| cache: true | ||
|
|
@@ -357,9 +417,9 @@ jobs: | |
| run: | ||
| working-directory: packages/contracts | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4.2.2 | ||
|
|
||
| - uses: dtolnay/rust-toolchain@stable | ||
| - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable | ||
| with: | ||
| targets: wasm32-unknown-unknown | ||
|
|
||
|
|
@@ -406,3 +466,50 @@ jobs: | |
| exit 1 | ||
| fi | ||
|
|
||
| sbom: | ||
| name: SBOM Generation | ||
| needs: changes | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@f548e57e544e1ff5a4c46bf1e1b8685f8e4a348a # v4.2.2 | ||
|
|
||
| - name: Install cyclonedx-bom (Go) | ||
| run: go install github.com/CycloneDX/cyclonedx-gomod/cmd/cyclonedx-gomod@v1 | ||
|
|
||
| - name: Install syft | ||
| run: | | ||
| curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin v1.19.0 | ||
|
|
||
| - name: Generate Go API SBOM | ||
| if: needs.changes.outputs.api == 'true' || needs.changes.outputs.shared == 'true' | ||
| working-directory: apps/api | ||
| run: | | ||
| cyclonedx-gomod mod -json -output ../../sbom-api.json . | ||
| syft dir:. --output cyclonedx-json=sbom-api-syft.json 2>/dev/null || true | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| - name: Generate frontend SBOM (npm) | ||
| if: needs.changes.outputs.dapp == 'true' || needs.changes.outputs.shared == 'true' | ||
| working-directory: apps/dapp/frontend | ||
| run: | | ||
| npx @cyclonedx/cyclonedx-npm --output-file ../../../sbom-frontend.json --pnpm 2>/dev/null || \ | ||
| npx @cyclonedx/cyclonedx-npm --output-file ../../../sbom-frontend.json 2>/dev/null || true | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
|
|
||
| - name: Generate Python SBOM | ||
| if: needs.changes.outputs.intelligence == 'true' || needs.changes.outputs.shared == 'true' | ||
| working-directory: apps/intelligence | ||
| run: | | ||
| syft dir:. --output cyclonedx-json=../../sbom-intelligence.json 2>/dev/null || true | ||
|
|
||
| - name: Generate contracts SBOM (Rust) | ||
| if: needs.changes.outputs.contracts == 'true' || needs.changes.outputs.shared == 'true' | ||
| working-directory: packages/contracts | ||
| run: | | ||
| syft dir:. --output cyclonedx-json=../../sbom-contracts.json 2>/dev/null || true | ||
|
|
||
| - name: Upload SBOM artifacts | ||
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4 | ||
| with: | ||
| name: sbom-artifacts | ||
| path: sbom-*.json | ||
| if-no-files-found: warn | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This block breaks the entire workflow file, not just this step.
run: |on line 276 opens a block scalar, but the Python starting here sits at column 0 — outside the scalar's indentation. YAML therefore ends the scalar at line 279 and tries to parseimport json, sys, reas a new top-level mapping key:GitHub Actions rejects a workflow it cannot parse, so merging this disables every job in
ci.ymlrepo-wide — website, intelligence, dapp, api, contracts, security, all of it.The 12 green checks on this PR are misleading: they ran against the merge-base
ci.ymlfromdev, not this version. The step is also gated onneeds.changes.outputs.dapp, which was false for this PR's own file set, so it never executed even locally.Two ways to fix it. Either indent the whole heredoc to sit inside the block scalar, or — better, and what #968 already does — move the script to
scripts/typosquat_check.pyand invoke it as a file. A committed script is testable and lintable; an inlined one is neither.Please validate before re-requesting review:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@0xDeon thanks for the thorough review — both points are addressed. The YAML block-scalar indentation is fixed (the Python now sits inside the
run: |block, andyaml.safe_loadpasses), and the missing)SyntaxError is fixed. Per your suggestion, the logic was also extracted into a committed, testablescripts/typosquat_check.py(invoked as a file, with regression tests inscripts/test_typosquat_check.py), and the step now hascontinue-on-error: trueso the fuzzy heuristic can never block an unrelated PR. Full scoped names are compared and only newly-added deps (incl. optionalDependencies) are checked.