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.
Files changed (3) hide show
  1. package/README.md +78 -70
  2. package/dist/cli.js +80 -22
  3. package/package.json +6 -5
package/README.md CHANGED
@@ -1,16 +1,30 @@
1
1
  # SolAudit
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
4
- [![npm version](https://img.shields.io/badge/npm-v0.1.0-blue.svg)](https://www.npmjs.com/package/solaudit)
4
+ [![npm version](https://img.shields.io/badge/npm-v0.2.0-blue.svg)](https://www.npmjs.com/package/solaudit)
5
5
  [![build](https://img.shields.io/badge/build-passing-brightgreen.svg)](./.github/workflows/ci.yml)
6
6
 
7
- **A zero-dependency, static-analysis CLI that scans Anchor/Solana Rust programs for the vulnerability patterns behind the most common on-chain exploits.**
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 your `.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 — with no external dependencies, no RPC calls, and no build step required to run it.
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
- ## Install
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.1.0 — static analysis for Anchor/Solana programs
50
- Scanned 1 file(s) at 2026-08-22T18:32:37.205Z
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
- ## Output formats
110
+ ## Accuracy
104
111
 
105
- SolAudit supports three output modes, so it fits into a terminal, a script, or a CI pipeline:
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
- - **Human** (default) — colorized terminal report grouped by severity, for local development.
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
- ```bash
112
- solaudit ./programs --sarif > results.sarif
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
- Add SolAudit to your CI so every push and PR is scanned automatically:
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: oven-sh/setup-bun@v2
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
- sarif_file: results.sarif
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
- This repository's own [`.github/workflows/ci.yml`](./.github/workflows/ci.yml) runs SolAudit against [`examples/vulnerable.rs`](./examples/vulnerable.rs) on every push.
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
- ## Project layout
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
- src/
145
- types.ts # Finding, Rule, ScanResult types
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 # run the unit test suite
159
- bun run start ./examples # run the CLI against the bundled example
160
- bun run build # bundle src/cli.ts -> dist/cli.js
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 better heuristics are all in scope.
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` and `bun run build` before opening a PR.
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 (e.g. new output formats or a plugin system) so we can discuss the approach.
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 scanFile(filePath, rules2) {
233
- const content = readFileSync(filePath, "utf8");
287
+ function scanContent(content, filePath, rules) {
234
288
  const findings = [];
235
- for (const rule of rules2) {
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
- async function scanDirectory(targetPath, rules2) {
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: rsFiles.length,
355
+ scannedFiles: rsFileCount,
304
356
  findings: allFindings,
305
357
  summary: tallySummary(allFindings)
306
358
  };
307
359
  }
308
-
309
- // src/output/report.ts
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.1.0 — static analysis for Anchor/Solana programs${RESET}`);
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.1.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.1.2",
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/index.js",
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": "npm run build",
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
  }