anchor-audit 0.1.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 +28 -0
- package/dist/auditor.d.ts +16 -0
- package/dist/auditor.js +235 -0
- package/dist/auditor.js.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/metadata.d.ts +25 -0
- package/dist/metadata.js +48 -0
- package/dist/metadata.js.map +1 -0
- package/dist/reporter.d.ts +18 -0
- package/dist/reporter.js +177 -0
- package/dist/reporter.js.map +1 -0
- package/dist/rules-loader.d.ts +12 -0
- package/dist/rules-loader.js +65 -0
- package/dist/rules-loader.js.map +1 -0
- package/dist/scanner.d.ts +6 -0
- package/dist/scanner.js +42 -0
- package/dist/scanner.js.map +1 -0
- package/package.json +41 -0
- package/rules/001-missing-signer-check.md +57 -0
- package/rules/002-missing-owner-check.md +53 -0
- package/rules/003-missing-discriminator-check.md +53 -0
- package/rules/004-account-substitution.md +54 -0
- package/rules/005-sysvar-spoofing.md +57 -0
- package/rules/006-missing-rent-exemption-check.md +47 -0
- package/rules/007-account-aliasing.md +53 -0
- package/rules/008-uninitialized-account-use.md +53 -0
- package/rules/009-missing-mut-constraint.md +52 -0
- package/rules/010-missing-close-constraint.md +48 -0
- package/rules/011-pda-seed-collision.md +49 -0
- package/rules/012-missing-bump-validation.md +55 -0
- package/rules/013-non-canonical-bump-accepted.md +52 -0
- package/rules/014-predictable-pda.md +57 -0
- package/rules/015-insecure-pda-across-upgrades.md +54 -0
- package/rules/016-bump-mismatch.md +49 -0
- package/rules/017-arbitrary-cpi.md +56 -0
- package/rules/018-cpi-confused-deputy.md +50 -0
- package/rules/019-missing-program-id-check-spl.md +51 -0
- package/rules/020-reentrancy-via-cpi.md +50 -0
- package/rules/021-untrusted-callback.md +49 -0
- package/rules/022-cpi-with-attacker-accounts.md +58 -0
- package/rules/023-lamport-overflow.md +50 -0
- package/rules/024-token-amount-overflow.md +52 -0
- package/rules/025-precision-loss.md +42 -0
- package/rules/026-rounding-direction.md +43 -0
- package/rules/027-token-decimal-mismatch.md +50 -0
- package/rules/028-integer-cast-truncation.md +42 -0
- package/rules/029-off-by-one.md +45 -0
- package/rules/030-missing-authorization.md +51 -0
- package/rules/031-reinitialization-attack.md +50 -0
- package/rules/032-closed-account-revival.md +49 -0
- package/rules/033-init-if-needed-misuse.md +66 -0
- package/rules/034-missing-has-one.md +55 -0
- package/rules/035-insecure-admin-transfer.md +49 -0
- package/rules/036-missing-pause-guards.md +50 -0
- package/rules/037-clock-manipulation.md +53 -0
- package/rules/038-missing-address-validation.md +53 -0
- package/rules/039-constraint-evaluation-stage.md +54 -0
- package/rules/040-realloc-zero-init.md +53 -0
- package/rules/041-missing-payer-on-init.md +59 -0
- package/rules/042-incorrect-space-allocation.md +53 -0
- package/rules/043-account-vs-account-info.md +53 -0
- package/rules/044-token-account-owner-unverified.md +56 -0
- package/rules/045-token-mint-unverified.md +58 -0
- package/rules/046-ata-assumption-errors.md +53 -0
- package/rules/047-token-program-id-hardcoded.md +55 -0
- package/rules/048-compute-budget-abuse.md +51 -0
- package/rules/049-log-spam-dos.md +49 -0
- package/rules/050-stack-overflow-deep-cpi.md +48 -0
- package/rules/INDEX.md +65 -0
- package/rules/README.md +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# anchor-audit CLI
|
|
2
|
+
|
|
3
|
+
AI-driven security audit CLI for Anchor programs on Solana. Sends your program source plus the [rule catalog](../rules) to the Claude API and renders a structured audit report.
|
|
4
|
+
|
|
5
|
+
> **Status:** scaffolding only — implementation lands in Phase 4 (see [IMPLEMENTATION_PLAN.md](../IMPLEMENTATION_PLAN.md)).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
anchor-audit <path-to-anchor-program> [options]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--output <path> output file (default: stdout)
|
|
12
|
+
--rules <ids> comma-separated rule IDs (default: all)
|
|
13
|
+
--severity <min> minimum severity to report (critical | high | medium | low)
|
|
14
|
+
--format <fmt> markdown (default) | json
|
|
15
|
+
--verbose print per-rule progress
|
|
16
|
+
--api-key <key> override ANTHROPIC_API_KEY env var
|
|
17
|
+
--model <id> override default model (claude-sonnet-4-6)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Node 20+ and an `ANTHROPIC_API_KEY`. Exit code is `0` when no critical/high findings, `1` otherwise, `2` on execution error.
|
|
21
|
+
|
|
22
|
+
## Module layout
|
|
23
|
+
|
|
24
|
+
- `src/index.ts` — entry point and flag parsing
|
|
25
|
+
- `src/scanner.ts` — file collection + filtering
|
|
26
|
+
- `src/rules-loader.ts` — loads `/rules/*.md` at runtime
|
|
27
|
+
- `src/auditor.ts` — Claude API orchestration (rules batched 4–6 per call)
|
|
28
|
+
- `src/reporter.ts` — markdown/JSON report generation
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { CliOptions, Severity } from "./index.js";
|
|
2
|
+
import type { Rule } from "./rules-loader.js";
|
|
3
|
+
import type { SourceFile } from "./scanner.js";
|
|
4
|
+
export interface Finding {
|
|
5
|
+
ruleId: string;
|
|
6
|
+
ruleName: string;
|
|
7
|
+
severity: Severity;
|
|
8
|
+
title: string;
|
|
9
|
+
file: string;
|
|
10
|
+
line: number | null;
|
|
11
|
+
description: string;
|
|
12
|
+
vulnerableCode: string;
|
|
13
|
+
recommendation: string;
|
|
14
|
+
}
|
|
15
|
+
export declare const SEVERITY_ORDER: Record<Severity, number>;
|
|
16
|
+
export declare function runAudit(sources: SourceFile[], rules: Rule[], options: CliOptions): Promise<Finding[]>;
|
package/dist/auditor.js
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI API orchestration — provider-agnostic.
|
|
3
|
+
*
|
|
4
|
+
* Anthropic is called natively via @anthropic-ai/sdk (best quality).
|
|
5
|
+
* Every other provider (cloud and local) uses the OpenAI-compatible chat
|
|
6
|
+
* completions API via the `openai` package with a custom baseURL.
|
|
7
|
+
*
|
|
8
|
+
* Cloud routing:
|
|
9
|
+
* anthropic → @anthropic-ai/sdk → api.anthropic.com
|
|
10
|
+
* openai → openai SDK → api.openai.com/v1
|
|
11
|
+
* google → openai SDK → generativelanguage.googleapis.com/v1beta/openai/
|
|
12
|
+
* groq → openai SDK → api.groq.com/openai/v1
|
|
13
|
+
* openrouter → openai SDK → openrouter.ai/api/v1
|
|
14
|
+
*
|
|
15
|
+
* Local routing (no API key required):
|
|
16
|
+
* ollama → openai SDK → http://localhost:11434/v1
|
|
17
|
+
* lmstudio → openai SDK → http://localhost:1234/v1
|
|
18
|
+
* vllm → openai SDK → http://localhost:8000/v1
|
|
19
|
+
* custom → openai SDK → <--base-url>
|
|
20
|
+
*/
|
|
21
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
22
|
+
import OpenAI from "openai";
|
|
23
|
+
import chalk from "chalk";
|
|
24
|
+
export const SEVERITY_ORDER = {
|
|
25
|
+
critical: 0,
|
|
26
|
+
high: 1,
|
|
27
|
+
medium: 2,
|
|
28
|
+
low: 3,
|
|
29
|
+
};
|
|
30
|
+
/** Max tokens per provider call, controlled by --effort. */
|
|
31
|
+
const EFFORT_MAX_TOKENS = {
|
|
32
|
+
low: 2048,
|
|
33
|
+
medium: 4096,
|
|
34
|
+
high: 8192,
|
|
35
|
+
};
|
|
36
|
+
const BATCH_SIZE = 5;
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Provider config
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/** Base URLs for OpenAI-compatible providers (cloud and local). */
|
|
41
|
+
const PROVIDER_BASE_URLS = {
|
|
42
|
+
// Cloud
|
|
43
|
+
openai: "https://api.openai.com/v1",
|
|
44
|
+
google: "https://generativelanguage.googleapis.com/v1beta/openai/",
|
|
45
|
+
groq: "https://api.groq.com/openai/v1",
|
|
46
|
+
openrouter: "https://openrouter.ai/api/v1",
|
|
47
|
+
// Local — override with --base-url if your server runs on a different port
|
|
48
|
+
ollama: "http://localhost:11434/v1",
|
|
49
|
+
lmstudio: "http://localhost:1234/v1",
|
|
50
|
+
vllm: "http://localhost:8000/v1",
|
|
51
|
+
};
|
|
52
|
+
/** Environment variable names per cloud provider. */
|
|
53
|
+
const PROVIDER_ENV_VARS = {
|
|
54
|
+
anthropic: "ANTHROPIC_API_KEY",
|
|
55
|
+
openai: "OPENAI_API_KEY",
|
|
56
|
+
google: "GEMINI_API_KEY",
|
|
57
|
+
groq: "GROQ_API_KEY",
|
|
58
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
59
|
+
custom: "OPENAI_API_KEY",
|
|
60
|
+
};
|
|
61
|
+
/** Local providers don't use real API keys; the OpenAI SDK still requires a non-empty string. */
|
|
62
|
+
const LOCAL_PROVIDERS = new Set(["ollama", "lmstudio", "vllm"]);
|
|
63
|
+
const LOCAL_KEY_PLACEHOLDER = "local";
|
|
64
|
+
function resolveApiKey(options) {
|
|
65
|
+
if (options.apiKey)
|
|
66
|
+
return options.apiKey;
|
|
67
|
+
const envVar = PROVIDER_ENV_VARS[options.provider];
|
|
68
|
+
if (envVar) {
|
|
69
|
+
const key = process.env[envVar];
|
|
70
|
+
if (key)
|
|
71
|
+
return key;
|
|
72
|
+
}
|
|
73
|
+
// Local providers work without a real API key.
|
|
74
|
+
if (LOCAL_PROVIDERS.has(options.provider)) {
|
|
75
|
+
return LOCAL_KEY_PLACEHOLDER;
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`No API key found for provider "${options.provider}".\n` +
|
|
78
|
+
`Set the ${PROVIDER_ENV_VARS[options.provider] ?? "OPENAI_API_KEY"} environment variable or pass --api-key.`);
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Prompt builders
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
function buildSystem(rules) {
|
|
84
|
+
const blocks = rules
|
|
85
|
+
.map((r) => `### Rule ${r.id}: ${r.name} [${r.severity.toUpperCase()}]\n\n${r.content}`)
|
|
86
|
+
.join("\n\n---\n\n");
|
|
87
|
+
return `You are a Solana Anchor smart-contract security auditor.
|
|
88
|
+
|
|
89
|
+
Analyze the provided Rust source files for violations of the security rules below.
|
|
90
|
+
Return ONLY a valid JSON array of findings — no markdown fences, no commentary.
|
|
91
|
+
If you find no violations, return exactly: []
|
|
92
|
+
|
|
93
|
+
Each object in the array must match this schema:
|
|
94
|
+
{
|
|
95
|
+
"ruleId": "<NNN>", // three-digit string, e.g. "017"
|
|
96
|
+
"severity": "<level>", // one of: critical | high | medium | low
|
|
97
|
+
"title": "<string>", // ≤ 100 chars; what was found
|
|
98
|
+
"file": "<path>", // as given in the <file path="..."> tag
|
|
99
|
+
"line": <number | null>, // best-effort integer line number
|
|
100
|
+
"description": "<string>", // what the code does wrong and why it matters
|
|
101
|
+
"vulnerableCode": "<string>", // relevant snippet, ≤ 400 chars
|
|
102
|
+
"recommendation": "<string>" // concrete, actionable fix
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
Security rules to check:
|
|
106
|
+
|
|
107
|
+
${blocks}`;
|
|
108
|
+
}
|
|
109
|
+
function buildUser(sources) {
|
|
110
|
+
return sources
|
|
111
|
+
.map((s) => `<file path="${s.path}">\n${s.content}\n</file>`)
|
|
112
|
+
.join("\n\n");
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Provider-specific API calls
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
async function callAnthropic(apiKey, model, maxTokens, system, user) {
|
|
118
|
+
const client = new Anthropic({ apiKey });
|
|
119
|
+
const msg = await client.messages.create({
|
|
120
|
+
model,
|
|
121
|
+
max_tokens: maxTokens,
|
|
122
|
+
system,
|
|
123
|
+
messages: [{ role: "user", content: user }],
|
|
124
|
+
});
|
|
125
|
+
return msg.content[0]?.type === "text" ? msg.content[0].text : "";
|
|
126
|
+
}
|
|
127
|
+
async function callOpenAICompatible(apiKey, model, baseURL, maxTokens, system, user) {
|
|
128
|
+
const client = new OpenAI({ apiKey, baseURL });
|
|
129
|
+
const response = await client.chat.completions.create({
|
|
130
|
+
model,
|
|
131
|
+
max_tokens: maxTokens,
|
|
132
|
+
messages: [
|
|
133
|
+
{ role: "system", content: system },
|
|
134
|
+
{ role: "user", content: user },
|
|
135
|
+
],
|
|
136
|
+
});
|
|
137
|
+
return response.choices[0]?.message?.content ?? "";
|
|
138
|
+
}
|
|
139
|
+
async function callProvider(options, apiKey, system, user) {
|
|
140
|
+
const maxTokens = EFFORT_MAX_TOKENS[options.effort];
|
|
141
|
+
if (options.provider === "anthropic") {
|
|
142
|
+
return callAnthropic(apiKey, options.model, maxTokens, system, user);
|
|
143
|
+
}
|
|
144
|
+
const baseURL = options.baseUrl ??
|
|
145
|
+
PROVIDER_BASE_URLS[options.provider] ??
|
|
146
|
+
"https://api.openai.com/v1";
|
|
147
|
+
return callOpenAICompatible(apiKey, options.model, baseURL, maxTokens, system, user);
|
|
148
|
+
}
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Response parsing
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
function parseFindings(raw, rules) {
|
|
153
|
+
const cleaned = raw
|
|
154
|
+
.replace(/^```(?:json)?\r?\n?/m, "")
|
|
155
|
+
.replace(/\r?\n?```$/m, "")
|
|
156
|
+
.trim();
|
|
157
|
+
let parsed;
|
|
158
|
+
try {
|
|
159
|
+
parsed = JSON.parse(cleaned);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
if (!Array.isArray(parsed))
|
|
165
|
+
return [];
|
|
166
|
+
const ruleMap = new Map(rules.map((r) => [r.id, r]));
|
|
167
|
+
const findings = [];
|
|
168
|
+
for (const item of parsed) {
|
|
169
|
+
if (typeof item !== "object" || item === null)
|
|
170
|
+
continue;
|
|
171
|
+
const obj = item;
|
|
172
|
+
const id = String(obj["ruleId"] ?? "").padStart(3, "0");
|
|
173
|
+
const rule = ruleMap.get(id);
|
|
174
|
+
if (!rule)
|
|
175
|
+
continue;
|
|
176
|
+
const sev = String(obj["severity"] ?? "").toLowerCase();
|
|
177
|
+
if (!["critical", "high", "medium", "low"].includes(sev))
|
|
178
|
+
continue;
|
|
179
|
+
findings.push({
|
|
180
|
+
ruleId: id,
|
|
181
|
+
ruleName: rule.name,
|
|
182
|
+
severity: sev,
|
|
183
|
+
title: String(obj["title"] ?? "").slice(0, 200),
|
|
184
|
+
file: String(obj["file"] ?? ""),
|
|
185
|
+
line: typeof obj["line"] === "number"
|
|
186
|
+
? Math.max(1, Math.floor(obj["line"]))
|
|
187
|
+
: null,
|
|
188
|
+
description: String(obj["description"] ?? ""),
|
|
189
|
+
vulnerableCode: String(obj["vulnerableCode"] ?? "").slice(0, 800),
|
|
190
|
+
recommendation: String(obj["recommendation"] ?? ""),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return findings;
|
|
194
|
+
}
|
|
195
|
+
// ---------------------------------------------------------------------------
|
|
196
|
+
// Main entry
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
export async function runAudit(sources, rules, options) {
|
|
199
|
+
const apiKey = resolveApiKey(options);
|
|
200
|
+
const minOrder = options.severity ? SEVERITY_ORDER[options.severity] : 3;
|
|
201
|
+
const eligible = rules.filter((r) => SEVERITY_ORDER[r.severity] <= minOrder);
|
|
202
|
+
if (eligible.length === 0)
|
|
203
|
+
return [];
|
|
204
|
+
const batches = [];
|
|
205
|
+
for (let i = 0; i < eligible.length; i += BATCH_SIZE) {
|
|
206
|
+
batches.push(eligible.slice(i, i + BATCH_SIZE));
|
|
207
|
+
}
|
|
208
|
+
if (options.verbose) {
|
|
209
|
+
process.stderr.write(chalk.dim(`provider: ${options.provider} model: ${options.model} effort: ${options.effort} batches: ${batches.length}\n`));
|
|
210
|
+
}
|
|
211
|
+
const userContent = buildUser(sources);
|
|
212
|
+
const allFindings = [];
|
|
213
|
+
for (let i = 0; i < batches.length; i++) {
|
|
214
|
+
const batch = batches[i];
|
|
215
|
+
if (options.verbose) {
|
|
216
|
+
process.stderr.write(chalk.dim(` [${i + 1}/${batches.length}] rules ${batch.map((r) => r.id).join(", ")}\n`));
|
|
217
|
+
}
|
|
218
|
+
let responseText;
|
|
219
|
+
try {
|
|
220
|
+
responseText = await callProvider(options, apiKey, buildSystem(batch), userContent);
|
|
221
|
+
}
|
|
222
|
+
catch (err) {
|
|
223
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
224
|
+
throw new Error(`API error on batch ${i + 1} (${options.provider}): ${msg}`);
|
|
225
|
+
}
|
|
226
|
+
const batchFindings = parseFindings(responseText, batch);
|
|
227
|
+
if (options.verbose && batchFindings.length > 0) {
|
|
228
|
+
process.stderr.write(chalk.yellow(` → ${batchFindings.length} finding(s)\n`));
|
|
229
|
+
}
|
|
230
|
+
allFindings.push(...batchFindings);
|
|
231
|
+
}
|
|
232
|
+
const filtered = allFindings.filter((f) => SEVERITY_ORDER[f.severity] <= minOrder);
|
|
233
|
+
return filtered.sort((a, b) => SEVERITY_ORDER[a.severity] - SEVERITY_ORDER[b.severity]);
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=auditor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auditor.js","sourceRoot":"","sources":["../src/auditor.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
export type Severity = "critical" | "high" | "medium" | "low";
|
|
4
|
+
export type Effort = "low" | "medium" | "high";
|
|
5
|
+
export type Provider = "anthropic" | "openai" | "google" | "groq" | "openrouter" | "ollama" | "lmstudio" | "vllm" | "custom";
|
|
6
|
+
/** Default model ID for each provider. */
|
|
7
|
+
export declare const DEFAULT_MODELS: Record<Provider, string>;
|
|
8
|
+
export interface CliOptions {
|
|
9
|
+
output?: string;
|
|
10
|
+
rules?: string;
|
|
11
|
+
severity?: Severity;
|
|
12
|
+
format: "markdown" | "json";
|
|
13
|
+
verbose: boolean;
|
|
14
|
+
apiKey?: string;
|
|
15
|
+
model: string;
|
|
16
|
+
provider: Provider;
|
|
17
|
+
baseUrl?: string;
|
|
18
|
+
effort: Effort;
|
|
19
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { basename } from "node:path";
|
|
6
|
+
import { scanProgram } from "./scanner.js";
|
|
7
|
+
import { loadRules } from "./rules-loader.js";
|
|
8
|
+
import { runAudit } from "./auditor.js";
|
|
9
|
+
import { renderReport } from "./reporter.js";
|
|
10
|
+
import { buildMetadata } from "./metadata.js";
|
|
11
|
+
/** Default model ID for each provider. */
|
|
12
|
+
export const DEFAULT_MODELS = {
|
|
13
|
+
// Cloud
|
|
14
|
+
anthropic: "claude-sonnet-4-6",
|
|
15
|
+
openai: "gpt-4o",
|
|
16
|
+
google: "gemini-2.0-flash",
|
|
17
|
+
groq: "llama-3.3-70b-versatile",
|
|
18
|
+
openrouter: "anthropic/claude-sonnet-4-6",
|
|
19
|
+
// Local
|
|
20
|
+
ollama: "llama3.1:8b",
|
|
21
|
+
lmstudio: "local-model",
|
|
22
|
+
vllm: "local-model",
|
|
23
|
+
custom: "gpt-4o",
|
|
24
|
+
};
|
|
25
|
+
const VALID_PROVIDERS = Object.keys(DEFAULT_MODELS);
|
|
26
|
+
const VALID_EFFORTS = ["low", "medium", "high"];
|
|
27
|
+
const program = new Command();
|
|
28
|
+
program
|
|
29
|
+
.name("anchor-audit")
|
|
30
|
+
.description("Security audit for Anchor programs on Solana.\n" +
|
|
31
|
+
"Cloud: anthropic, openai, google, groq, openrouter\n" +
|
|
32
|
+
"Local: ollama, lmstudio, vllm, custom")
|
|
33
|
+
.argument("<path>", "path to the Anchor program source directory")
|
|
34
|
+
.option("--output <path>", "also write report to this specific file")
|
|
35
|
+
.option("--rules <ids>", "comma-separated rule IDs to run (default: all 50)")
|
|
36
|
+
.option("--severity <min>", "minimum severity to report: critical | high | medium | low")
|
|
37
|
+
.option("--format <fmt>", "output format: markdown | json", "markdown")
|
|
38
|
+
.option("--verbose", "print per-rule batch progress", false)
|
|
39
|
+
.option("--api-key <key>", "API key (overrides env var; not required for local providers)")
|
|
40
|
+
.option("--provider <name>", `AI provider: ${VALID_PROVIDERS.join(" | ")}`, "anthropic")
|
|
41
|
+
.option("--model <id>", "model ID (default depends on --provider; see README)")
|
|
42
|
+
.option("--base-url <url>", "base URL for OpenAI-compatible endpoint (required for --provider custom; " +
|
|
43
|
+
"optional override for ollama/lmstudio/vllm defaults)")
|
|
44
|
+
.option("--effort <level>", "analysis depth — low (2 k tokens) | medium (4 k) | high (8 k)", "medium")
|
|
45
|
+
.version("0.1.0")
|
|
46
|
+
.action(async (targetPath, opts) => {
|
|
47
|
+
const startTime = new Date();
|
|
48
|
+
const provider = (opts.provider ?? "anthropic");
|
|
49
|
+
if (!VALID_PROVIDERS.includes(provider)) {
|
|
50
|
+
console.error(chalk.red(`error: unknown provider "${provider}". Valid: ${VALID_PROVIDERS.join(", ")}`));
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
if (provider === "custom" && !opts.baseUrl) {
|
|
54
|
+
console.error(chalk.red(`error: --provider custom requires --base-url <url>`));
|
|
55
|
+
process.exit(2);
|
|
56
|
+
}
|
|
57
|
+
const effort = (opts.effort ?? "medium");
|
|
58
|
+
if (!VALID_EFFORTS.includes(effort)) {
|
|
59
|
+
console.error(chalk.red(`error: --effort must be one of: ${VALID_EFFORTS.join(" | ")}`));
|
|
60
|
+
process.exit(2);
|
|
61
|
+
}
|
|
62
|
+
const options = {
|
|
63
|
+
output: opts.output,
|
|
64
|
+
rules: opts.rules,
|
|
65
|
+
severity: opts.severity,
|
|
66
|
+
format: (opts.format ?? "markdown"),
|
|
67
|
+
verbose: opts.verbose ?? false,
|
|
68
|
+
apiKey: opts.apiKey,
|
|
69
|
+
model: opts.model ?? DEFAULT_MODELS[provider],
|
|
70
|
+
provider,
|
|
71
|
+
baseUrl: opts.baseUrl,
|
|
72
|
+
effort,
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
const sources = await scanProgram(targetPath);
|
|
76
|
+
const rules = await loadRules(options.rules);
|
|
77
|
+
const findings = await runAudit(sources, rules, options);
|
|
78
|
+
const meta = buildMetadata({
|
|
79
|
+
model: options.model,
|
|
80
|
+
provider: options.provider,
|
|
81
|
+
effort: options.effort,
|
|
82
|
+
project: basename(targetPath),
|
|
83
|
+
startTime,
|
|
84
|
+
totalFiles: sources.length,
|
|
85
|
+
totalFindings: findings.length,
|
|
86
|
+
});
|
|
87
|
+
await renderReport(findings, options, meta);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
91
|
+
console.error(chalk.red(`error: ${message}`));
|
|
92
|
+
process.exit(2);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
program.parseAsync(process.argv);
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,eAAe,CAAC;AACvB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC9C,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAkB9C,0CAA0C;AAC1C,MAAM,CAAC,MAAM,cAAc,GAA6B;IACtD,QAAQ;IACR,SAAS,EAAE,mBAAmB;IAC9B,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,kBAAkB;IAC1B,IAAI,EAAE,yBAAyB;IAC/B,UAAU,EAAE,6BAA6B;IACzC,QAAQ;IACR,MAAM,EAAE,aAAa;IACrB,QAAQ,EAAE,aAAa;IACvB,IAAI,EAAE,aAAa;IACnB,MAAM,EAAE,QAAQ;CACjB,CAAC;AAeF,MAAM,eAAe,GAAG,MAAM,CAAC,IAAI,CAAC,cAAc,CAAe,CAAC;AAClE,MAAM,aAAa,GAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AAE1D,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,cAAc,CAAC;KACpB,WAAW,CACV,iDAAiD;IAC/C,sDAAsD;IACtD,uCAAuC,CAC1C;KACA,QAAQ,CAAC,QAAQ,EAAE,6CAA6C,CAAC;KACjE,MAAM,CAAC,iBAAiB,EAAE,yCAAyC,CAAC;KACpE,MAAM,CAAC,eAAe,EAAE,mDAAmD,CAAC;KAC5E,MAAM,CACL,kBAAkB,EAClB,4DAA4D,CAC7D;KACA,MAAM,CAAC,gBAAgB,EAAE,gCAAgC,EAAE,UAAU,CAAC;KACtE,MAAM,CAAC,WAAW,EAAE,+BAA+B,EAAE,KAAK,CAAC;KAC3D,MAAM,CACL,iBAAiB,EACjB,+DAA+D,CAChE;KACA,MAAM,CACL,mBAAmB,EACnB,gBAAgB,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAC7C,WAAW,CACZ;KACA,MAAM,CAAC,cAAc,EAAE,sDAAsD,CAAC;KAC9E,MAAM,CACL,kBAAkB,EAClB,2EAA2E;IACzE,sDAAsD,CACzD;KACA,MAAM,CACL,kBAAkB,EAClB,+DAA+D,EAC/D,QAAQ,CACT;KACA,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,KAAK,EAAE,UAAkB,EAAE,IAAyB,EAAE,EAAE;IAC9D,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,WAAW,CAAa,CAAC;IAE5D,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACxC,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CACP,4BAA4B,QAAQ,aAAa,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9E,CACF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,QAAQ,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3C,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAChE,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,QAAQ,CAAW,CAAC;IACnD,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CACP,mCAAmC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAC/D,CACF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAe;QAC1B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,MAAM,EAAE,CAAC,IAAI,CAAC,MAAM,IAAI,UAAU,CAAwB;QAC1D,OAAO,EAAE,IAAI,CAAC,OAAO,IAAI,KAAK;QAC9B,MAAM,EAAE,IAAI,CAAC,MAAM;QACnB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,cAAc,CAAC,QAAQ,CAAC;QAC7C,QAAQ;QACR,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,MAAM;KACP,CAAC;IAEF,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC7C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAEzD,MAAM,IAAI,GAAG,aAAa,CAAC;YACzB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC;YAC7B,SAAS;YACT,UAAU,EAAE,OAAO,CAAC,MAAM;YAC1B,aAAa,EAAE,QAAQ,CAAC,MAAM;SAC/B,CAAC,CAAC;QAEH,MAAM,YAAY,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface AuditMetadata {
|
|
2
|
+
date: string;
|
|
3
|
+
time: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
model: string;
|
|
6
|
+
provider: string;
|
|
7
|
+
effort: string;
|
|
8
|
+
cliVersion: string;
|
|
9
|
+
project: string;
|
|
10
|
+
gitCommit?: string;
|
|
11
|
+
gitBranch?: string;
|
|
12
|
+
os: string;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
totalFiles: number;
|
|
15
|
+
totalFindings: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function buildMetadata(params: {
|
|
18
|
+
model: string;
|
|
19
|
+
provider: string;
|
|
20
|
+
effort: string;
|
|
21
|
+
project: string;
|
|
22
|
+
startTime: Date;
|
|
23
|
+
totalFiles: number;
|
|
24
|
+
totalFindings: number;
|
|
25
|
+
}): AuditMetadata;
|
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
function git(cmd) {
|
|
7
|
+
try {
|
|
8
|
+
return execSync(cmd, {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
11
|
+
timeout: 2000,
|
|
12
|
+
}).trim() || undefined;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function readVersion() {
|
|
19
|
+
try {
|
|
20
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
21
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
22
|
+
return pkg.version ?? "0.1.0";
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return "0.1.0";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export function buildMetadata(params) {
|
|
29
|
+
const now = new Date();
|
|
30
|
+
const iso = now.toISOString();
|
|
31
|
+
return {
|
|
32
|
+
date: iso.slice(0, 10),
|
|
33
|
+
time: iso.slice(11, 19) + " UTC",
|
|
34
|
+
timestamp: iso,
|
|
35
|
+
model: params.model,
|
|
36
|
+
provider: params.provider,
|
|
37
|
+
effort: params.effort,
|
|
38
|
+
cliVersion: readVersion(),
|
|
39
|
+
project: params.project,
|
|
40
|
+
gitCommit: git("git rev-parse --short HEAD"),
|
|
41
|
+
gitBranch: git("git rev-parse --abbrev-ref HEAD"),
|
|
42
|
+
os: `${os.type()} ${os.release()} (${os.arch()})`,
|
|
43
|
+
durationMs: now.getTime() - params.startTime.getTime(),
|
|
44
|
+
totalFiles: params.totalFiles,
|
|
45
|
+
totalFindings: params.totalFindings,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAmB1C,SAAS,GAAG,CAAC,GAAW;IACtB,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,GAAG,EAAE;YACnB,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;YACjC,OAAO,EAAE,IAAI;SACd,CAAC,CAAC,IAAI,EAAE,IAAI,SAAS,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,iBAAiB,CAAC,CAAC;QACjF,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAyB,CAAC;QAC9E,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAQ7B;IACC,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAE9B,OAAO;QACL,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC;QACtB,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,MAAM;QAChC,SAAS,EAAE,GAAG;QACd,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,UAAU,EAAE,WAAW,EAAE;QACzB,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,SAAS,EAAE,GAAG,CAAC,4BAA4B,CAAC;QAC5C,SAAS,EAAE,GAAG,CAAC,iCAAiC,CAAC;QACjD,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,GAAG;QACjD,UAAU,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE;QACtD,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,aAAa,EAAE,MAAM,CAAC,aAAa;KACpC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { SEVERITY_ORDER, type Finding } from "./auditor.js";
|
|
2
|
+
import type { CliOptions, Severity } from "./index.js";
|
|
3
|
+
import type { AuditMetadata } from "./metadata.js";
|
|
4
|
+
type Counts = Record<Severity, number>;
|
|
5
|
+
export declare function countBySeverity(findings: Finding[]): Counts;
|
|
6
|
+
export interface AuditReport {
|
|
7
|
+
version: string;
|
|
8
|
+
date: string;
|
|
9
|
+
metadata?: AuditMetadata;
|
|
10
|
+
summary: Counts & {
|
|
11
|
+
total: number;
|
|
12
|
+
};
|
|
13
|
+
findings: Finding[];
|
|
14
|
+
}
|
|
15
|
+
export declare function renderJson(findings: Finding[], meta?: AuditMetadata): string;
|
|
16
|
+
export declare function renderMarkdown(findings: Finding[], meta?: AuditMetadata): string;
|
|
17
|
+
export declare function renderReport(findings: Finding[], options: CliOptions, meta?: AuditMetadata): Promise<void>;
|
|
18
|
+
export { SEVERITY_ORDER };
|