audit-notes-cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -41
- package/dist/analyzer/diff.js +32 -0
- package/dist/analyzer/normalize.js +43 -0
- package/dist/analyzer/types.js +2 -0
- package/dist/cli.js +64 -40
- package/dist/diff/compare.js +26 -0
- package/dist/diff/normalize.js +11 -0
- package/dist/diff/types.js +2 -0
- package/dist/github/comment.js +20 -0
- package/dist/invariants/suggest.js +20 -0
- package/dist/license.js +58 -4
- package/dist/paid/fixHints.js +11 -0
- package/dist/paid/recommendations.js +24 -0
- package/dist/paid/types.js +2 -0
- package/dist/profiles/profiles.js +57 -0
- package/dist/report/contest.js +22 -0
- package/dist/report/diff.js +19 -0
- package/dist/report/executive.js +33 -0
- package/dist/report/markdown.js +38 -51
- package/dist/report/sarif.js +62 -0
- package/dist/risk/factors.js +32 -0
- package/dist/risk/score.js +26 -0
- package/dist/risk/scorer.js +30 -0
- package/dist/risk/types.js +3 -0
- package/dist/types/finding.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,41 +1,250 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
1
|
+
## Audit Notes CLI
|
|
2
|
+
------------------------------------------------------------------------
|
|
3
|
+
Noise-Free Smart Contract Security for PRs
|
|
4
|
+
|
|
5
|
+
Audit Notes is a CI-first smart contract audit CLI built on top of Slither.
|
|
6
|
+
It focuses on signal over noise by failing CI only when new HIGH or CRITICAL issues are introduced.
|
|
7
|
+
|
|
8
|
+
The core scan and CI gating are free forever.
|
|
9
|
+
Audit Notes PRO adds actionable remediation guidance.
|
|
10
|
+
|
|
11
|
+
--------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
## Why Audit Notes?
|
|
14
|
+
|
|
15
|
+
Most security tools are noisy.
|
|
16
|
+
|
|
17
|
+
Audit Notes is opinionated:
|
|
18
|
+
|
|
19
|
+
- Catch new risks, not old ones
|
|
20
|
+
|
|
21
|
+
- Fail CI only on HIGH / CRITICAL regressions
|
|
22
|
+
|
|
23
|
+
- Generate outputs developers can actually read
|
|
24
|
+
|
|
25
|
+
- Work locally and in CI
|
|
26
|
+
|
|
27
|
+
- No dashboards. No accounts. Just files.
|
|
28
|
+
|
|
29
|
+
--------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
#🆓 Free (No license required)
|
|
34
|
+
|
|
35
|
+
- Static analysis via Slither
|
|
36
|
+
|
|
37
|
+
- Markdown audit notes
|
|
38
|
+
|
|
39
|
+
- SARIF output (GitHub Security tab)
|
|
40
|
+
|
|
41
|
+
- PR diffing against base branch
|
|
42
|
+
|
|
43
|
+
- CI gating on new HIGH / CRITICAL issues
|
|
44
|
+
|
|
45
|
+
- Noise suppression profiles
|
|
46
|
+
|
|
47
|
+
# 💎 PRO (License required)
|
|
48
|
+
|
|
49
|
+
- Remediation recommendations
|
|
50
|
+
|
|
51
|
+
- Fix hints and guidance
|
|
52
|
+
|
|
53
|
+
- Designed for solo auditors and teams
|
|
54
|
+
|
|
55
|
+
- Works locally and in CI
|
|
56
|
+
|
|
57
|
+
--------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
Audit Notes is distributed via npm.
|
|
62
|
+
|
|
63
|
+
- npm install -g audit-notes
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
Or run directly with:
|
|
67
|
+
|
|
68
|
+
- npx audit-notes
|
|
69
|
+
|
|
70
|
+
--------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
## Prerequisites
|
|
73
|
+
|
|
74
|
+
Audit Notes expects a Slither JSON report.
|
|
75
|
+
|
|
76
|
+
Generate one with:
|
|
77
|
+
|
|
78
|
+
- slither . --json slither.json || true
|
|
79
|
+
|
|
80
|
+
--------------------------------------------------------------------------
|
|
81
|
+
|
|
82
|
+
## Usage
|
|
83
|
+
|
|
84
|
+
Run a basic audit (Free)
|
|
85
|
+
|
|
86
|
+
- npx audit-notes run
|
|
87
|
+
|
|
88
|
+
Generates:
|
|
89
|
+
|
|
90
|
+
output/AUDIT_NOTES.md
|
|
91
|
+
|
|
92
|
+
--------------------------------------------------------------------------
|
|
93
|
+
|
|
94
|
+
## Generate SARIF (Free)
|
|
95
|
+
|
|
96
|
+
- npx audit-notes run --sarif
|
|
97
|
+
|
|
98
|
+
Generates:
|
|
99
|
+
|
|
100
|
+
output/audit-notes.sarif
|
|
101
|
+
|
|
102
|
+
Upload this to GitHub’s Security tab using upload-sarif.
|
|
103
|
+
|
|
104
|
+
--------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
## PR diff mode (Free, CI-safe)
|
|
107
|
+
|
|
108
|
+
- npx audit-notes run --diff --base slither-main.json
|
|
109
|
+
|
|
110
|
+
Behavior:
|
|
111
|
+
|
|
112
|
+
Compares current findings with base
|
|
113
|
+
|
|
114
|
+
Fails CI only if new HIGH / CRITICAL issues are introduced
|
|
115
|
+
|
|
116
|
+
--------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
## PRO Usage
|
|
119
|
+
|
|
120
|
+
PRO unlocks guidance on how to fix issues, not basic scanning.
|
|
121
|
+
|
|
122
|
+
Set your license
|
|
123
|
+
- export AUDIT_NOTES_LICENSE=AN-PRO-XXXX
|
|
124
|
+
- export AUDIT_NOTES_SECRET=your_secret_here
|
|
125
|
+
|
|
126
|
+
(For local testing, AN-DEV can be used.)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
## Generate remediation recommendations (PRO)
|
|
130
|
+
|
|
131
|
+
- npx audit-notes run --recommendations
|
|
132
|
+
|
|
133
|
+
Generates:
|
|
134
|
+
|
|
135
|
+
output/recommendations.json
|
|
136
|
+
|
|
137
|
+
## Generate fix hints (PRO)
|
|
138
|
+
|
|
139
|
+
- npx audit-notes run --fix-hints
|
|
140
|
+
|
|
141
|
+
Generates:
|
|
142
|
+
|
|
143
|
+
output/fix-hints.json
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
- Without a license
|
|
147
|
+
|
|
148
|
+
Running PRO commands without a license will show:
|
|
149
|
+
|
|
150
|
+
" This feature requires Audit Notes PRO. Set AUDIT_NOTES_LICENSE to continue."
|
|
151
|
+
|
|
152
|
+
--------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
## Free vs PRO
|
|
155
|
+
|
|
156
|
+
Feature Free PRO
|
|
157
|
+
Static analysis (Slither) ✅ ✅
|
|
158
|
+
Markdown audit report ✅ ✅
|
|
159
|
+
SARIF (GitHub Security tab) ✅ ✅
|
|
160
|
+
PR diff & CI gating ✅ ✅
|
|
161
|
+
Noise suppression profiles ✅ ✅
|
|
162
|
+
Remediation recommendations ❌ ✅
|
|
163
|
+
Fix hints & guidance ❌ ✅
|
|
164
|
+
Solo usage ✅ ✅
|
|
165
|
+
Team / CI usage ✅ ✅
|
|
166
|
+
Priority support ❌ ✅
|
|
167
|
+
|
|
168
|
+
Audit Notes is free forever for scanning and CI gating.
|
|
169
|
+
PRO adds actionable guidance.
|
|
170
|
+
|
|
171
|
+
--------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
## GitHub Actions
|
|
174
|
+
|
|
175
|
+
Audit Notes is designed to run in CI.
|
|
176
|
+
|
|
177
|
+
Typical workflow:
|
|
178
|
+
|
|
179
|
+
- Run Slither on base branch
|
|
180
|
+
|
|
181
|
+
- Run Slither on PR
|
|
182
|
+
|
|
183
|
+
- Run audit-notes run --diff --base slither-main.json
|
|
184
|
+
|
|
185
|
+
Optionally upload SARIF
|
|
186
|
+
|
|
187
|
+
--------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
## Philosophy
|
|
190
|
+
|
|
191
|
+
Audit Notes is intentionally minimal:
|
|
192
|
+
|
|
193
|
+
- No dashboards
|
|
194
|
+
|
|
195
|
+
- No accounts
|
|
196
|
+
|
|
197
|
+
- No phone-home
|
|
198
|
+
|
|
199
|
+
- Files you can review, diff, and commit
|
|
200
|
+
|
|
201
|
+
Security tools should help you think less, not more.
|
|
202
|
+
|
|
203
|
+
--------------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
## License & Plans
|
|
206
|
+
|
|
207
|
+
Audit Notes is free to use for scanning and CI gating.
|
|
208
|
+
|
|
209
|
+
## PRO plans
|
|
210
|
+
|
|
211
|
+
- PRO Solo — individual auditors
|
|
212
|
+
|
|
213
|
+
- PRO Team — shared repos and CI
|
|
214
|
+
|
|
215
|
+
## PRO unlocks:
|
|
216
|
+
|
|
217
|
+
- Remediation recommendations
|
|
218
|
+
|
|
219
|
+
- Fix hints
|
|
220
|
+
|
|
221
|
+
Pricing is simple and transparent.
|
|
222
|
+
|
|
223
|
+
--------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
## Roadmap
|
|
226
|
+
|
|
227
|
+
- Improve recommendation quality
|
|
228
|
+
|
|
229
|
+
- Better PR summaries
|
|
230
|
+
|
|
231
|
+
- Optional team-level enforcement (later)
|
|
232
|
+
|
|
233
|
+
- No bloat. No feature creep.
|
|
234
|
+
|
|
235
|
+
--------------------------------------------------------------------------
|
|
236
|
+
|
|
237
|
+
## Contributing
|
|
238
|
+
|
|
239
|
+
Issues and PRs are welcome.
|
|
240
|
+
|
|
241
|
+
This project prioritizes clarity, signal, and developer trust.
|
|
242
|
+
|
|
243
|
+
--------------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
## One-line summary
|
|
246
|
+
|
|
247
|
+
- Free for scanning and CI gating.
|
|
248
|
+
- PRO for guidance on how to fix what’s found.
|
|
249
|
+
|
|
250
|
+
--------------------------------------------------------------------------
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffFindings = diffFindings;
|
|
4
|
+
function fingerprint(f) {
|
|
5
|
+
const loc = f.locations[0];
|
|
6
|
+
return [
|
|
7
|
+
f.check,
|
|
8
|
+
loc?.contract ?? "",
|
|
9
|
+
loc?.function ?? ""
|
|
10
|
+
].join("|");
|
|
11
|
+
}
|
|
12
|
+
function diffFindings(base, current) {
|
|
13
|
+
const baseMap = new Map(base.map(f => [fingerprint(f), f]));
|
|
14
|
+
const currMap = new Map(current.map(f => [fingerprint(f), f]));
|
|
15
|
+
const added = [];
|
|
16
|
+
const existing = [];
|
|
17
|
+
const removed = [];
|
|
18
|
+
for (const [key, curr] of currMap) {
|
|
19
|
+
if (baseMap.has(key)) {
|
|
20
|
+
existing.push(curr);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
added.push(curr);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
for (const [key, baseF] of baseMap) {
|
|
27
|
+
if (!currMap.has(key)) {
|
|
28
|
+
removed.push(baseF);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return { added, existing, removed };
|
|
32
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.normalizeDetectors = normalizeDetectors;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
function normalizeDetectors(detectors) {
|
|
9
|
+
return detectors.map((d) => ({
|
|
10
|
+
id: crypto_1.default
|
|
11
|
+
.createHash("sha1")
|
|
12
|
+
.update(d.check + JSON.stringify(d.locations ?? []))
|
|
13
|
+
.digest("hex"),
|
|
14
|
+
check: d.check,
|
|
15
|
+
description: d.description,
|
|
16
|
+
impact: mapImpact(d.impact),
|
|
17
|
+
riskScore: mapRiskScore(d.impact),
|
|
18
|
+
locations: d.locations ?? []
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
function mapImpact(impact) {
|
|
22
|
+
switch (impact?.toLowerCase()) {
|
|
23
|
+
case "critical":
|
|
24
|
+
case "high":
|
|
25
|
+
return "HIGH";
|
|
26
|
+
case "medium":
|
|
27
|
+
return "MEDIUM";
|
|
28
|
+
default:
|
|
29
|
+
return "LOW";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function mapRiskScore(impact) {
|
|
33
|
+
switch (impact?.toLowerCase()) {
|
|
34
|
+
case "critical":
|
|
35
|
+
return 9;
|
|
36
|
+
case "high":
|
|
37
|
+
return 7;
|
|
38
|
+
case "medium":
|
|
39
|
+
return 4;
|
|
40
|
+
default:
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -9,55 +9,79 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
const license_1 = require("./license");
|
|
10
10
|
const slither_1 = require("./analyzer/slither");
|
|
11
11
|
const detectors_1 = require("./analyzer/detectors");
|
|
12
|
+
const normalize_1 = require("./analyzer/normalize");
|
|
13
|
+
const compare_1 = require("./diff/compare");
|
|
12
14
|
const markdown_1 = require("./report/markdown");
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
const
|
|
15
|
+
const sarif_1 = require("./report/sarif");
|
|
16
|
+
const profiles_1 = require("./profiles/profiles");
|
|
17
|
+
const recommendations_1 = require("./paid/recommendations");
|
|
18
|
+
const fixHints_1 = require("./paid/fixHints");
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const cmd = args[0] ?? "run";
|
|
21
|
+
const useSarif = args.includes("--sarif");
|
|
22
|
+
const useDiff = args.includes("--diff");
|
|
23
|
+
const wantRecommendations = args.includes("--recommendations");
|
|
24
|
+
const wantFixHints = args.includes("--fix-hints");
|
|
25
|
+
function getFlag(name) {
|
|
26
|
+
const i = args.indexOf(name);
|
|
27
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
28
|
+
}
|
|
29
|
+
function getProfile() {
|
|
30
|
+
const i = args.indexOf("--profile");
|
|
31
|
+
if (i === -1)
|
|
32
|
+
return "default";
|
|
33
|
+
const p = args[i + 1];
|
|
34
|
+
if (!["default", "defi", "protocol", "strict"].includes(p)) {
|
|
35
|
+
throw new Error("Invalid profile");
|
|
36
|
+
}
|
|
37
|
+
return p;
|
|
38
|
+
}
|
|
17
39
|
async function run() {
|
|
18
|
-
(
|
|
40
|
+
if (wantRecommendations || wantFixHints) {
|
|
41
|
+
(0, license_1.validateLicense)();
|
|
42
|
+
}
|
|
19
43
|
const slitherPath = path_1.default.resolve("slither.json");
|
|
20
44
|
if (!fs_1.default.existsSync(slitherPath)) {
|
|
21
|
-
throw new Error("slither.json not found
|
|
45
|
+
throw new Error("slither.json not found");
|
|
46
|
+
}
|
|
47
|
+
const profile = getProfile();
|
|
48
|
+
const raw = (0, detectors_1.analyzeDetectors)((0, slither_1.loadSlither)(slitherPath).detectors);
|
|
49
|
+
const current = (0, profiles_1.applyProfile)((0, normalize_1.normalizeDetectors)(raw), profile);
|
|
50
|
+
fs_1.default.mkdirSync("output", { recursive: true });
|
|
51
|
+
if (useSarif) {
|
|
52
|
+
fs_1.default.writeFileSync("output/audit-notes.sarif", JSON.stringify((0, sarif_1.generateSarif)(current), null, 2));
|
|
53
|
+
return;
|
|
22
54
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
55
|
+
let markdown;
|
|
56
|
+
if (useDiff) {
|
|
57
|
+
const basePath = getFlag("--base");
|
|
58
|
+
if (!basePath)
|
|
59
|
+
throw new Error("Diff requires --base");
|
|
60
|
+
const baseRaw = (0, detectors_1.analyzeDetectors)((0, slither_1.loadSlither)(basePath).detectors);
|
|
61
|
+
const base = (0, profiles_1.applyProfile)((0, normalize_1.normalizeDetectors)(baseRaw), profile);
|
|
62
|
+
const diff = (0, compare_1.diffFindings)(base, current);
|
|
63
|
+
const blocking = diff.added.filter((f) => f.impact === "HIGH" || f.impact === "CRITICAL");
|
|
64
|
+
if (blocking.length)
|
|
65
|
+
process.exit(2);
|
|
66
|
+
markdown = (0, markdown_1.generateMarkdown)({ findings: current, diff });
|
|
29
67
|
}
|
|
30
68
|
else {
|
|
31
|
-
|
|
69
|
+
markdown = (0, markdown_1.generateMarkdown)({ findings: current });
|
|
70
|
+
}
|
|
71
|
+
fs_1.default.writeFileSync("output/AUDIT_NOTES.md", markdown);
|
|
72
|
+
if (wantRecommendations) {
|
|
73
|
+
fs_1.default.writeFileSync("output/recommendations.json", JSON.stringify((0, recommendations_1.generateRecommendations)(current), null, 2));
|
|
74
|
+
}
|
|
75
|
+
if (wantFixHints) {
|
|
76
|
+
fs_1.default.writeFileSync("output/fix-hints.json", JSON.stringify((0, fixHints_1.generateFixHints)(current), null, 2));
|
|
32
77
|
}
|
|
33
|
-
fs_1.default.mkdirSync("output", { recursive: true });
|
|
34
|
-
fs_1.default.writeFileSync("output/AUDIT_NOTES.md", report);
|
|
35
|
-
console.log("✅ Audit notes generated → output/AUDIT_NOTES.md");
|
|
36
78
|
}
|
|
37
79
|
async function main() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
console.log("✅ License valid");
|
|
45
|
-
}
|
|
46
|
-
else if (cmd === "version") {
|
|
47
|
-
console.log("audit-notes v0.1.0");
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
console.log(`
|
|
51
|
-
Usage:
|
|
52
|
-
audit-notes run
|
|
53
|
-
audit-notes check
|
|
54
|
-
audit-notes version
|
|
55
|
-
`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch (err) {
|
|
59
|
-
console.error("❌", err.message);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
80
|
+
if (cmd === "run")
|
|
81
|
+
await run();
|
|
82
|
+
else if (cmd === "check")
|
|
83
|
+
(0, license_1.validateLicense)();
|
|
84
|
+
else
|
|
85
|
+
console.log("audit-notes v0.1.0");
|
|
62
86
|
}
|
|
63
87
|
main();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffFindings = diffFindings;
|
|
4
|
+
const normalize_1 = require("./normalize");
|
|
5
|
+
function diffFindings(base, current) {
|
|
6
|
+
const baseMap = new Map(base.map((f) => [(0, normalize_1.normalizeFindingKey)(f), f]));
|
|
7
|
+
const result = {
|
|
8
|
+
added: [],
|
|
9
|
+
increasedRisk: [],
|
|
10
|
+
unchanged: []
|
|
11
|
+
};
|
|
12
|
+
for (const f of current) {
|
|
13
|
+
const key = (0, normalize_1.normalizeFindingKey)(f);
|
|
14
|
+
const prev = baseMap.get(key);
|
|
15
|
+
if (!prev) {
|
|
16
|
+
result.added.push(f);
|
|
17
|
+
}
|
|
18
|
+
else if (f.riskScore > prev.riskScore) {
|
|
19
|
+
result.increasedRisk.push(f);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
result.unchanged.push(f);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeFindingKey = normalizeFindingKey;
|
|
4
|
+
function normalizeFindingKey(f) {
|
|
5
|
+
const loc = f.locations[0];
|
|
6
|
+
return [
|
|
7
|
+
f.check,
|
|
8
|
+
loc?.contract ?? "unknown",
|
|
9
|
+
loc?.function ?? "unknown"
|
|
10
|
+
].join("::");
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.postPRComment = postPRComment;
|
|
7
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
8
|
+
async function postPRComment(repo, pr, token, body) {
|
|
9
|
+
const res = await (0, node_fetch_1.default)(`https://api.github.com/repos/${repo}/issues/${pr}/comments`, {
|
|
10
|
+
method: "POST",
|
|
11
|
+
headers: {
|
|
12
|
+
Authorization: `Bearer ${token}`,
|
|
13
|
+
"Content-Type": "application/json"
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify({ body })
|
|
16
|
+
});
|
|
17
|
+
if (!res.ok) {
|
|
18
|
+
throw new Error("Failed to post PR comment");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.suggestInvariants = suggestInvariants;
|
|
4
|
+
function suggestInvariants(findings) {
|
|
5
|
+
const invariants = [];
|
|
6
|
+
for (const f of findings) {
|
|
7
|
+
if (f.riskLevel !== "CRITICAL")
|
|
8
|
+
continue;
|
|
9
|
+
if (f.check.toLowerCase().includes("reentrancy")) {
|
|
10
|
+
invariants.push("State must be updated before any external call execution.");
|
|
11
|
+
}
|
|
12
|
+
if (f.check.toLowerCase().includes("delegatecall")) {
|
|
13
|
+
invariants.push("Delegatecall targets must be immutable and trusted.");
|
|
14
|
+
}
|
|
15
|
+
if (f.check.toLowerCase().includes("upgrade")) {
|
|
16
|
+
invariants.push("Upgrade paths must be restricted to governance-controlled roles.");
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return [...new Set(invariants)];
|
|
20
|
+
}
|
package/dist/license.js
CHANGED
|
@@ -1,13 +1,67 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.validateLicense = validateLicense;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
/**
|
|
9
|
+
* Validate PRO license.
|
|
10
|
+
* This function MUST ONLY be called for paid features.
|
|
11
|
+
*/
|
|
4
12
|
function validateLicense() {
|
|
5
13
|
const key = process.env.AUDIT_NOTES_LICENSE;
|
|
6
14
|
if (!key) {
|
|
7
|
-
throw new Error("
|
|
15
|
+
throw new Error("This feature requires Audit Notes PRO. Set AUDIT_NOTES_LICENSE to continue.");
|
|
8
16
|
}
|
|
9
|
-
|
|
10
|
-
|
|
17
|
+
// Explicit dev / CI bypass
|
|
18
|
+
if (key === "AN-DEV" || key === "AN-CI") {
|
|
19
|
+
return {
|
|
20
|
+
plan: "PRO",
|
|
21
|
+
scope: "SOLO",
|
|
22
|
+
interval: "M",
|
|
23
|
+
expiry: "2099-12"
|
|
24
|
+
};
|
|
11
25
|
}
|
|
12
|
-
|
|
26
|
+
const secret = process.env.AUDIT_NOTES_SECRET;
|
|
27
|
+
if (!secret) {
|
|
28
|
+
throw new Error("AUDIT_NOTES_SECRET must be set when using a paid license.");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Expected format:
|
|
32
|
+
* AN-PRO-SOLO-M-2026-01-<SIG>
|
|
33
|
+
*/
|
|
34
|
+
const parts = key.split("-");
|
|
35
|
+
if (parts.length !== 7) {
|
|
36
|
+
throw new Error("Invalid license key format");
|
|
37
|
+
}
|
|
38
|
+
const [prefix, plan, scope, interval, year, month, sig] = parts;
|
|
39
|
+
if (prefix !== "AN")
|
|
40
|
+
throw new Error("Invalid license prefix");
|
|
41
|
+
if (plan !== "PRO")
|
|
42
|
+
throw new Error("Invalid plan");
|
|
43
|
+
if (scope !== "SOLO" && scope !== "TEAM") {
|
|
44
|
+
throw new Error("Invalid license scope");
|
|
45
|
+
}
|
|
46
|
+
if (interval !== "M" && interval !== "Y") {
|
|
47
|
+
throw new Error("Invalid billing interval");
|
|
48
|
+
}
|
|
49
|
+
const expiry = `${year}-${month}`;
|
|
50
|
+
const payload = `AN-${plan}-${scope}-${interval}-${expiry}`;
|
|
51
|
+
const expectedSig = crypto_1.default
|
|
52
|
+
.createHmac("sha256", secret)
|
|
53
|
+
.update(payload)
|
|
54
|
+
.digest("hex")
|
|
55
|
+
.slice(0, 10);
|
|
56
|
+
if (sig !== expectedSig) {
|
|
57
|
+
throw new Error("Invalid license signature");
|
|
58
|
+
}
|
|
59
|
+
// Expiry check (month-based)
|
|
60
|
+
const now = new Date();
|
|
61
|
+
const exp = new Date(`${expiry}-01T00:00:00Z`);
|
|
62
|
+
exp.setMonth(exp.getMonth() + 1);
|
|
63
|
+
if (now >= exp) {
|
|
64
|
+
throw new Error("License expired");
|
|
65
|
+
}
|
|
66
|
+
return { plan, scope, interval, expiry };
|
|
13
67
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateFixHints = generateFixHints;
|
|
4
|
+
function generateFixHints(findings) {
|
|
5
|
+
return findings
|
|
6
|
+
.filter((f) => f.impact === "HIGH" || f.impact === "CRITICAL")
|
|
7
|
+
.map((f) => ({
|
|
8
|
+
findingId: f.id,
|
|
9
|
+
explanation: "This issue may be exploitable. Review affected code paths and apply best-practice mitigations."
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateRecommendations = generateRecommendations;
|
|
4
|
+
function generateRecommendations(findings) {
|
|
5
|
+
return findings.map((f) => {
|
|
6
|
+
let text = "Review this finding carefully.";
|
|
7
|
+
if (f.check.includes("reentrancy")) {
|
|
8
|
+
text =
|
|
9
|
+
"Ensure all state changes occur before external calls. Consider using checks-effects-interactions or a reentrancy guard.";
|
|
10
|
+
}
|
|
11
|
+
else if (f.check.includes("delegatecall")) {
|
|
12
|
+
text =
|
|
13
|
+
"Avoid delegatecall where possible. If required, strictly validate target addresses and storage layout.";
|
|
14
|
+
}
|
|
15
|
+
else if (f.check.includes("low-level-call")) {
|
|
16
|
+
text =
|
|
17
|
+
"Validate return values of low-level calls and ensure failure handling is explicit.";
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
findingId: f.id,
|
|
21
|
+
text
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROFILES = void 0;
|
|
4
|
+
exports.applyProfile = applyProfile;
|
|
5
|
+
const IMPACT_ORDER = [
|
|
6
|
+
"LOW",
|
|
7
|
+
"MEDIUM",
|
|
8
|
+
"HIGH",
|
|
9
|
+
"CRITICAL"
|
|
10
|
+
];
|
|
11
|
+
function impactAtLeast(impact, min) {
|
|
12
|
+
if (!min)
|
|
13
|
+
return true;
|
|
14
|
+
return (IMPACT_ORDER.indexOf(impact) >= IMPACT_ORDER.indexOf(min));
|
|
15
|
+
}
|
|
16
|
+
exports.PROFILES = {
|
|
17
|
+
default: {
|
|
18
|
+
ignoreChecks: [
|
|
19
|
+
"naming-convention",
|
|
20
|
+
"solc-version",
|
|
21
|
+
"pragma",
|
|
22
|
+
"unused-return"
|
|
23
|
+
],
|
|
24
|
+
minImpact: "LOW"
|
|
25
|
+
},
|
|
26
|
+
defi: {
|
|
27
|
+
ignoreChecks: [
|
|
28
|
+
"naming-convention",
|
|
29
|
+
"pragma",
|
|
30
|
+
"unused-return",
|
|
31
|
+
"low-level-calls",
|
|
32
|
+
"solc-version"
|
|
33
|
+
],
|
|
34
|
+
minImpact: "MEDIUM"
|
|
35
|
+
},
|
|
36
|
+
protocol: {
|
|
37
|
+
ignoreChecks: [
|
|
38
|
+
"naming-convention",
|
|
39
|
+
"pragma",
|
|
40
|
+
"unused-return"
|
|
41
|
+
],
|
|
42
|
+
minImpact: "HIGH"
|
|
43
|
+
},
|
|
44
|
+
strict: {
|
|
45
|
+
minImpact: "LOW"
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
function applyProfile(findings, profile) {
|
|
49
|
+
const rules = exports.PROFILES[profile];
|
|
50
|
+
return findings.filter((f) => {
|
|
51
|
+
if (rules.ignoreChecks &&
|
|
52
|
+
rules.ignoreChecks.includes(f.check)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return impactAtLeast(f.impact, rules.minImpact);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateContestReport = generateContestReport;
|
|
4
|
+
function generateContestReport(findings) {
|
|
5
|
+
return findings.map((f, i) => `
|
|
6
|
+
## Issue ${i + 1}: ${f.check}
|
|
7
|
+
|
|
8
|
+
**Severity:** ${f.riskLevel}
|
|
9
|
+
|
|
10
|
+
### Description
|
|
11
|
+
${f.description}
|
|
12
|
+
|
|
13
|
+
### Impact
|
|
14
|
+
${f.impact}
|
|
15
|
+
|
|
16
|
+
### Proof of Concept
|
|
17
|
+
Manual review required.
|
|
18
|
+
|
|
19
|
+
### Recommendation
|
|
20
|
+
${f.recommendation ?? "No remediation suggestion provided."}
|
|
21
|
+
`).join("\n\n---\n\n");
|
|
22
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateDiffReport = generateDiffReport;
|
|
4
|
+
function generateDiffReport(diff) {
|
|
5
|
+
let md = `# 🔍 Security Diff Report\n\n`;
|
|
6
|
+
md += `## 🆕 New Findings (${diff.added.length})\n\n`;
|
|
7
|
+
for (const f of diff.added) {
|
|
8
|
+
md += `- **${f.impact}** ${f.check}\n`;
|
|
9
|
+
}
|
|
10
|
+
md += `\n## 🔺 Risk Increased (${diff.increasedRisk.length})\n\n`;
|
|
11
|
+
for (const f of diff.increasedRisk) {
|
|
12
|
+
md += `- **${f.impact}** ${f.check}\n`;
|
|
13
|
+
}
|
|
14
|
+
if (diff.added.length === 0 &&
|
|
15
|
+
diff.increasedRisk.length === 0) {
|
|
16
|
+
md += `\n✅ **No new security risks introduced**\n`;
|
|
17
|
+
}
|
|
18
|
+
return md;
|
|
19
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateExecutiveSummary = generateExecutiveSummary;
|
|
4
|
+
function generateExecutiveSummary(findings) {
|
|
5
|
+
const counts = {
|
|
6
|
+
CRITICAL: 0,
|
|
7
|
+
HIGH: 0,
|
|
8
|
+
MEDIUM: 0,
|
|
9
|
+
LOW: 0,
|
|
10
|
+
};
|
|
11
|
+
for (const f of findings) {
|
|
12
|
+
counts[f.riskLevel]++;
|
|
13
|
+
}
|
|
14
|
+
const highRiskThemes = new Set();
|
|
15
|
+
for (const f of findings) {
|
|
16
|
+
if (f.riskScore >= 7) {
|
|
17
|
+
highRiskThemes.add(f.check);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
let summary = `### Executive Summary\n\n`;
|
|
21
|
+
summary += `The automated analysis identified **${findings.length} actionable findings**. `;
|
|
22
|
+
summary += `The most significant risks are concentrated in **${counts.CRITICAL + counts.HIGH} high-impact issues**, `;
|
|
23
|
+
summary += `which should be reviewed immediately.\n\n`;
|
|
24
|
+
if (highRiskThemes.size > 0) {
|
|
25
|
+
summary += `**Primary risk themes identified:**\n`;
|
|
26
|
+
for (const theme of highRiskThemes) {
|
|
27
|
+
summary += `- ${theme}\n`;
|
|
28
|
+
}
|
|
29
|
+
summary += `\n`;
|
|
30
|
+
}
|
|
31
|
+
summary += `Lower-severity findings are primarily related to best-practice deviations and configuration hygiene.\n`;
|
|
32
|
+
return summary;
|
|
33
|
+
}
|
package/dist/report/markdown.js
CHANGED
|
@@ -2,65 +2,52 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.generateMarkdown = generateMarkdown;
|
|
4
4
|
function generateMarkdown(input) {
|
|
5
|
+
var _a;
|
|
6
|
+
let md = `# Smart Contract Audit Notes\n\n`;
|
|
7
|
+
if (input.diff) {
|
|
8
|
+
const { added, increasedRisk, unchanged } = input.diff;
|
|
9
|
+
md += `## 🔍 Pull Request Risk Summary\n\n`;
|
|
10
|
+
if (added.length === 0) {
|
|
11
|
+
md += `✅ **No new risks introduced**\n\n`;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
md += `### ❗ New Findings\n\n`;
|
|
15
|
+
for (const f of added) {
|
|
16
|
+
md += `- **${f.check}** — ${f.impact}\n`;
|
|
17
|
+
}
|
|
18
|
+
md += `\n`;
|
|
19
|
+
}
|
|
20
|
+
if (increasedRisk.length > 0) {
|
|
21
|
+
md += `### 🔺 Risk Increased\n\n`;
|
|
22
|
+
for (const f of increasedRisk) {
|
|
23
|
+
md += `- **${f.check}** — ${f.impact}\n`;
|
|
24
|
+
}
|
|
25
|
+
md += `\n`;
|
|
26
|
+
}
|
|
27
|
+
if (unchanged.length > 0) {
|
|
28
|
+
md += `### ⚠️ Existing Issues\n\n`;
|
|
29
|
+
for (const f of unchanged) {
|
|
30
|
+
md += `- ${f.check}\n`;
|
|
31
|
+
}
|
|
32
|
+
md += `\n`;
|
|
33
|
+
}
|
|
34
|
+
md += `---\n\n`;
|
|
35
|
+
}
|
|
36
|
+
md += `## Full Findings\n\n`;
|
|
37
|
+
md += `Total findings: **${input.findings.length}**\n\n`;
|
|
5
38
|
const byImpact = {};
|
|
6
39
|
for (const f of input.findings) {
|
|
7
|
-
|
|
8
|
-
byImpact[
|
|
9
|
-
byImpact[key].push(f);
|
|
40
|
+
byImpact[_a = f.impact] ?? (byImpact[_a] = []);
|
|
41
|
+
byImpact[f.impact].push(f);
|
|
10
42
|
}
|
|
11
|
-
let md = `# Smart Contract Audit Notes\n\n`;
|
|
12
|
-
md += `## Overview\n`;
|
|
13
|
-
md += `This document contains **automated audit notes** generated using static analysis tools.\n`;
|
|
14
|
-
md += `It is intended to **assist auditors and developers** during manual review.\n\n`;
|
|
15
|
-
md += `> ⚠️ **Disclaimer**\n`;
|
|
16
|
-
md += `> This report does **not** constitute a full security audit.\n`;
|
|
17
|
-
md += `> Findings represent **potential risk indicators**, not confirmed vulnerabilities.\n\n`;
|
|
18
|
-
md += `---\n\n`;
|
|
19
|
-
md += `## Scope\n`;
|
|
20
|
-
md += `- Target: Solidity smart contracts in the repository\n`;
|
|
21
|
-
md += `- Tooling: Slither (static analysis)\n`;
|
|
22
|
-
md += `- Coverage:\n`;
|
|
23
|
-
md += ` - Reentrancy patterns\n`;
|
|
24
|
-
md += ` - Low-level calls\n`;
|
|
25
|
-
md += ` - Compiler configuration issues\n`;
|
|
26
|
-
md += ` - Best-practice violations\n`;
|
|
27
|
-
md += `- Exclusions:\n`;
|
|
28
|
-
md += ` - Business logic correctness\n`;
|
|
29
|
-
md += ` - Economic & governance attacks\n`;
|
|
30
|
-
md += ` - Cross-protocol integrations\n\n`;
|
|
31
|
-
md += `---\n\n`;
|
|
32
|
-
md += `## Severity Interpretation\n`;
|
|
33
|
-
md += `- **High**: Likely exploitable patterns or critical misconfigurations\n`;
|
|
34
|
-
md += `- **Medium**: Context-dependent risks requiring review\n`;
|
|
35
|
-
md += `- **Low**: Best-practice or hygiene issues\n`;
|
|
36
|
-
md += `- **Informational**: Awareness items\n\n`;
|
|
37
|
-
md += `---\n\n`;
|
|
38
|
-
md += `## Summary\n`;
|
|
39
|
-
md += `Total findings identified: **${input.findings.length}**\n\n`;
|
|
40
43
|
for (const impact of Object.keys(byImpact)) {
|
|
41
|
-
md += `## ${impact} Severity
|
|
44
|
+
md += `## ${impact} Severity\n\n`;
|
|
42
45
|
for (const f of byImpact[impact]) {
|
|
43
46
|
md += `### ${f.check}\n\n`;
|
|
44
|
-
md += `${f.description
|
|
45
|
-
if (f.locations.length > 0) {
|
|
46
|
-
md += `**Relevant Locations:**\n`;
|
|
47
|
-
for (const l of f.locations) {
|
|
48
|
-
md += `- \`${l.contract ?? "?"}::${l.function ?? "?"}\``;
|
|
49
|
-
if (l.lines)
|
|
50
|
-
md += ` (lines ${l.lines.join(", ")})`;
|
|
51
|
-
md += `\n`;
|
|
52
|
-
}
|
|
53
|
-
md += `\n`;
|
|
54
|
-
}
|
|
47
|
+
md += `${f.description}\n\n`;
|
|
55
48
|
md += `---\n\n`;
|
|
56
49
|
}
|
|
57
50
|
}
|
|
58
|
-
md +=
|
|
59
|
-
md += `- Validate exploitability of reentrancy paths\n`;
|
|
60
|
-
md += `- Review trust assumptions around low-level calls\n`;
|
|
61
|
-
md += `- Confirm compiler version risks are acceptable\n`;
|
|
62
|
-
md += `- Ensure findings align with protocol design intent\n\n`;
|
|
63
|
-
md += `---\n\n`;
|
|
64
|
-
md += `*Generated automatically. Manual review is strongly recommended.*\n`;
|
|
51
|
+
md += `*Generated automatically. Manual review required.*\n`;
|
|
65
52
|
return md;
|
|
66
53
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateSarif = generateSarif;
|
|
4
|
+
function generateSarif(findings) {
|
|
5
|
+
return {
|
|
6
|
+
version: "2.1.0",
|
|
7
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
8
|
+
runs: [
|
|
9
|
+
{
|
|
10
|
+
tool: {
|
|
11
|
+
driver: {
|
|
12
|
+
name: "audit-notes",
|
|
13
|
+
version: "0.1.0",
|
|
14
|
+
rules: findings.map((f) => ({
|
|
15
|
+
id: f.id,
|
|
16
|
+
name: f.check,
|
|
17
|
+
shortDescription: { text: f.check },
|
|
18
|
+
fullDescription: { text: f.description },
|
|
19
|
+
defaultConfiguration: {
|
|
20
|
+
level: mapImpactToSarifLevel(f.impact)
|
|
21
|
+
}
|
|
22
|
+
}))
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
results: findings.flatMap((f) => f.locations.map((loc) => ({
|
|
26
|
+
ruleId: f.id,
|
|
27
|
+
level: mapImpactToSarifLevel(f.impact),
|
|
28
|
+
message: { text: f.description },
|
|
29
|
+
locations: loc.file
|
|
30
|
+
? [
|
|
31
|
+
{
|
|
32
|
+
physicalLocation: {
|
|
33
|
+
artifactLocation: {
|
|
34
|
+
uri: loc.file
|
|
35
|
+
},
|
|
36
|
+
region: loc.lines
|
|
37
|
+
? {
|
|
38
|
+
startLine: loc.lines[0],
|
|
39
|
+
endLine: loc.lines[loc.lines.length - 1]
|
|
40
|
+
}
|
|
41
|
+
: undefined
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
: []
|
|
46
|
+
})))
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function mapImpactToSarifLevel(impact) {
|
|
52
|
+
switch (impact) {
|
|
53
|
+
case "CRITICAL":
|
|
54
|
+
case "HIGH":
|
|
55
|
+
return "error";
|
|
56
|
+
case "MEDIUM":
|
|
57
|
+
return "warning";
|
|
58
|
+
case "LOW":
|
|
59
|
+
default:
|
|
60
|
+
return "note";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.deriveRisk = deriveRisk;
|
|
4
|
+
function deriveRisk(f) {
|
|
5
|
+
let score = 0;
|
|
6
|
+
const factors = [];
|
|
7
|
+
const text = `${f.check} ${f.description}`.toLowerCase();
|
|
8
|
+
if (text.includes("reentrancy")) {
|
|
9
|
+
score += 3;
|
|
10
|
+
factors.push("reentrancy");
|
|
11
|
+
}
|
|
12
|
+
if (text.includes("delegatecall")) {
|
|
13
|
+
score += 3;
|
|
14
|
+
factors.push("delegatecall");
|
|
15
|
+
}
|
|
16
|
+
if (text.includes("low level call")) {
|
|
17
|
+
score += 2;
|
|
18
|
+
factors.push("low-level-call");
|
|
19
|
+
}
|
|
20
|
+
if (text.includes("state")) {
|
|
21
|
+
score += 1;
|
|
22
|
+
factors.push("state-mutation");
|
|
23
|
+
}
|
|
24
|
+
let level = "LOW";
|
|
25
|
+
if (score >= 6)
|
|
26
|
+
level = "CRITICAL";
|
|
27
|
+
else if (score >= 4)
|
|
28
|
+
level = "HIGH";
|
|
29
|
+
else if (score >= 2)
|
|
30
|
+
level = "MEDIUM";
|
|
31
|
+
return { score, level, factors };
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.scoreRisk = scoreRisk;
|
|
4
|
+
function scoreRisk(finding, factors) {
|
|
5
|
+
let score = 0;
|
|
6
|
+
if (factors.externalCall)
|
|
7
|
+
score += 3;
|
|
8
|
+
if (factors.usesDelegatecall)
|
|
9
|
+
score += 4;
|
|
10
|
+
if (factors.userControlledInput)
|
|
11
|
+
score += 3;
|
|
12
|
+
if (factors.mutatesState)
|
|
13
|
+
score += 2;
|
|
14
|
+
let impact = "LOW";
|
|
15
|
+
if (score >= 8)
|
|
16
|
+
impact = "CRITICAL";
|
|
17
|
+
else if (score >= 6)
|
|
18
|
+
impact = "HIGH";
|
|
19
|
+
else if (score >= 4)
|
|
20
|
+
impact = "MEDIUM";
|
|
21
|
+
return {
|
|
22
|
+
...finding,
|
|
23
|
+
riskScore: score,
|
|
24
|
+
impact
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.computeRiskScore = computeRiskScore;
|
|
4
|
+
exports.riskBand = riskBand;
|
|
5
|
+
const WEIGHTS = {
|
|
6
|
+
usesAssembly: 1,
|
|
7
|
+
usesDelegatecall: 3,
|
|
8
|
+
externalCall: 2,
|
|
9
|
+
mutatesState: 2,
|
|
10
|
+
highComplexity: 2,
|
|
11
|
+
touchesCoreContract: 3,
|
|
12
|
+
userControlledInput: 3
|
|
13
|
+
};
|
|
14
|
+
function computeRiskScore(factors) {
|
|
15
|
+
let score = 0;
|
|
16
|
+
for (const [k, v] of Object.entries(factors)) {
|
|
17
|
+
if (v)
|
|
18
|
+
score += WEIGHTS[k];
|
|
19
|
+
}
|
|
20
|
+
return Math.min(score, 10);
|
|
21
|
+
}
|
|
22
|
+
function riskBand(score) {
|
|
23
|
+
if (score >= 8)
|
|
24
|
+
return "CRITICAL";
|
|
25
|
+
if (score >= 6)
|
|
26
|
+
return "HIGH";
|
|
27
|
+
if (score >= 4)
|
|
28
|
+
return "MEDIUM";
|
|
29
|
+
return "LOW";
|
|
30
|
+
}
|