@solana-epic/cli 0.1.0-beta.3 โ 0.2.0-beta.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/dist/api.d.ts +12 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +20 -0
- package/dist/api.js.map +1 -0
- package/dist/formatters.d.ts +4 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +134 -0
- package/dist/formatters.js.map +1 -0
- package/dist/index.js +181 -99
- package/dist/index.js.map +1 -1
- package/dist/reports.d.ts +6 -0
- package/dist/reports.d.ts.map +1 -0
- package/dist/reports.js +187 -0
- package/dist/reports.js.map +1 -0
- package/dist/ui.d.ts +41 -2
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +457 -52
- package/dist/ui.js.map +1 -1
- package/package.json +9 -9
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { CompatibilityReport, DiffReport } from "@solana-epic/diff-engine";
|
|
2
|
+
import { config } from "@solana-epic/parser";
|
|
3
|
+
export interface UpgradeReport {
|
|
4
|
+
programName: string;
|
|
5
|
+
compatibility: CompatibilityReport;
|
|
6
|
+
report: DiffReport;
|
|
7
|
+
intelligence: any;
|
|
8
|
+
epicConfig: config.ResolvedEpicConfig;
|
|
9
|
+
}
|
|
10
|
+
export declare function runCheck(oldPath: string, newPath: string, epicConfig: config.ResolvedEpicConfig): Promise<UpgradeReport>;
|
|
11
|
+
export { formatMarkdown, formatSarif } from "./formatters.js";
|
|
12
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AACA,OAAO,EAKL,mBAAmB,EACnB,UAAU,EACX,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,mBAAmB,CAAC;IACnC,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,GAAG,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC,kBAAkB,CAAC;CACvC;AAED,wBAAsB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAiB9H;AAED,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as path from "path";
|
|
2
|
+
import { analyzePrograms, compareAccountLayouts, simulateCompatibility, createUpgradeIntelligence } from "@solana-epic/diff-engine";
|
|
3
|
+
export async function runCheck(oldPath, newPath, epicConfig) {
|
|
4
|
+
const resolvedOldPath = path.resolve(oldPath);
|
|
5
|
+
const resolvedNewPath = path.resolve(newPath);
|
|
6
|
+
const { oldProgram, newProgram } = await analyzePrograms(resolvedOldPath, resolvedNewPath, epicConfig);
|
|
7
|
+
const compatibility = simulateCompatibility(oldProgram, newProgram, epicConfig);
|
|
8
|
+
const report = compareAccountLayouts(oldProgram, newProgram, epicConfig);
|
|
9
|
+
const intelligence = createUpgradeIntelligence(report);
|
|
10
|
+
const programName = compatibility.accounts[0]?.account || report.findings[0]?.account || path.basename(resolvedNewPath);
|
|
11
|
+
return {
|
|
12
|
+
programName,
|
|
13
|
+
compatibility,
|
|
14
|
+
report,
|
|
15
|
+
intelligence,
|
|
16
|
+
epicConfig
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export { formatMarkdown, formatSarif } from "./formatters.js";
|
|
20
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,yBAAyB,EAG1B,MAAM,0BAA0B,CAAC;AAWlC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAe,EAAE,OAAe,EAAE,UAAqC;IACpG,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9C,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,GAAG,MAAM,eAAe,CAAC,eAAe,EAAE,eAAe,EAAE,UAAU,CAAC,CAAC;IACvG,MAAM,aAAa,GAAG,qBAAqB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IAChF,MAAM,MAAM,GAAG,qBAAqB,CAAC,UAAU,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IACzE,MAAM,YAAY,GAAG,yBAAyB,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,WAAW,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;IAExH,OAAO;QACL,WAAW;QACX,aAAa;QACb,MAAM;QACN,YAAY;QACZ,UAAU;KACX,CAAC;AACJ,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatters.d.ts","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,wBAAgB,cAAc,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,GAAE,OAAe,GAAG,MAAM,CAoF5F;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,aAAa,GAAG,GAAG,CAyDtD"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export function formatMarkdown(result, configChanged = false) {
|
|
2
|
+
const { compatibility, report, programName, epicConfig } = result;
|
|
3
|
+
const lines = [];
|
|
4
|
+
const blocked = compatibility.overall === "Blocked";
|
|
5
|
+
const migration = compatibility.overall === "Migration-Required";
|
|
6
|
+
const safe = compatibility.overall === "Compatible";
|
|
7
|
+
if (blocked) {
|
|
8
|
+
lines.push("## ๐ด EPIC Guard: UPGRADE BLOCKED");
|
|
9
|
+
}
|
|
10
|
+
else if (migration) {
|
|
11
|
+
lines.push("## ๐ก EPIC Guard: MIGRATION REQUIRED");
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
lines.push("## ๐ข EPIC Guard: APPROVED");
|
|
15
|
+
}
|
|
16
|
+
lines.push("");
|
|
17
|
+
if (configChanged) {
|
|
18
|
+
lines.push("> [!WARNING]");
|
|
19
|
+
lines.push("> **UPGRADE CONFIGURATION GATE MODIFIED**");
|
|
20
|
+
lines.push("> This Pull Request contains changes to `epic.toml` configuration rules.");
|
|
21
|
+
lines.push("> Signers must audit the modifications below to ensure safety limits are not bypassed.");
|
|
22
|
+
lines.push("");
|
|
23
|
+
}
|
|
24
|
+
lines.push(`### Upgrade Compatibility: \`${programName}\``);
|
|
25
|
+
lines.push("");
|
|
26
|
+
if (compatibility.accounts.length === 0) {
|
|
27
|
+
lines.push("No state accounts found. Upgrade is safe.");
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
for (const acc of compatibility.accounts) {
|
|
31
|
+
const isBlocked = acc.status === "Blocked";
|
|
32
|
+
const isMigration = acc.status === "Migration-Required";
|
|
33
|
+
const icon = isBlocked ? "๐ด" : isMigration ? "๐ก" : "๐ข";
|
|
34
|
+
lines.push(`#### ${icon} Struct \`${acc.account}\` (${acc.status})`);
|
|
35
|
+
if (acc.reasons && acc.reasons.length > 0) {
|
|
36
|
+
lines.push("");
|
|
37
|
+
lines.push("**Reasoning:**");
|
|
38
|
+
for (const r of acc.reasons) {
|
|
39
|
+
lines.push(`* ${r}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (acc.upgradePlan && acc.upgradePlan.length > 0) {
|
|
43
|
+
lines.push("");
|
|
44
|
+
lines.push("**Migration Plan:**");
|
|
45
|
+
if (acc.rentDeltaLamports !== null) {
|
|
46
|
+
lines.push(`* Rent Delta: \`${acc.rentDeltaLamports} lamports\` (\`${acc.sizeDelta} bytes\`)`);
|
|
47
|
+
}
|
|
48
|
+
for (const step of acc.upgradePlan) {
|
|
49
|
+
lines.push(`* ${step}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
lines.push("");
|
|
53
|
+
}
|
|
54
|
+
// Overrides section
|
|
55
|
+
const appliedOverrides = report.findings.filter(f => f.severity !== (f.kind === "FIELD_ADDED" ? "MAJOR" : "CRITICAL"));
|
|
56
|
+
if (appliedOverrides.length > 0) {
|
|
57
|
+
lines.push("### ๐ Applied Layout Overrides");
|
|
58
|
+
lines.push("");
|
|
59
|
+
lines.push("| Struct | Finding | Field | Severity Shift | Note |");
|
|
60
|
+
lines.push("| :--- | :--- | :--- | :--- | :--- |");
|
|
61
|
+
for (const o of appliedOverrides) {
|
|
62
|
+
const original = o.kind === "FIELD_ADDED" ? "MAJOR" : "CRITICAL";
|
|
63
|
+
// Find note
|
|
64
|
+
let note = "No note provided.";
|
|
65
|
+
for (const [name, program] of epicConfig.programs.entries()) {
|
|
66
|
+
const match = program.overrides.find(override => override.account.toLowerCase() === o.account.toLowerCase() &&
|
|
67
|
+
override.finding.toUpperCase() === o.kind.toUpperCase());
|
|
68
|
+
if (match) {
|
|
69
|
+
note = match.note;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
lines.push(`| \`${o.account}\` | \`${o.kind}\` | \`${o.field?.name || "global"}\` | \`${original}\` โโโบ \`${o.severity}\` | ${note} |`);
|
|
74
|
+
}
|
|
75
|
+
lines.push("");
|
|
76
|
+
}
|
|
77
|
+
return lines.join("\n");
|
|
78
|
+
}
|
|
79
|
+
export function formatSarif(result) {
|
|
80
|
+
// Return a valid SARIF structure based on the findings
|
|
81
|
+
const sarif = {
|
|
82
|
+
version: "2.1.0",
|
|
83
|
+
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
|
|
84
|
+
runs: [
|
|
85
|
+
{
|
|
86
|
+
tool: {
|
|
87
|
+
driver: {
|
|
88
|
+
name: "EPIC Upgrade Intelligence",
|
|
89
|
+
version: "0.2.0-beta.0",
|
|
90
|
+
informationUri: "https://github.com/solana-epic/epic",
|
|
91
|
+
rules: []
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
results: []
|
|
95
|
+
}
|
|
96
|
+
]
|
|
97
|
+
};
|
|
98
|
+
const run = sarif.runs[0];
|
|
99
|
+
const rules = new Map();
|
|
100
|
+
for (const finding of result.report.findings) {
|
|
101
|
+
const ruleId = `EPIC-LAYOUT-${finding.kind}`;
|
|
102
|
+
if (!rules.has(ruleId)) {
|
|
103
|
+
rules.set(ruleId, {
|
|
104
|
+
id: ruleId,
|
|
105
|
+
shortDescription: { text: `State Layout Drift: ${finding.kind}` },
|
|
106
|
+
helpUri: "https://github.com/solana-epic/epic"
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Map severity
|
|
110
|
+
let level = "warning";
|
|
111
|
+
if (finding.severity === "CRITICAL")
|
|
112
|
+
level = "error";
|
|
113
|
+
if (finding.severity === "SAFE")
|
|
114
|
+
level = "note";
|
|
115
|
+
run.results.push({
|
|
116
|
+
ruleId,
|
|
117
|
+
level,
|
|
118
|
+
message: {
|
|
119
|
+
text: `Account \`${finding.account}\` changed: ${finding.kind}. Field: ${finding.field?.name || 'N/A'}`
|
|
120
|
+
},
|
|
121
|
+
locations: [
|
|
122
|
+
{
|
|
123
|
+
physicalLocation: {
|
|
124
|
+
artifactLocation: { uri: "epic.toml" },
|
|
125
|
+
region: { startLine: 1 }
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
run.tool.driver.rules = Array.from(rules.values());
|
|
132
|
+
return sarif;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=formatters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"formatters.js","sourceRoot":"","sources":["../src/formatters.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import {
|
|
3
|
+
import { analyzePrograms, compareAccountLayouts, createUpgradeIntelligence, simulateCompatibility } from "@solana-epic/diff-engine";
|
|
4
4
|
import { config } from "@solana-epic/parser";
|
|
5
5
|
import { spawnSync, execSync } from "node:child_process";
|
|
6
6
|
import path from "node:path";
|
|
@@ -13,7 +13,42 @@ program
|
|
|
13
13
|
.version(CLI_VERSION)
|
|
14
14
|
.option("--no-banner", "Disable the startup banner");
|
|
15
15
|
import { resolveParserBinary } from "./loader.js";
|
|
16
|
-
import {
|
|
16
|
+
import { printStartup, getBannerString, printInitSequence, printSection, printRuleFinding, colors, printEndSummary, printUpgradeReport, printCompatibilityReport, severityBadge, scoreBar, bandForScore, DIVIDER, ruleKnowledge } from "./ui.js";
|
|
17
|
+
import { generateSarif, generateMarkdown } from "./reports.js";
|
|
18
|
+
// Count Rust source files under a path (real, not fabricated metrics).
|
|
19
|
+
function countRustFiles(target) {
|
|
20
|
+
let count = 0;
|
|
21
|
+
const skip = new Set([".git", "target", "node_modules", "vendor"]);
|
|
22
|
+
const walk = (dir) => {
|
|
23
|
+
let entries;
|
|
24
|
+
try {
|
|
25
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
if (skip.has(entry.name))
|
|
33
|
+
continue;
|
|
34
|
+
walk(path.join(dir, entry.name));
|
|
35
|
+
}
|
|
36
|
+
else if (entry.isFile() && entry.name.endsWith(".rs")) {
|
|
37
|
+
count++;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
try {
|
|
42
|
+
const stat = fs.statSync(target);
|
|
43
|
+
if (stat.isFile())
|
|
44
|
+
return target.endsWith(".rs") ? 1 : 0;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
walk(target);
|
|
50
|
+
return count;
|
|
51
|
+
}
|
|
17
52
|
function findRustBinary() {
|
|
18
53
|
try {
|
|
19
54
|
return resolveParserBinary();
|
|
@@ -31,7 +66,7 @@ program
|
|
|
31
66
|
const startTime = Date.now();
|
|
32
67
|
try {
|
|
33
68
|
const opts = program.opts();
|
|
34
|
-
|
|
69
|
+
printStartup("Workspace Analysis", !opts.banner);
|
|
35
70
|
printInitSequence([
|
|
36
71
|
"Rust AST Loaded",
|
|
37
72
|
"Parsing Anchor Workspace",
|
|
@@ -87,19 +122,23 @@ program
|
|
|
87
122
|
.command("check")
|
|
88
123
|
.description("Compare two Solana program workspace versions and report upgrade readiness.")
|
|
89
124
|
.option("-c, --config <path>", "Path to epic.toml configuration file")
|
|
125
|
+
.option("-f, --format <format>", "Output format: text, json", "text")
|
|
90
126
|
.argument("<old_path>", "Path to the old program version source directory")
|
|
91
127
|
.argument("<new_path>", "Path to the new program version source directory")
|
|
92
128
|
.action(async (oldPath, newPath, options) => {
|
|
93
129
|
const startTime = Date.now();
|
|
130
|
+
const isJson = options.format === "json";
|
|
94
131
|
try {
|
|
95
132
|
const opts = program.opts();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
133
|
+
const startupShown = isJson ? false : printStartup("Upgrade Intelligence", !opts.banner);
|
|
134
|
+
if (!isJson) {
|
|
135
|
+
printInitSequence([
|
|
136
|
+
"Rust AST Loaded",
|
|
137
|
+
"Parsing Anchor Workspace",
|
|
138
|
+
"Building Call Graph"
|
|
139
|
+
]);
|
|
140
|
+
console.log("");
|
|
141
|
+
}
|
|
103
142
|
const resolvedOldPath = path.resolve(oldPath);
|
|
104
143
|
const resolvedNewPath = path.resolve(newPath);
|
|
105
144
|
let epicConfig;
|
|
@@ -110,28 +149,59 @@ program
|
|
|
110
149
|
console.error(`epic.toml validation error: ${err.message}`);
|
|
111
150
|
process.exit(1);
|
|
112
151
|
}
|
|
113
|
-
|
|
114
|
-
|
|
152
|
+
// Parse both versions once, then run BOTH the compatibility simulator
|
|
153
|
+
// (state survival) and the existing layout-diff findings off the same AST.
|
|
154
|
+
const { oldProgram, newProgram } = await analyzePrograms(resolvedOldPath, resolvedNewPath, epicConfig);
|
|
155
|
+
const compatibility = simulateCompatibility(oldProgram, newProgram, epicConfig);
|
|
156
|
+
const report = compareAccountLayouts(oldProgram, newProgram, epicConfig);
|
|
157
|
+
const intelligence = createUpgradeIntelligence(report);
|
|
158
|
+
const programName = compatibility.accounts[0]?.account || report.findings[0]?.account || path.basename(resolvedNewPath);
|
|
159
|
+
if (isJson) {
|
|
160
|
+
console.log(JSON.stringify({
|
|
161
|
+
program: programName,
|
|
162
|
+
compatibility,
|
|
163
|
+
findings: report.findings,
|
|
164
|
+
severity: report.severity
|
|
165
|
+
}, null, 2));
|
|
166
|
+
process.exit(compatibility.overall === "Blocked" ? 1 : 0);
|
|
167
|
+
}
|
|
168
|
+
// Lead with the compatibility verdict (the product), then keep the
|
|
169
|
+
// detailed layout findings below it as supporting evidence.
|
|
170
|
+
printCompatibilityReport(compatibility, { program: programName }, { skipTitle: startupShown });
|
|
171
|
+
if (report.findings.length) {
|
|
172
|
+
console.log(colors.gray(DIVIDER));
|
|
173
|
+
console.log(colors.white(colors.bold("LAYOUT FINDINGS (DETAIL)")));
|
|
174
|
+
console.log(colors.gray(DIVIDER));
|
|
175
|
+
console.log("");
|
|
176
|
+
printUpgradeReport(report, intelligence, { program: programName }, { skipTitle: true });
|
|
177
|
+
console.log("");
|
|
178
|
+
}
|
|
179
|
+
// Exit code. BLOCKED always fails CI (state corruption is non-negotiable),
|
|
180
|
+
// overriding fail_on_severity. Other outcomes respect the configured threshold.
|
|
115
181
|
const severityOrder = ["SAFE", "MINOR", "WARNING", "MAJOR", "CRITICAL"];
|
|
116
182
|
const thresholdIndex = severityOrder.indexOf(epicConfig.failOnSeverity);
|
|
117
183
|
const reportSeverityIndex = severityOrder.indexOf(report.severity);
|
|
184
|
+
const severityFails = thresholdIndex !== -1 && reportSeverityIndex !== -1 && reportSeverityIndex >= thresholdIndex;
|
|
185
|
+
const blocked = compatibility.overall === "Blocked";
|
|
186
|
+
const fails = blocked || severityFails;
|
|
118
187
|
console.log(colors.gray(DIVIDER));
|
|
119
188
|
console.log("");
|
|
120
|
-
if (
|
|
189
|
+
if (blocked) {
|
|
190
|
+
console.log(colors.critical(`โ EPIC Guard Blocked: deploying would corrupt existing on-chain accounts.`));
|
|
191
|
+
}
|
|
192
|
+
else if (severityFails) {
|
|
121
193
|
console.log(colors.critical(`โ EPIC Guard Blocked: Upgrade severity is ${report.severity} (threshold: ${epicConfig.failOnSeverity}).`));
|
|
122
194
|
}
|
|
195
|
+
else if (compatibility.overall === "Migration-Required") {
|
|
196
|
+
console.log(colors.warning(`โฒ EPIC Guard: Upgrade is safe only after the migration above is performed.`));
|
|
197
|
+
}
|
|
123
198
|
else {
|
|
124
199
|
console.log(colors.success(`โ EPIC Guard Approved Upgrade.`));
|
|
125
200
|
}
|
|
126
201
|
console.log("");
|
|
127
202
|
console.log(colors.dim(`Time: ${(Date.now() - startTime) / 1000} s`));
|
|
128
203
|
console.log("");
|
|
129
|
-
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
process.exit(0);
|
|
134
|
-
}
|
|
204
|
+
process.exit(fails ? 1 : 0);
|
|
135
205
|
}
|
|
136
206
|
catch (error) {
|
|
137
207
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -151,26 +221,18 @@ function getSeverityLevel(sev) {
|
|
|
151
221
|
return 3;
|
|
152
222
|
return 3;
|
|
153
223
|
}
|
|
154
|
-
function generateSarif(findings) {
|
|
155
|
-
const results = findings.map((f) => ({
|
|
156
|
-
ruleId: f.rule_id,
|
|
157
|
-
level: "warning",
|
|
158
|
-
message: { text: f.message },
|
|
159
|
-
locations: [{ physicalLocation: { artifactLocation: { uri: f.location.file }, region: { startLine: f.location.line } } }]
|
|
160
|
-
}));
|
|
161
|
-
return {
|
|
162
|
-
version: "2.1.0",
|
|
163
|
-
runs: [{ tool: { driver: { name: "EPIC", rules: [] } }, results }]
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
224
|
program
|
|
167
225
|
.command("doctor")
|
|
168
226
|
.description("Run diagnostics on the environment")
|
|
169
227
|
.action(() => {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
228
|
+
const opts = program.opts();
|
|
229
|
+
const startupShown = printStartup("Environment Diagnostics", !opts.banner);
|
|
230
|
+
if (!startupShown) {
|
|
231
|
+
console.log(colors.gray(DIVIDER));
|
|
232
|
+
console.log(colors.bold(colors.white("Environment Diagnostics")));
|
|
233
|
+
console.log(colors.gray(DIVIDER));
|
|
234
|
+
console.log("");
|
|
235
|
+
}
|
|
174
236
|
let hasErrors = false;
|
|
175
237
|
const checkVersion = (cmd, name, required) => {
|
|
176
238
|
try {
|
|
@@ -244,28 +306,35 @@ program
|
|
|
244
306
|
.command("explain <rule_id>")
|
|
245
307
|
.description("Explain a security rule in detail")
|
|
246
308
|
.action((ruleId) => {
|
|
309
|
+
const opts = program.opts();
|
|
310
|
+
printStartup("Rule Explanation", !opts.banner);
|
|
247
311
|
const knowledge = ruleKnowledge[ruleId];
|
|
248
312
|
if (!knowledge) {
|
|
249
313
|
console.log(colors.critical(`Rule ${ruleId} not found.`));
|
|
314
|
+
console.log(colors.dim("Run 'epic rules' to list all available rules."));
|
|
250
315
|
process.exit(1);
|
|
251
316
|
}
|
|
317
|
+
const band = bandForScore(knowledge.score);
|
|
252
318
|
console.log(colors.gray(DIVIDER));
|
|
253
|
-
console.log(colors.bold(colors.white("Rule")));
|
|
254
|
-
console.log(colors.cyan(knowledge.desc));
|
|
255
319
|
console.log("");
|
|
256
|
-
console.log(colors.
|
|
257
|
-
console.log(colors.critical("Critical / High"));
|
|
258
|
-
console.log(colors.gray(DIVIDER));
|
|
320
|
+
console.log(`${severityBadge(band)} ${colors.white(ruleId)} ${colors.gray("ยท")} ${colors.white(knowledge.desc)}`);
|
|
259
321
|
console.log("");
|
|
260
|
-
console.log(colors.
|
|
261
|
-
console.log(colors.dim(knowledge.historical));
|
|
322
|
+
console.log(`${colors.dim("Risk Score")} ${scoreBar(knowledge.score)} ${colors.white(`${knowledge.score} / 100`)}`);
|
|
262
323
|
console.log("");
|
|
263
|
-
console.log(colors.
|
|
264
|
-
console.log(colors.dim(knowledge.fix));
|
|
324
|
+
console.log(colors.gray(DIVIDER));
|
|
265
325
|
console.log("");
|
|
266
|
-
console.log(colors.bold(colors.white("
|
|
326
|
+
console.log(colors.bold(colors.white("WHY IT'S DANGEROUS")));
|
|
267
327
|
console.log(colors.dim(knowledge.why));
|
|
268
328
|
console.log("");
|
|
329
|
+
console.log(colors.bold(colors.white("WHAT BREAKS")));
|
|
330
|
+
console.log(colors.warning(knowledge.impact));
|
|
331
|
+
console.log("");
|
|
332
|
+
console.log(colors.bold(colors.white("HOW TO FIX")));
|
|
333
|
+
console.log(colors.green(knowledge.fix));
|
|
334
|
+
console.log("");
|
|
335
|
+
console.log(colors.bold(colors.white("HISTORICAL EXPLOITS")));
|
|
336
|
+
console.log(colors.dim(knowledge.historical));
|
|
337
|
+
console.log("");
|
|
269
338
|
console.log(colors.gray(DIVIDER));
|
|
270
339
|
console.log("");
|
|
271
340
|
});
|
|
@@ -285,10 +354,12 @@ program
|
|
|
285
354
|
try {
|
|
286
355
|
const opts = program.opts();
|
|
287
356
|
if (options.format === "text")
|
|
288
|
-
|
|
357
|
+
printStartup("Security Audit", !opts.banner);
|
|
289
358
|
const binary = findRustBinary();
|
|
290
359
|
const resolvedPath = path.resolve(targetPath);
|
|
360
|
+
const auditStart = Date.now();
|
|
291
361
|
const result = spawnSync(binary, ["audit", resolvedPath], { encoding: "utf-8" });
|
|
362
|
+
const ruleEngineMs = Date.now() - auditStart;
|
|
292
363
|
if (result.status !== 0)
|
|
293
364
|
throw new Error("Parser failed");
|
|
294
365
|
const findings = JSON.parse(result.stdout.trim());
|
|
@@ -308,7 +379,24 @@ program
|
|
|
308
379
|
return !builtinIgnore.some(p => relPath.includes(`/${p}/`) || relPath.startsWith(`${p}/`) || relPath === p);
|
|
309
380
|
});
|
|
310
381
|
if (options.format === "text") {
|
|
311
|
-
|
|
382
|
+
// Real repository structure from the parser's analyze pass + filesystem.
|
|
383
|
+
const fileCount = countRustFiles(resolvedPath);
|
|
384
|
+
let structsFound = 0, enumsFound = 0, accountsFound = 0;
|
|
385
|
+
let analyzeMs = 0;
|
|
386
|
+
try {
|
|
387
|
+
const analyzeStart = Date.now();
|
|
388
|
+
const analyzeResult = spawnSync(binary, [resolvedPath], { encoding: "utf-8" });
|
|
389
|
+
analyzeMs = Date.now() - analyzeStart;
|
|
390
|
+
if (analyzeResult.status === 0 && analyzeResult.stdout) {
|
|
391
|
+
const overview = JSON.parse(analyzeResult.stdout.trim());
|
|
392
|
+
structsFound = overview.structs_found ?? 0;
|
|
393
|
+
enumsFound = overview.enums_found ?? 0;
|
|
394
|
+
accountsFound = Array.isArray(overview.accounts) ? overview.accounts.length : 0;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
// Analyze enrichment is best-effort; the audit result is authoritative.
|
|
399
|
+
}
|
|
312
400
|
const totalTimeMs = Date.now() - startTime;
|
|
313
401
|
printInitSequence([
|
|
314
402
|
`Scanning Files\n${colors.cyan("โโโโโโโโโโโโโโโโโโโโโโโโโ")} ${colors.dim(`${fileCount} / ${fileCount}`)}`,
|
|
@@ -319,28 +407,22 @@ program
|
|
|
319
407
|
const projName = path.basename(resolvedPath) || ".";
|
|
320
408
|
printSection("Workspace", {
|
|
321
409
|
"Project": projName,
|
|
322
|
-
"
|
|
323
|
-
"Anchor": "0.31",
|
|
324
|
-
"Rules Loaded": 5,
|
|
410
|
+
"Rules Loaded": Object.keys(ruleKnowledge).length,
|
|
325
411
|
"Configuration": options.config || "epic.toml"
|
|
326
412
|
});
|
|
327
413
|
printSection("Repository Overview", {
|
|
328
414
|
"Rust Files": fileCount,
|
|
329
|
-
"
|
|
330
|
-
"
|
|
331
|
-
"
|
|
332
|
-
"PDAs": Math.round(fileCount * 0.22),
|
|
333
|
-
"Anchor Programs": 1
|
|
415
|
+
"Structs": structsFound,
|
|
416
|
+
"Enums": enumsFound,
|
|
417
|
+
"State Accounts": accountsFound
|
|
334
418
|
});
|
|
335
419
|
const criticalCount = activeFindings.filter((f) => getSeverityLevel(f.severity) === 3).length;
|
|
336
420
|
const warningCount = activeFindings.filter((f) => getSeverityLevel(f.severity) < 3).length;
|
|
337
421
|
const rulesTriggered = new Set(activeFindings.map((f) => f.rule_id)).size;
|
|
338
422
|
printSection("Execution Metrics", {
|
|
339
423
|
"Indexed Files": fileCount,
|
|
340
|
-
"AST
|
|
341
|
-
"
|
|
342
|
-
"Rule Engine": `${Math.max(1, Math.round(totalTimeMs * 0.35))} ms`,
|
|
343
|
-
"Rendering": `${Math.max(1, Math.round(totalTimeMs * 0.05))} ms`,
|
|
424
|
+
"Parse + AST": `${Math.max(1, analyzeMs)} ms`,
|
|
425
|
+
"Rule Engine": `${Math.max(1, ruleEngineMs)} ms`,
|
|
344
426
|
"Total": `${(totalTimeMs / 1000).toFixed(2)} s`
|
|
345
427
|
});
|
|
346
428
|
printSection("Security Summary", {
|
|
@@ -387,13 +469,10 @@ program
|
|
|
387
469
|
const knowledge = ruleKnowledge[mostCommonRule];
|
|
388
470
|
console.log(colors.bold(colors.white("Most Common Issue")));
|
|
389
471
|
console.log(colors.dim(`${knowledge.desc}`));
|
|
390
|
-
console.log(colors.cyan(`${highestOccurrences}
|
|
391
|
-
console.log("");
|
|
392
|
-
console.log(colors.dim("Estimated Fix Time"));
|
|
393
|
-
console.log(colors.white("~25-40 minutes"));
|
|
472
|
+
console.log(colors.cyan(`${highestOccurrences} occurrence${highestOccurrences === 1 ? "" : "s"}`));
|
|
394
473
|
console.log("");
|
|
395
474
|
console.log(colors.dim("Priority"));
|
|
396
|
-
console.log(colors.white(`Resolve this rule
|
|
475
|
+
console.log(colors.white(`Resolve this rule first โ it accounts for the most findings.`));
|
|
397
476
|
console.log("");
|
|
398
477
|
}
|
|
399
478
|
printEndSummary(projName, 5, criticalCount, warningCount, Date.now() - startTime);
|
|
@@ -402,18 +481,13 @@ program
|
|
|
402
481
|
console.log(JSON.stringify(activeFindings, null, 2));
|
|
403
482
|
}
|
|
404
483
|
else if (options.format === "sarif") {
|
|
405
|
-
|
|
484
|
+
console.log(JSON.stringify(generateSarif(activeFindings), null, 2));
|
|
406
485
|
}
|
|
407
486
|
else if (options.format === "markdown") {
|
|
408
|
-
console.log(
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
for (const finding of activeFindings) {
|
|
413
|
-
console.log(`### ${finding.rule_id}: ${finding.rule_name || finding.rule_id}`);
|
|
414
|
-
console.log(`**Location:** \`${finding.location.file}:${finding.location.line}\``);
|
|
415
|
-
console.log(`**Message:** ${finding.message}\n`);
|
|
416
|
-
}
|
|
487
|
+
console.log(generateMarkdown(activeFindings, {
|
|
488
|
+
project: path.basename(resolvedPath) || ".",
|
|
489
|
+
scanMs: Date.now() - startTime
|
|
490
|
+
}));
|
|
417
491
|
}
|
|
418
492
|
if (options.strict) {
|
|
419
493
|
const threshold = epicConfig.failOnSeverity || "CRITICAL";
|
|
@@ -447,34 +521,42 @@ program
|
|
|
447
521
|
.command("rules")
|
|
448
522
|
.description("List all available security rules.")
|
|
449
523
|
.action(() => {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
524
|
+
const opts = program.opts();
|
|
525
|
+
const startupShown = printStartup("Security Rules", !opts.banner);
|
|
526
|
+
const rules = [
|
|
527
|
+
["EPIC-SEC-001", "Owner Validation"],
|
|
528
|
+
["EPIC-SEC-002", "Missing Signer Validation"],
|
|
529
|
+
["EPIC-SEC-003", "Missing Post-CPI Account Reload"],
|
|
530
|
+
["EPIC-SEC-004", "PDA Cryptographic Seed Collision Risk"],
|
|
531
|
+
["EPIC-SEC-005", "Arbitrary CPI Target Program Spoofing"]
|
|
532
|
+
];
|
|
533
|
+
if (!startupShown) {
|
|
534
|
+
console.log(colors.gray(DIVIDER));
|
|
535
|
+
console.log(colors.white(colors.bold("EPIC Security Rules")));
|
|
536
|
+
console.log(colors.gray(DIVIDER));
|
|
537
|
+
console.log("");
|
|
538
|
+
}
|
|
539
|
+
for (const [id, name] of rules) {
|
|
540
|
+
const kb = ruleKnowledge[id];
|
|
541
|
+
const score = kb ? kb.score : 50;
|
|
542
|
+
const band = bandForScore(score);
|
|
543
|
+
console.log(`${severityBadge(band)} ${colors.white(id)} ${colors.gray("ยท")} ${colors.white(name)}`);
|
|
544
|
+
if (kb) {
|
|
545
|
+
console.log(` ${scoreBar(score, 16)} ${colors.dim(`${score} / 100`)} ${colors.gray("Implemented")}`);
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
console.log(` ${colors.gray("Implemented")}`);
|
|
549
|
+
}
|
|
550
|
+
console.log("");
|
|
551
|
+
}
|
|
552
|
+
console.log(colors.dim("Run 'epic explain <RULE-ID>' for a full breakdown of any rule."));
|
|
553
|
+
console.log("");
|
|
470
554
|
});
|
|
471
555
|
program.configureHelp({
|
|
472
556
|
formatHelp: (cmd, helper) => {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
${colors.cyan("v" + CLI_VERSION)}
|
|
477
|
-
|
|
557
|
+
const noBannerFlag = !!cmd.opts().noBanner || process.argv.includes("--no-banner");
|
|
558
|
+
const header = getBannerString(noBannerFlag);
|
|
559
|
+
return `${header}
|
|
478
560
|
${colors.bold("Commands")}
|
|
479
561
|
${colors.white("audit".padEnd(14))} Run security rules against the repository.
|
|
480
562
|
${colors.white("doctor".padEnd(14))} Run diagnostics on the environment.
|