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 CHANGED
@@ -1,41 +1,250 @@
1
- # AI-Assisted Audit
2
-
3
- Professional **security pre-review tooling** for smart contracts.
4
-
5
- This is NOT:
6
- - an AI auditor
7
- - a vulnerability scanner
8
- - severity scoring software
9
-
10
- This IS:
11
- - a thinking assistant for auditors
12
- - deterministic security analysis
13
- - structured audit context before full audits
14
-
15
- ---
16
-
17
- ## What it generates
18
- - Privileged role map
19
- - External call & reentrancy surfaces
20
- - State mutation & invariant hints
21
- - Upgradeability risks
22
- - Critical audit questions
23
-
24
- Output: `AUDIT_NOTES.md`
25
-
26
- ---
27
-
28
- ## Why this exists
29
- Most audit issues are missed due to **lost context**, not missing tools.
30
-
31
- This tool extracts structure so humans reason better.
32
-
33
- ---
34
-
35
- ## Usage (CLI)
36
-
37
- ```bash
38
- slither . --json slither.json
39
- export AUDIT_NOTES_LICENSE=AN-XXXX
40
- export OPENAI_API_KEY=sk-...
41
- audit-notes .
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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
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 prompt_1 = require("./llm/prompt");
14
- const client_1 = require("./llm/client");
15
- const rules_schema_json_1 = __importDefault(require("./rules/rules.schema.json"));
16
- const cmd = process.argv[2] ?? "run";
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
- (0, license_1.validateLicense)();
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. Run `slither <path> --json slither.json` first.");
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
- const slither = (0, slither_1.loadSlither)(slitherPath);
24
- const findings = (0, detectors_1.analyzeDetectors)(slither.detectors);
25
- let report;
26
- if (process.env.OPENAI_API_KEY) {
27
- const prompt = (0, prompt_1.buildPrompt)({ findings }, rules_schema_json_1.default);
28
- report = await (0, client_1.generateAuditNotes)(prompt);
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
- report = (0, markdown_1.generateMarkdown)({ findings });
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
- try {
39
- if (cmd === "run") {
40
- await run();
41
- }
42
- else if (cmd === "check") {
43
- (0, license_1.validateLicense)();
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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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("Missing license key. Set AUDIT_NOTES_LICENSE environment variable.");
15
+ throw new Error("This feature requires Audit Notes PRO. Set AUDIT_NOTES_LICENSE to continue.");
8
16
  }
9
- if (key.length < 20 || !key.startsWith("AN-")) {
10
- throw new Error("Invalid license key format.");
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
- return true;
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,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
+ }
@@ -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
- const key = f.impact ?? "Unknown";
8
- byImpact[key] ?? (byImpact[key] = []);
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 Findings\n\n`;
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.trim()}\n\n`;
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 += `## Auditor Review Checklist\n`;
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
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // src/risk/types.ts
3
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "audit-notes-cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "commonjs",
5
5
  "bin": {
6
6
  "audit-notes": "dist/cli.js"