solaudit 0.1.2 → 0.2.0
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 +78 -70
- package/dist/cli.js +80 -22
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
# SolAudit
|
|
2
2
|
|
|
3
3
|
[](./LICENSE)
|
|
4
|
-
[](https://www.npmjs.com/package/solaudit)
|
|
5
5
|
[](./.github/workflows/ci.yml)
|
|
6
6
|
|
|
7
|
-
**A zero-dependency
|
|
7
|
+
**A zero-dependency static-analysis engine that scans Anchor/Solana Rust programs for the vulnerability patterns behind the most common on-chain exploits — available as a CLI and as a free web app.**
|
|
8
8
|
|
|
9
|
-
SolAudit reads
|
|
9
|
+
SolAudit reads `.rs` source files, applies a set of pattern-matching security rules (missing signer checks, arbitrary CPIs, unchecked deserialization, integer overflow, and more), and gives you an actionable report in seconds — no RPC calls, no wallet, no build step required to run it.
|
|
10
|
+
|
|
11
|
+
- **CLI** — `npx solaudit ./program`, for local development and CI.
|
|
12
|
+
- **Web app** — [solaudit-web.vercel.app](https://solaudit-web.vercel.app/) — paste a public GitHub repo URL or upload a single `.rs` file, no install required.
|
|
10
13
|
|
|
11
14
|
---
|
|
12
15
|
|
|
13
|
-
##
|
|
16
|
+
## Repository layout
|
|
17
|
+
|
|
18
|
+
This is a Bun workspaces monorepo. The CLI and the web app share one scanning engine, so there is a single source of truth for every rule — no drift between the two.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
packages/core/ Shared engine: rules, scanner, output formatters (published nowhere; workspace-only)
|
|
22
|
+
cli/ npm package "solaudit" — the command-line tool
|
|
23
|
+
web/ Next.js app deployed to Vercel — the web version
|
|
24
|
+
examples/ Intentionally vulnerable sample Anchor program, used by both the CLI and web tests
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## CLI
|
|
14
28
|
|
|
15
29
|
```bash
|
|
16
30
|
# Run instantly with npx (no install)
|
|
@@ -19,15 +33,8 @@ npx solaudit ./program
|
|
|
19
33
|
# Or install globally with Bun
|
|
20
34
|
bun add -g solaudit
|
|
21
35
|
solaudit ./program
|
|
22
|
-
|
|
23
|
-
# Or clone and run from source
|
|
24
|
-
git clone https://github.com/blueeyez425-art/solaudit.git
|
|
25
|
-
cd solaudit
|
|
26
|
-
bun run solaudit ./examples
|
|
27
36
|
```
|
|
28
37
|
|
|
29
|
-
## Usage
|
|
30
|
-
|
|
31
38
|
```bash
|
|
32
39
|
solaudit [path] [options]
|
|
33
40
|
```
|
|
@@ -46,8 +53,8 @@ $ solaudit ./examples
|
|
|
46
53
|
```
|
|
47
54
|
|
|
48
55
|
```
|
|
49
|
-
SolAudit v0.
|
|
50
|
-
Scanned 1 file(s) at 2026-08-
|
|
56
|
+
SolAudit v0.2.0 — static analysis for Anchor/Solana programs
|
|
57
|
+
Scanned 1 file(s) at 2026-08-27T06:00:00.000Z
|
|
51
58
|
──────────────────────────────────────────────────────────────────────
|
|
52
59
|
|
|
53
60
|
CRITICAL (2)
|
|
@@ -57,26 +64,6 @@ CRITICAL (2)
|
|
|
57
64
|
└─ Verify the authority/owner account is a Signer or explicitly
|
|
58
65
|
check `.is_signer` before transferring or minting tokens.
|
|
59
66
|
|
|
60
|
-
HIGH (3)
|
|
61
|
-
HIGH SOL-003 Arbitrary CPI (Cross-Program Invocation)
|
|
62
|
-
examples/vulnerable.rs:68
|
|
63
|
-
│ invoke(&ix, &[ctx.accounts.target_program.to_account_info()])?;
|
|
64
|
-
└─ Validate the target program ID against a known constant or type
|
|
65
|
-
the account as `Program<'info, T>` before invoking it.
|
|
66
|
-
|
|
67
|
-
HIGH SOL-004 Missing Bump Seed Canonicalization
|
|
68
|
-
examples/vulnerable.rs:76
|
|
69
|
-
│ let signer_pda = Pubkey::create_program_address(seeds, ctx.program_id)
|
|
70
|
-
└─ Use `Pubkey::find_program_address` to derive the canonical bump
|
|
71
|
-
instead of calling `create_program_address` directly.
|
|
72
|
-
|
|
73
|
-
MEDIUM (3)
|
|
74
|
-
MEDIUM SOL-005 Integer Overflow Risk
|
|
75
|
-
examples/vulnerable.rs:27
|
|
76
|
-
│ vault.total_staked = vault.total_staked - amount;
|
|
77
|
-
└─ Replace raw arithmetic with `.checked_sub()` / `.checked_add()`
|
|
78
|
-
or `saturating_*` equivalents.
|
|
79
|
-
|
|
80
67
|
──────────────────────────────────────────────────────────────────────
|
|
81
68
|
Summary
|
|
82
69
|
CRITICAL 2 HIGH 3 MEDIUM 3 LOW 6 INFO 0
|
|
@@ -85,6 +72,24 @@ Summary
|
|
|
85
72
|
|
|
86
73
|
Exit code is `1` when any `CRITICAL` or `HIGH` finding is present — making SolAudit a natural CI gate.
|
|
87
74
|
|
|
75
|
+
## Web app
|
|
76
|
+
|
|
77
|
+
**Live at [solaudit-web.vercel.app](https://solaudit-web.vercel.app/)** — no CLI, no local Anchor toolchain, no wallet required:
|
|
78
|
+
|
|
79
|
+
1. Paste a public GitHub repo URL **or** upload a single `.rs` file.
|
|
80
|
+
2. The server fetches `.rs` files via GitHub's API (no `git clone`) and scans them with the exact same engine as the CLI.
|
|
81
|
+
3. Get a report grouped by severity, right in the browser.
|
|
82
|
+
|
|
83
|
+
Optional: set `GITHUB_TOKEN` (see `web/.env.example`) to raise the GitHub API rate limit from 60 to 5,000 requests/hour. No special token scopes are needed for public repos.
|
|
84
|
+
|
|
85
|
+
### Running the web app locally
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
bun install # from the repo root — links the workspace packages
|
|
89
|
+
cd web
|
|
90
|
+
bun run dev
|
|
91
|
+
```
|
|
92
|
+
|
|
88
93
|
## Rules
|
|
89
94
|
|
|
90
95
|
| ID | Severity | Name | What it catches |
|
|
@@ -97,79 +102,82 @@ Exit code is `1` when any `CRITICAL` or `HIGH` finding is present — making Sol
|
|
|
97
102
|
| `SOL-006` | HIGH | Type Cosplay / Missing Discriminator Check | Manual `try_from_slice` deserialization with no discriminator validation or `Account<'info, T>` wrapper |
|
|
98
103
|
| `SOL-007` | MEDIUM | Insecure Account Initialization | `#[account(init, ...)]` constraints missing `payer` and/or `space` |
|
|
99
104
|
| `SOL-008` | LOW | Missing Owner Check | Raw `AccountInfo` fields used without a nearby `.owner` comparison |
|
|
105
|
+
| `SOL-009` | HIGH | Insecure Manual Account Closure | An account's lamports manually drained to zero without discriminator zeroing or owner reassignment (unlike Anchor's `close = destination`), enabling closed-account revival |
|
|
106
|
+
| `SOL-011` | MEDIUM | Missing Rent-Exemption Check | A partial lamport withdrawal with no nearby `Rent::get()?.minimum_balance(...)` check, risking runtime garbage-collection of the account |
|
|
100
107
|
|
|
101
|
-
Each rule is implemented as real regex/line-scanning logic in [`src/rules/index.ts`](./src/rules/index.ts) — see that file (and its accompanying [tests](./src/rules/index.test.ts)) for the exact matching heuristics and false-positive guards.
|
|
108
|
+
Each rule is implemented as real regex/line-scanning logic in [`packages/core/src/rules/index.ts`](./packages/core/src/rules/index.ts) — see that file (and its accompanying [tests](./packages/core/src/rules/index.test.ts)) for the exact matching heuristics and false-positive guards.
|
|
102
109
|
|
|
103
|
-
##
|
|
110
|
+
## Accuracy
|
|
104
111
|
|
|
105
|
-
|
|
112
|
+
On the committed labeled suite in [`benchmark/`](./benchmark), the deterministic engine scores **93.8% precision, 100% recall, 96.8% F1** on rule-covered categories.
|
|
106
113
|
|
|
107
|
-
|
|
108
|
-
- **JSON** (`--json`) — the full `ScanResult` object (`scannedFiles`, `findings[]`, `summary`), for piping into other tooling.
|
|
109
|
-
- **SARIF** (`--sarif`) — [SARIF 2.1.0](https://sarifweb.azurewebsites.net/) output compatible with GitHub Code Scanning, so findings show up as annotations directly on your pull requests.
|
|
114
|
+
One known gap remains: **duplicate mutable accounts**. A regex “distinctness” heuristic produced false positives on common legitimate patterns (for example a deposit instruction’s user/vault token accounts of the same type), so that rule was not shipped. These numbers describe this suite, not general security effectiveness.
|
|
110
115
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
116
|
+
## Output formats (CLI)
|
|
117
|
+
|
|
118
|
+
- **Human** (default) — colorized terminal report grouped by severity.
|
|
119
|
+
- **JSON** (`--json`) — the full `ScanResult` object (`scannedFiles`, `findings[]`, `summary`).
|
|
120
|
+
- **SARIF** (`--sarif`) — [SARIF 2.1.0](https://sarifweb.azurewebsites.net/) output compatible with GitHub Code Scanning.
|
|
114
121
|
|
|
115
122
|
## GitHub Actions
|
|
116
123
|
|
|
117
|
-
|
|
124
|
+
SolAudit ships as a ready-to-use [GitHub Action](./action.yml). Drop this into `.github/workflows/solaudit.yml` in your own repo and every push/PR gets scanned automatically, with results uploaded straight to your repo's Security tab:
|
|
118
125
|
|
|
119
126
|
```yaml
|
|
120
127
|
name: SolAudit
|
|
121
128
|
|
|
122
129
|
on: [push, pull_request]
|
|
123
130
|
|
|
131
|
+
permissions:
|
|
132
|
+
contents: read
|
|
133
|
+
security-events: write # required to upload SARIF results
|
|
134
|
+
|
|
124
135
|
jobs:
|
|
125
136
|
scan:
|
|
126
137
|
runs-on: ubuntu-latest
|
|
127
138
|
steps:
|
|
128
139
|
- uses: actions/checkout@v4
|
|
129
|
-
- uses:
|
|
130
|
-
with:
|
|
131
|
-
bun-version: latest
|
|
132
|
-
- run: bun install
|
|
133
|
-
- run: bun src/cli.ts ./programs --sarif > results.sarif
|
|
134
|
-
- uses: github/codeql-action/upload-sarif@v3
|
|
140
|
+
- uses: blueeyez425-art/solaudit@main
|
|
135
141
|
with:
|
|
136
|
-
|
|
142
|
+
path: ./programs # file or directory to scan (default: .)
|
|
143
|
+
min-severity: INFO # lowest severity to report (default: INFO)
|
|
144
|
+
fail-on-findings: true # fail the build on any CRITICAL/HIGH finding (default: true)
|
|
145
|
+
upload-sarif: true # upload results to code scanning (default: true)
|
|
137
146
|
```
|
|
138
147
|
|
|
139
|
-
|
|
148
|
+
No `security-events: write` permission in your workflow? Set `upload-sarif: false` and the action will still run the scan and fail the build on CRITICAL/HIGH findings — it just won't publish to the Security tab.
|
|
140
149
|
|
|
141
|
-
|
|
150
|
+
Prefer to wire it up manually instead of using the packaged action:
|
|
142
151
|
|
|
152
|
+
```yaml
|
|
153
|
+
- uses: actions/checkout@v4
|
|
154
|
+
- run: npx solaudit ./programs --sarif > results.sarif
|
|
155
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
156
|
+
with:
|
|
157
|
+
sarif_file: results.sarif
|
|
143
158
|
```
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
rules/index.ts # The 8 vulnerability rules (pattern-matching logic)
|
|
147
|
-
scanner.ts # File/directory walking + aggregation
|
|
148
|
-
output/report.ts # Colored terminal report
|
|
149
|
-
output/sarif.ts # SARIF 2.1.0 conversion
|
|
150
|
-
cli.ts # Argument parsing + entrypoint
|
|
151
|
-
examples/vulnerable.rs # Intentionally vulnerable sample Anchor program
|
|
152
|
-
```
|
|
159
|
+
|
|
160
|
+
This repository's own [`.github/workflows/ci.yml`](./.github/workflows/ci.yml) runs the full test suite, builds the CLI and web app, and uses this repo's own action against `examples/` on every push.
|
|
153
161
|
|
|
154
162
|
## Development
|
|
155
163
|
|
|
156
164
|
```bash
|
|
157
|
-
bun install
|
|
158
|
-
bun test
|
|
159
|
-
|
|
160
|
-
bun run build
|
|
165
|
+
bun install # installs and links all workspaces
|
|
166
|
+
cd packages/core && bun test # run the engine's unit test suite
|
|
167
|
+
cd cli && bun run build # bundle cli/src/cli.ts -> cli/dist/cli.js
|
|
168
|
+
cd web && bun run build # production-build the web app
|
|
161
169
|
```
|
|
162
170
|
|
|
163
171
|
## Contributing
|
|
164
172
|
|
|
165
|
-
Contributions are very welcome — new rules, false-positive fixes, additional output formats, or
|
|
173
|
+
Contributions are very welcome — new rules, false-positive fixes, additional output formats, or web UI improvements are all in scope.
|
|
166
174
|
|
|
167
175
|
1. Fork the repo and create a branch from `main`.
|
|
168
|
-
2. Add or update a rule in `src/rules/index.ts` (each rule is a self-contained `Rule` object).
|
|
169
|
-
3. Add unit tests covering both a triggering and a non-triggering case (`src/rules/index.test.ts`).
|
|
170
|
-
4. Run `bun test`
|
|
176
|
+
2. Add or update a rule in `packages/core/src/rules/index.ts` (each rule is a self-contained `Rule` object).
|
|
177
|
+
3. Add unit tests covering both a triggering and a non-triggering case (`packages/core/src/rules/index.test.ts`).
|
|
178
|
+
4. Run `bun test` (from `packages/core`) before opening a PR.
|
|
171
179
|
|
|
172
|
-
Please open an issue first for larger changes
|
|
180
|
+
Please open an issue first for larger changes so we can discuss the approach.
|
|
173
181
|
|
|
174
182
|
## License
|
|
175
183
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/rules/index.ts
|
|
3
|
+
// ../packages/core/src/rules/index.ts
|
|
4
4
|
function makeFinding(rule, filePath, line, snippet, description) {
|
|
5
5
|
return {
|
|
6
6
|
ruleId: rule.id,
|
|
@@ -15,6 +15,10 @@ function makeFinding(rule, filePath, line, snippet, description) {
|
|
|
15
15
|
function splitLines(fileContent) {
|
|
16
16
|
return fileContent.split(/\r\n|\r|\n/);
|
|
17
17
|
}
|
|
18
|
+
function stripLineComments(text) {
|
|
19
|
+
return text.split(/\r\n|\r|\n/).map((line) => line.replace(/\/\/.*$/, "")).join(`
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
18
22
|
var missingSignerCheck = {
|
|
19
23
|
id: "SOL-001",
|
|
20
24
|
name: "Missing Signer Check",
|
|
@@ -215,6 +219,56 @@ var missingOwnerCheck = {
|
|
|
215
219
|
return findings;
|
|
216
220
|
}
|
|
217
221
|
};
|
|
222
|
+
var insecureAccountClosure = {
|
|
223
|
+
id: "SOL-009",
|
|
224
|
+
name: "Insecure Manual Account Closure",
|
|
225
|
+
severity: "HIGH",
|
|
226
|
+
description: "An account's lamports were manually drained to zero without zeroing its data/discriminator or reassigning ownership to the System Program, unlike Anchor's `close = destination` constraint (which does both automatically). A 'closed' account that still carries its original discriminator and data can be revived/reused within the same or a later transaction before garbage collection, letting an attacker re-claim already-withdrawn funds or reopen closed state. Use `#[account(mut, close = destination)]` instead of manual lamport-draining, or manually zero the account's discriminator/data and reassign owner to the System Program.",
|
|
227
|
+
check(fileContent, filePath) {
|
|
228
|
+
const findings = [];
|
|
229
|
+
const lines = splitLines(fileContent);
|
|
230
|
+
const zeroLamportsPattern = /\.lamports\.borrow_mut\(\)\s*=\s*0\s*;/;
|
|
231
|
+
lines.forEach((line, idx) => {
|
|
232
|
+
if (!zeroLamportsPattern.test(line))
|
|
233
|
+
return;
|
|
234
|
+
const windowStart = Math.max(0, idx - 15);
|
|
235
|
+
const windowEnd = Math.min(lines.length, idx + 15);
|
|
236
|
+
const window = stripLineComments(lines.slice(windowStart, windowEnd).join(`
|
|
237
|
+
`));
|
|
238
|
+
const hasSafeClosure = /close\s*=/.test(window) || /discriminator/i.test(window) || /CLOSED_ACCOUNT_DISCRIMINATOR/.test(window) || /\.data\.borrow_mut\(\)/.test(window);
|
|
239
|
+
if (!hasSafeClosure) {
|
|
240
|
+
findings.push(makeFinding(insecureAccountClosure, filePath, idx + 1, line, "Use Anchor's `#[account(mut, close = destination)]` constraint, or manually zero the account's 8-byte discriminator and reassign its owner to the System Program, before treating this account as closed."));
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
return findings;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
var missingRentExemptCheck = {
|
|
247
|
+
id: "SOL-011",
|
|
248
|
+
name: "Missing Rent-Exemption Check on Partial Withdrawal",
|
|
249
|
+
severity: "MEDIUM",
|
|
250
|
+
description: "Lamports are subtracted from an account's balance (a partial withdrawal) with no nearby check against `Rent::get()?.minimum_balance(...)`. If the withdrawal can drop the account below the rent-exempt threshold, the runtime may purge the account on the next epoch boundary, destroying any remaining state and potentially enabling accounting-drift or re-initialization bugs. Compare the post-withdrawal balance against the account's rent-exempt minimum before allowing a partial (non-zeroing) withdrawal.",
|
|
251
|
+
check(fileContent, filePath) {
|
|
252
|
+
const findings = [];
|
|
253
|
+
const lines = splitLines(fileContent);
|
|
254
|
+
const partialWithdrawPattern = /\.lamports\.borrow_mut\(\)\s*=\s*[\w.]+\.lamports\(\)\.checked_sub\(/;
|
|
255
|
+
lines.forEach((line, idx) => {
|
|
256
|
+
if (!partialWithdrawPattern.test(line))
|
|
257
|
+
return;
|
|
258
|
+
if (/=\s*0\s*;/.test(line))
|
|
259
|
+
return;
|
|
260
|
+
const windowStart = Math.max(0, idx - 15);
|
|
261
|
+
const windowEnd = Math.min(lines.length, idx + 15);
|
|
262
|
+
const window = stripLineComments(lines.slice(windowStart, windowEnd).join(`
|
|
263
|
+
`));
|
|
264
|
+
const hasRentCheck = /Rent::get\(\)|minimum_balance/.test(window);
|
|
265
|
+
if (!hasRentCheck) {
|
|
266
|
+
findings.push(makeFinding(missingRentExemptCheck, filePath, idx + 1, line, "Check the resulting balance against `Rent::get()?.minimum_balance(account.data_len())` before allowing a partial lamport withdrawal, or require the withdrawal to fully close the account via `close = destination`."));
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
return findings;
|
|
270
|
+
}
|
|
271
|
+
};
|
|
218
272
|
var rules = [
|
|
219
273
|
missingSignerCheck,
|
|
220
274
|
uncheckedDeserialization,
|
|
@@ -223,16 +277,16 @@ var rules = [
|
|
|
223
277
|
integerOverflowRisk,
|
|
224
278
|
typeCosplay,
|
|
225
279
|
insecureInit,
|
|
226
|
-
missingOwnerCheck
|
|
280
|
+
missingOwnerCheck,
|
|
281
|
+
insecureAccountClosure,
|
|
282
|
+
missingRentExemptCheck
|
|
227
283
|
];
|
|
228
|
-
|
|
229
|
-
// src/scanner.ts
|
|
284
|
+
// ../packages/core/src/scanner.ts
|
|
230
285
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
231
286
|
import { join } from "node:path";
|
|
232
|
-
function
|
|
233
|
-
const content = readFileSync(filePath, "utf8");
|
|
287
|
+
function scanContent(content, filePath, rules) {
|
|
234
288
|
const findings = [];
|
|
235
|
-
for (const rule of
|
|
289
|
+
for (const rule of rules) {
|
|
236
290
|
try {
|
|
237
291
|
const ruleFindings = rule.check(content, filePath);
|
|
238
292
|
findings.push(...ruleFindings);
|
|
@@ -242,6 +296,10 @@ function scanFile(filePath, rules2) {
|
|
|
242
296
|
}
|
|
243
297
|
return findings.sort((a, b) => a.line - b.line);
|
|
244
298
|
}
|
|
299
|
+
function scanFile(filePath, rules) {
|
|
300
|
+
const content = readFileSync(filePath, "utf8");
|
|
301
|
+
return scanContent(content, filePath, rules);
|
|
302
|
+
}
|
|
245
303
|
function walk(dirPath, files = []) {
|
|
246
304
|
const entries = readdirSync(dirPath);
|
|
247
305
|
for (const entry of entries) {
|
|
@@ -283,13 +341,7 @@ function tallySummary(findings) {
|
|
|
283
341
|
}
|
|
284
342
|
return summary;
|
|
285
343
|
}
|
|
286
|
-
|
|
287
|
-
const stat = statSync(targetPath);
|
|
288
|
-
const rsFiles = stat.isDirectory() ? walk(targetPath) : targetPath.endsWith(".rs") ? [targetPath] : [];
|
|
289
|
-
const allFindings = [];
|
|
290
|
-
for (const file of rsFiles) {
|
|
291
|
-
allFindings.push(...scanFile(file, rules2));
|
|
292
|
-
}
|
|
344
|
+
function sortAndSummarize(rsFileCount, allFindings) {
|
|
293
345
|
const severityOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, INFO: 4 };
|
|
294
346
|
allFindings.sort((a, b) => {
|
|
295
347
|
const sev = severityOrder[a.severity] - severityOrder[b.severity];
|
|
@@ -300,13 +352,21 @@ async function scanDirectory(targetPath, rules2) {
|
|
|
300
352
|
return a.line - b.line;
|
|
301
353
|
});
|
|
302
354
|
return {
|
|
303
|
-
scannedFiles:
|
|
355
|
+
scannedFiles: rsFileCount,
|
|
304
356
|
findings: allFindings,
|
|
305
357
|
summary: tallySummary(allFindings)
|
|
306
358
|
};
|
|
307
359
|
}
|
|
308
|
-
|
|
309
|
-
|
|
360
|
+
async function scanDirectory(targetPath, rules) {
|
|
361
|
+
const stat = statSync(targetPath);
|
|
362
|
+
const rsFiles = stat.isDirectory() ? walk(targetPath) : targetPath.endsWith(".rs") ? [targetPath] : [];
|
|
363
|
+
const allFindings = [];
|
|
364
|
+
for (const file of rsFiles) {
|
|
365
|
+
allFindings.push(...scanFile(file, rules));
|
|
366
|
+
}
|
|
367
|
+
return sortAndSummarize(rsFiles.length, allFindings);
|
|
368
|
+
}
|
|
369
|
+
// ../packages/core/src/output/report.ts
|
|
310
370
|
var RESET = "\x1B[0m";
|
|
311
371
|
var BOLD = "\x1B[1m";
|
|
312
372
|
var DIM = "\x1B[2m";
|
|
@@ -338,7 +398,7 @@ function badge(severity) {
|
|
|
338
398
|
var SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"];
|
|
339
399
|
function printReport(result) {
|
|
340
400
|
const timestamp = new Date().toISOString();
|
|
341
|
-
console.log(`${BOLD}${CYAN}SolAudit${RESET} ${DIM}v0.
|
|
401
|
+
console.log(`${BOLD}${CYAN}SolAudit${RESET} ${DIM}v0.2.0 — static analysis for Anchor/Solana programs${RESET}`);
|
|
342
402
|
console.log(`${DIM}Scanned ${result.scannedFiles} file(s) at ${timestamp}${RESET}`);
|
|
343
403
|
console.log(`${GRAY}${"─".repeat(70)}${RESET}`);
|
|
344
404
|
if (result.findings.length === 0) {
|
|
@@ -371,10 +431,9 @@ function printReport(result) {
|
|
|
371
431
|
console.log(` ${DIM}Total findings: ${result.findings.length} across ${result.scannedFiles} file(s)${RESET}`);
|
|
372
432
|
console.log("");
|
|
373
433
|
}
|
|
374
|
-
|
|
375
|
-
// src/output/sarif.ts
|
|
434
|
+
// ../packages/core/src/output/sarif.ts
|
|
376
435
|
var SARIF_VERSION = "2.1.0";
|
|
377
|
-
var TOOL_VERSION = "0.
|
|
436
|
+
var TOOL_VERSION = "0.2.0";
|
|
378
437
|
function severityToSarifLevel(severity) {
|
|
379
438
|
switch (severity) {
|
|
380
439
|
case "CRITICAL":
|
|
@@ -429,7 +488,6 @@ function toSarif(result) {
|
|
|
429
488
|
]
|
|
430
489
|
};
|
|
431
490
|
}
|
|
432
|
-
|
|
433
491
|
// src/cli.ts
|
|
434
492
|
var SEVERITY_RANK = {
|
|
435
493
|
CRITICAL: 0,
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "solaudit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Open-source static analysis CLI for Anchor/Solana programs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"solaudit": "dist/cli.js"
|
|
8
8
|
},
|
|
9
|
-
"main": "./dist/
|
|
9
|
+
"main": "./dist/cli.js",
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"examples",
|
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "bun build src/cli.ts --outdir dist --target node --sourcemap=none",
|
|
21
|
-
"prepublishOnly": "
|
|
22
|
-
"start": "bun src/cli.ts"
|
|
23
|
-
"test": "bun test"
|
|
21
|
+
"prepublishOnly": "bun run build && cp -r ../examples . && cp ../README.md ../LICENSE .",
|
|
22
|
+
"start": "bun src/cli.ts"
|
|
24
23
|
},
|
|
25
24
|
"keywords": [
|
|
26
25
|
"solana",
|
|
@@ -35,7 +34,9 @@
|
|
|
35
34
|
"type": "git",
|
|
36
35
|
"url": "git+https://github.com/blueeyez425-art/solaudit.git"
|
|
37
36
|
},
|
|
37
|
+
"dependencies": {},
|
|
38
38
|
"devDependencies": {
|
|
39
|
+
"@solaudit/core": "workspace:*",
|
|
39
40
|
"bun-types": "latest"
|
|
40
41
|
}
|
|
41
42
|
}
|