Skip to content

Commit 038925c

Browse files
author
Big Della
authored
feat: add security audit and dependency controls (#536)
1 parent 822b5d0 commit 038925c

25 files changed

Lines changed: 1644 additions & 114 deletions

File tree

.github/dependabot.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,41 @@ updates:
7474
- "root"
7575
versioning-strategy: "increase"
7676

77+
- package-ecosystem: "npm"
78+
directory: "/packages/sdk"
79+
schedule:
80+
interval: "weekly"
81+
day: "monday"
82+
time: "09:00"
83+
open-pull-requests-limit: 3
84+
labels:
85+
- "dependencies"
86+
- "sdk"
87+
versioning-strategy: "increase"
88+
89+
- package-ecosystem: "npm"
90+
directory: "/contracts/evm"
91+
schedule:
92+
interval: "weekly"
93+
day: "monday"
94+
time: "09:00"
95+
open-pull-requests-limit: 3
96+
labels:
97+
- "dependencies"
98+
- "solidity"
99+
versioning-strategy: "increase"
100+
101+
- package-ecosystem: "cargo"
102+
directory: "/contracts"
103+
schedule:
104+
interval: "weekly"
105+
day: "monday"
106+
time: "09:00"
107+
open-pull-requests-limit: 3
108+
labels:
109+
- "dependencies"
110+
- "rust"
111+
77112
# Enable security updates
78113
security-updates:
79114
- package-ecosystem: "npm"
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
name: Dependency Vulnerability Scan
2+
3+
on:
4+
pull_request:
5+
branches: [main, dev]
6+
push:
7+
branches: [main, dev]
8+
schedule:
9+
- cron: '0 9 * * 1'
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
issues: write
15+
16+
env:
17+
SECURITY_REPORT_DIR: security-reports/dependencies
18+
DEPENDENCY_POLICY_PATH: scripts/security/dependency-policy.json
19+
20+
jobs:
21+
dependency-scan:
22+
name: npm, Cargo, Solidity, and License Scan
23+
runs-on: ubuntu-latest
24+
timeout-minutes: 30
25+
steps:
26+
- uses: actions/checkout@v4
27+
28+
- uses: actions/setup-node@v4
29+
with:
30+
node-version: '22'
31+
cache: npm
32+
cache-dependency-path: |
33+
package-lock.json
34+
backend/package-lock.json
35+
frontend/package-lock.json
36+
packages/sdk/package-lock.json
37+
contracts/evm/package-lock.json
38+
39+
- uses: dtolnay/rust-toolchain@stable
40+
41+
- name: Install scan tools
42+
run: |
43+
mkdir -p "$SECURITY_REPORT_DIR"
44+
cargo install cargo-audit --locked
45+
npm install -g license-checker
46+
47+
- name: npm audit
48+
continue-on-error: true
49+
run: |
50+
for dir in . backend frontend packages/sdk contracts/evm; do
51+
if [ -f "$dir/package-lock.json" ]; then
52+
name=$(echo "$dir" | sed 's#^\.$#root#;s#[/.]#-#g')
53+
npm audit --json --prefix "$dir" > "$SECURITY_REPORT_DIR/${name}-npm-audit.json" || true
54+
license-checker --json --start "$dir" > "$SECURITY_REPORT_DIR/${name}-licenses.json" || true
55+
fi
56+
done
57+
58+
- name: Cargo audit
59+
continue-on-error: true
60+
working-directory: contracts
61+
run: |
62+
cargo audit --json > "../$SECURITY_REPORT_DIR/cargo-audit.json" || true
63+
64+
- name: Solidity dependency/static scan
65+
continue-on-error: true
66+
run: |
67+
python -m pip install --user slither-analyzer
68+
"$HOME/.local/bin/slither" contracts --json "$SECURITY_REPORT_DIR/slither.json" --exclude-dependencies || true
69+
70+
- name: Aggregate and enforce policy
71+
run: node scripts/security/aggregate-vulnerability-reports.mjs
72+
73+
- name: Upload vulnerability report
74+
if: always()
75+
uses: actions/upload-artifact@v4
76+
with:
77+
name: dependency-vulnerability-report
78+
path: ${{ env.SECURITY_REPORT_DIR }}/
79+
retention-days: 90
80+
81+
- name: Notify Slack on critical vulnerabilities
82+
if: failure()
83+
env:
84+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
85+
run: |
86+
if [ -z "$SLACK_WEBHOOK_URL" ]; then
87+
echo "SLACK_WEBHOOK_URL is not configured; skipping Slack notification."
88+
exit 0
89+
fi
90+
curl -X POST -H 'Content-Type: application/json' \
91+
--data "{\"text\":\"Critical/high dependency vulnerabilities detected in ${GITHUB_REPOSITORY}. See workflow run ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}.\"}" \
92+
"$SLACK_WEBHOOK_URL"
93+
94+
- name: Create issue for scheduled scan failures
95+
if: failure() && github.event_name == 'schedule'
96+
uses: actions/github-script@v7
97+
with:
98+
script: |
99+
const fs = require('fs');
100+
const body = fs.existsSync(`${process.env.SECURITY_REPORT_DIR}/dependency-vulnerability-report.md`)
101+
? fs.readFileSync(`${process.env.SECURITY_REPORT_DIR}/dependency-vulnerability-report.md`, 'utf8')
102+
: 'Dependency scan failed. See workflow artifacts for details.';
103+
await github.rest.issues.create({
104+
owner: context.repo.owner,
105+
repo: context.repo.repo,
106+
title: `Dependency vulnerabilities detected - ${new Date().toISOString().slice(0, 10)}`,
107+
body,
108+
labels: ['security', 'dependencies']
109+
});
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
ALTER TABLE "webhooks"
2+
ADD COLUMN IF NOT EXISTS "signature_version" TEXT NOT NULL DEFAULT 'v1',
3+
ADD COLUMN IF NOT EXISTS "secret_expires_at" TIMESTAMP(3),
4+
ADD COLUMN IF NOT EXISTS "rotated_at" TIMESTAMP(3),
5+
ADD COLUMN IF NOT EXISTS "encryption_public_key" TEXT;
6+
7+
ALTER TABLE "audit_logs"
8+
ADD COLUMN IF NOT EXISTS "timestamp" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
9+
ADD COLUMN IF NOT EXISTS "actor" TEXT NOT NULL DEFAULT 'system',
10+
ADD COLUMN IF NOT EXISTS "resource" TEXT NOT NULL DEFAULT 'legacy',
11+
ADD COLUMN IF NOT EXISTS "details" JSONB,
12+
ADD COLUMN IF NOT EXISTS "previous_hash" TEXT NOT NULL DEFAULT repeat('0', 64),
13+
ADD COLUMN IF NOT EXISTS "hash" TEXT,
14+
ADD COLUMN IF NOT EXISTS "anchor_id" TEXT,
15+
ADD COLUMN IF NOT EXISTS "archived_at" TIMESTAMP(3),
16+
ADD COLUMN IF NOT EXISTS "cold_archived_at" TIMESTAMP(3);
17+
18+
UPDATE "audit_logs"
19+
SET "hash" = md5("id" || "created_at"::text || "action")
20+
WHERE "hash" IS NULL;
21+
22+
ALTER TABLE "audit_logs" ALTER COLUMN "hash" SET NOT NULL;
23+
24+
CREATE UNIQUE INDEX IF NOT EXISTS "audit_logs_hash_key" ON "audit_logs"("hash");
25+
CREATE INDEX IF NOT EXISTS "audit_logs_timestamp_idx" ON "audit_logs"("timestamp");
26+
CREATE INDEX IF NOT EXISTS "audit_logs_actor_idx" ON "audit_logs"("actor");
27+
CREATE INDEX IF NOT EXISTS "audit_logs_action_idx" ON "audit_logs"("action");
28+
CREATE INDEX IF NOT EXISTS "audit_logs_actor_action_timestamp_idx" ON "audit_logs"("actor", "action", "timestamp");
29+
30+
CREATE TABLE IF NOT EXISTS "audit_anchors" (
31+
"id" TEXT NOT NULL,
32+
"latest_hash" TEXT NOT NULL,
33+
"chain" TEXT NOT NULL,
34+
"transaction_hash" TEXT,
35+
"block_number" TEXT,
36+
"status" TEXT NOT NULL DEFAULT 'pending',
37+
"error" TEXT,
38+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
39+
CONSTRAINT "audit_anchors_pkey" PRIMARY KEY ("id")
40+
);
41+
42+
CREATE INDEX IF NOT EXISTS "audit_anchors_latest_hash_idx" ON "audit_anchors"("latest_hash");
43+
CREATE INDEX IF NOT EXISTS "audit_anchors_created_at_idx" ON "audit_anchors"("created_at");
44+
45+
CREATE TABLE IF NOT EXISTS "account_lockouts" (
46+
"id" TEXT NOT NULL,
47+
"account_id" TEXT NOT NULL,
48+
"ip_address" TEXT,
49+
"failed_attempts" INTEGER NOT NULL DEFAULT 0,
50+
"locked_until" TIMESTAMP(3),
51+
"unlock_token_hash" TEXT,
52+
"last_failed_at" TIMESTAMP(3),
53+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
54+
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
55+
CONSTRAINT "account_lockouts_pkey" PRIMARY KEY ("id")
56+
);
57+
58+
CREATE UNIQUE INDEX IF NOT EXISTS "account_lockouts_account_id_ip_address_key" ON "account_lockouts"("account_id", "ip_address");
59+
CREATE INDEX IF NOT EXISTS "account_lockouts_account_id_idx" ON "account_lockouts"("account_id");
60+
CREATE INDEX IF NOT EXISTS "account_lockouts_ip_address_idx" ON "account_lockouts"("ip_address");
61+
CREATE INDEX IF NOT EXISTS "account_lockouts_locked_until_idx" ON "account_lockouts"("locked_until");
62+
63+
CREATE TABLE IF NOT EXISTS "login_attempts" (
64+
"id" TEXT NOT NULL,
65+
"account_id" TEXT NOT NULL,
66+
"ip_address" TEXT NOT NULL,
67+
"user_agent" TEXT,
68+
"success" BOOLEAN NOT NULL,
69+
"reason" TEXT,
70+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
71+
CONSTRAINT "login_attempts_pkey" PRIMARY KEY ("id")
72+
);
73+
74+
CREATE INDEX IF NOT EXISTS "login_attempts_account_id_created_at_idx" ON "login_attempts"("account_id", "created_at");
75+
CREATE INDEX IF NOT EXISTS "login_attempts_ip_address_created_at_idx" ON "login_attempts"("ip_address", "created_at");
76+
CREATE INDEX IF NOT EXISTS "login_attempts_success_idx" ON "login_attempts"("success");
77+
78+
CREATE TABLE IF NOT EXISTS "webhook_secrets" (
79+
"id" TEXT NOT NULL,
80+
"merchant_id" TEXT NOT NULL,
81+
"key_id" TEXT NOT NULL,
82+
"secret_hash" TEXT NOT NULL,
83+
"version" TEXT NOT NULL DEFAULT 'v1',
84+
"active_from" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
85+
"expires_at" TIMESTAMP(3) NOT NULL,
86+
"rotated_at" TIMESTAMP(3),
87+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
88+
CONSTRAINT "webhook_secrets_pkey" PRIMARY KEY ("id")
89+
);
90+
91+
CREATE UNIQUE INDEX IF NOT EXISTS "webhook_secrets_merchant_id_key_id_key" ON "webhook_secrets"("merchant_id", "key_id");
92+
CREATE INDEX IF NOT EXISTS "webhook_secrets_merchant_id_expires_at_idx" ON "webhook_secrets"("merchant_id", "expires_at");
93+
94+
CREATE TABLE IF NOT EXISTS "vulnerability_reports" (
95+
"id" TEXT NOT NULL,
96+
"source" TEXT NOT NULL,
97+
"scanned_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
98+
"summary" JSONB NOT NULL,
99+
"artifact_url" TEXT,
100+
CONSTRAINT "vulnerability_reports_pkey" PRIMARY KEY ("id")
101+
);
102+
103+
CREATE INDEX IF NOT EXISTS "vulnerability_reports_source_scanned_at_idx" ON "vulnerability_reports"("source", "scanned_at");
104+
105+
CREATE TABLE IF NOT EXISTS "dependency_vulnerabilities" (
106+
"id" TEXT NOT NULL,
107+
"report_id" TEXT NOT NULL,
108+
"ecosystem" TEXT NOT NULL,
109+
"package_name" TEXT NOT NULL,
110+
"installed_version" TEXT,
111+
"fixed_version" TEXT,
112+
"severity" TEXT NOT NULL,
113+
"advisory_id" TEXT,
114+
"title" TEXT NOT NULL,
115+
"remediation" TEXT,
116+
"due_at" TIMESTAMP(3),
117+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
118+
CONSTRAINT "dependency_vulnerabilities_pkey" PRIMARY KEY ("id")
119+
);
120+
121+
CREATE INDEX IF NOT EXISTS "dependency_vulnerabilities_ecosystem_severity_idx" ON "dependency_vulnerabilities"("ecosystem", "severity");
122+
CREATE INDEX IF NOT EXISTS "dependency_vulnerabilities_package_name_idx" ON "dependency_vulnerabilities"("package_name");
123+
ALTER TABLE "dependency_vulnerabilities"
124+
ADD CONSTRAINT "dependency_vulnerabilities_report_id_fkey"
125+
FOREIGN KEY ("report_id") REFERENCES "vulnerability_reports"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

0 commit comments

Comments
 (0)