hex-audit 0.4.3 → 0.4.5
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/DEMO.md +3 -2
- package/README.md +45 -41
- package/dashboard/.next/BUILD_ID +1 -1
- package/dashboard/.next/app-build-manifest.json +24 -17
- package/dashboard/.next/app-path-routes-manifest.json +1 -1
- package/dashboard/.next/build-manifest.json +4 -4
- package/dashboard/.next/next-minimal-server.js.nft.json +1 -1
- package/dashboard/.next/next-server.js.nft.json +1 -1
- package/dashboard/.next/prerender-manifest.js +1 -1
- package/dashboard/.next/prerender-manifest.json +1 -1
- package/dashboard/.next/required-server-files.json +1 -1
- package/dashboard/.next/routes-manifest.json +1 -1
- package/dashboard/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/_not-found.html +1 -1
- package/dashboard/.next/server/app/_not-found.rsc +4 -4
- package/dashboard/.next/server/app/access/page.js +1 -1
- package/dashboard/.next/server/app/access/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/api/flows/route.js +1 -1
- package/dashboard/.next/server/app/api/github-status/route.js +1 -1
- package/dashboard/.next/server/app/api/issues/[id]/move/route.js +1 -1
- package/dashboard/.next/server/app/api/issues/[id]/route.js +1 -1
- package/dashboard/.next/server/app/api/mermaid/route.js +1 -1
- package/dashboard/.next/server/app/api/progress/route.js +1 -1
- package/dashboard/.next/server/app/api/watch/route.js +2 -2
- package/dashboard/.next/server/app/calls/page.js +1 -1
- package/dashboard/.next/server/app/calls/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/conformance/page.js +1 -1
- package/dashboard/.next/server/app/conformance/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/diagram/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/diagram.html +1 -1
- package/dashboard/.next/server/app/diagram.rsc +4 -4
- package/dashboard/.next/server/app/flows/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/flows.html +1 -1
- package/dashboard/.next/server/app/flows.rsc +4 -4
- package/dashboard/.next/server/app/functions/page.js +1 -1
- package/dashboard/.next/server/app/functions/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/issues/page.js +12 -1
- package/dashboard/.next/server/app/issues/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/overleaf/page.js +1 -0
- package/dashboard/.next/server/app/overleaf/page.js.nft.json +1 -0
- package/dashboard/.next/server/app/overleaf/page_client-reference-manifest.js +1 -0
- package/dashboard/.next/server/app/page.js +2 -2
- package/dashboard/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/progress/page.js +1 -1
- package/dashboard/.next/server/app/progress/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app/stats/page.js +1 -1
- package/dashboard/.next/server/app/stats/page_client-reference-manifest.js +1 -1
- package/dashboard/.next/server/app-paths-manifest.json +12 -11
- package/dashboard/.next/server/chunks/431.js +1 -1
- package/dashboard/.next/server/chunks/557.js +1 -1
- package/dashboard/.next/server/functions-config-manifest.json +1 -1
- package/dashboard/.next/server/middleware-build-manifest.js +1 -1
- package/dashboard/.next/server/pages/404.html +1 -1
- package/dashboard/.next/server/pages/500.html +1 -1
- package/dashboard/.next/server/server-reference-manifest.js +1 -1
- package/dashboard/.next/server/server-reference-manifest.json +1 -1
- package/dashboard/.next/static/chunks/app/issues/page-30f7bcde0f4f6ddb.js +1 -0
- package/dashboard/.next/static/chunks/app/{layout-e0f50820b06a7621.js → layout-cde7d75ea6df4f81.js} +1 -1
- package/dashboard/.next/static/chunks/app/overleaf/page-bcea12a80c97b92f.js +1 -0
- package/dashboard/.next/static/chunks/{main-70c6d841ad572657.js → main-8f1e837090aef5cf.js} +1 -1
- package/dashboard/.next/static/css/36c6bb859d4545fd.css +3 -0
- package/dashboard/app/access/page.tsx +7 -0
- package/dashboard/app/api/flows/route.ts +4 -0
- package/dashboard/app/api/issues/[id]/move/route.ts +29 -58
- package/dashboard/app/api/issues/[id]/route.ts +11 -102
- package/dashboard/app/calls/page.tsx +7 -0
- package/dashboard/app/conformance/page.tsx +7 -0
- package/dashboard/app/functions/page.tsx +7 -0
- package/dashboard/app/issues/IssuesBoardClient.tsx +109 -35
- package/dashboard/app/issues/page.tsx +43 -2
- package/dashboard/app/overleaf/OverleafClient.tsx +97 -0
- package/dashboard/app/overleaf/page.tsx +32 -0
- package/dashboard/app/page.tsx +7 -0
- package/dashboard/app/progress/page.tsx +7 -0
- package/dashboard/app/stats/page.tsx +7 -0
- package/dashboard/components/DashboardLayout.tsx +8 -2
- package/dashboard/lib/data.ts +4 -1
- package/dashboard/lib/finding-markdown.ts +59 -0
- package/dashboard/next.config.mjs +4 -0
- package/dist/analysis/external-calls.d.ts +8 -1
- package/dist/analysis/external-calls.d.ts.map +1 -1
- package/dist/analysis/external-calls.js +57 -13
- package/dist/analysis/external-calls.js.map +1 -1
- package/dist/commands/calls.d.ts.map +1 -1
- package/dist/commands/calls.js +15 -6
- package/dist/commands/calls.js.map +1 -1
- package/dist/commands/dashboard.js +6 -6
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +17 -19
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/issue.d.ts +3 -0
- package/dist/commands/issue.d.ts.map +1 -0
- package/dist/commands/issue.js +115 -0
- package/dist/commands/issue.js.map +1 -0
- package/dist/core/issues.d.ts +79 -0
- package/dist/core/issues.d.ts.map +1 -0
- package/dist/core/issues.js +253 -0
- package/dist/core/issues.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/skills/init-audit.md +3 -1
- package/dist/skills/sync-issues.md +17 -34
- package/dist/skills/validate-issue.md +20 -10
- package/dist/skills/write-finding.md +1 -1
- package/package.json +1 -1
- package/dashboard/.next/server/app/access.html +0 -1
- package/dashboard/.next/server/app/access.meta +0 -5
- package/dashboard/.next/server/app/access.rsc +0 -8
- package/dashboard/.next/server/app/api/flows.body +0 -1
- package/dashboard/.next/server/app/api/flows.meta +0 -1
- package/dashboard/.next/server/app/calls.html +0 -1
- package/dashboard/.next/server/app/calls.meta +0 -5
- package/dashboard/.next/server/app/calls.rsc +0 -8
- package/dashboard/.next/server/app/conformance.html +0 -1
- package/dashboard/.next/server/app/conformance.meta +0 -5
- package/dashboard/.next/server/app/conformance.rsc +0 -8
- package/dashboard/.next/server/app/functions.html +0 -1
- package/dashboard/.next/server/app/functions.meta +0 -5
- package/dashboard/.next/server/app/functions.rsc +0 -8
- package/dashboard/.next/server/app/index.html +0 -1
- package/dashboard/.next/server/app/index.meta +0 -5
- package/dashboard/.next/server/app/index.rsc +0 -9
- package/dashboard/.next/server/app/issues.html +0 -1
- package/dashboard/.next/server/app/issues.meta +0 -5
- package/dashboard/.next/server/app/issues.rsc +0 -8
- package/dashboard/.next/server/app/progress.html +0 -1
- package/dashboard/.next/server/app/progress.meta +0 -5
- package/dashboard/.next/server/app/progress.rsc +0 -8
- package/dashboard/.next/server/app/stats.html +0 -1
- package/dashboard/.next/server/app/stats.meta +0 -5
- package/dashboard/.next/server/app/stats.rsc +0 -8
- package/dashboard/.next/static/chunks/app/issues/page-dc110d4cc0a5c398.js +0 -1
- package/dashboard/.next/static/css/265d7e2538aaffc0.css +0 -3
- /package/dashboard/.next/static/{f3-iD1w0WCbTDKsUc5-Id → 4HWoeGmBSZi6iGcKL3VWA}/_buildManifest.js +0 -0
- /package/dashboard/.next/static/{f3-iD1w0WCbTDKsUc5-Id → 4HWoeGmBSZi6iGcKL3VWA}/_ssgManifest.js +0 -0
|
@@ -2,6 +2,12 @@ import { readJsonFile, readNestedJsonFile, listSubdirs } from '@/lib/data';
|
|
|
2
2
|
import { NotYetGenerated } from '@/components/NotYetGenerated';
|
|
3
3
|
import { IssuesBoardClient, type BoardIssue } from './IssuesBoardClient';
|
|
4
4
|
|
|
5
|
+
// Read .hex/ at request time, not at build time. Without this, next build
|
|
6
|
+
// prerenders this page with whatever .hex/ data was available in the build
|
|
7
|
+
// environment and ships static HTML — the dashboard then renders that
|
|
8
|
+
// stale (or empty) HTML for every audit project, ignoring the runtime cwd.
|
|
9
|
+
export const dynamic = 'force-dynamic';
|
|
10
|
+
|
|
5
11
|
interface FindingsFile {
|
|
6
12
|
findings: Array<{
|
|
7
13
|
id: string;
|
|
@@ -13,11 +19,17 @@ interface FindingsFile {
|
|
|
13
19
|
update_from_client?: string;
|
|
14
20
|
root_cause?: { locations?: Array<{ file: string; snippet?: string }> };
|
|
15
21
|
poc?: { status?: string; file?: string | null; validation_memo?: string | null };
|
|
16
|
-
github?: { issue_number?: number; issue_url?: string; state?: string };
|
|
22
|
+
github?: { issue_number?: number; issue_url?: string; state?: string; sync_status?: string };
|
|
17
23
|
category?: string;
|
|
18
24
|
}>;
|
|
19
25
|
}
|
|
20
26
|
|
|
27
|
+
interface ConfigFile {
|
|
28
|
+
settings?: {
|
|
29
|
+
report?: { repository_url?: string; initial_commit?: string };
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
interface TrackingFile {
|
|
22
34
|
findings: Array<{
|
|
23
35
|
id: string;
|
|
@@ -67,6 +79,26 @@ interface SpecConformanceFile {
|
|
|
67
79
|
}>;
|
|
68
80
|
}
|
|
69
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Derive the GitHub sync chip state. Only verified findings are push
|
|
84
|
+
* candidates, so anything else returns 'none' (no chip).
|
|
85
|
+
*/
|
|
86
|
+
function computeSyncState(
|
|
87
|
+
status: string | undefined,
|
|
88
|
+
github: { issue_number?: number; state?: string; sync_status?: string } | undefined,
|
|
89
|
+
): BoardIssue['sync_state'] {
|
|
90
|
+
if (status !== 'verified') return 'none';
|
|
91
|
+
if (!github?.issue_number) return 'unsynced';
|
|
92
|
+
if (
|
|
93
|
+
github.sync_status === 'conflict' ||
|
|
94
|
+
github.sync_status === 'local_ahead' ||
|
|
95
|
+
github.sync_status === 'remote_ahead'
|
|
96
|
+
) {
|
|
97
|
+
return 'conflict';
|
|
98
|
+
}
|
|
99
|
+
return github.state === 'closed' ? 'synced_closed' : 'synced_open';
|
|
100
|
+
}
|
|
101
|
+
|
|
70
102
|
function statusToColumn(status: string | undefined): BoardIssue['column'] {
|
|
71
103
|
switch (status) {
|
|
72
104
|
case 'verified':
|
|
@@ -172,7 +204,9 @@ function buildBoardIssues(): BoardIssue[] {
|
|
|
172
204
|
code_locations: locations,
|
|
173
205
|
github_synced: Boolean(finding?.github?.issue_number),
|
|
174
206
|
github_issue_url: finding?.github?.issue_url,
|
|
207
|
+
github_issue_number: finding?.github?.issue_number,
|
|
175
208
|
github_state: finding?.github?.state,
|
|
209
|
+
sync_state: computeSyncState(t.status, finding?.github),
|
|
176
210
|
duplicate_of: t.duplicate_of ?? dup?.matches ?? null,
|
|
177
211
|
match_signals: matchSignals,
|
|
178
212
|
reasoning: dup?.reasoning,
|
|
@@ -203,7 +237,9 @@ function buildBoardIssues(): BoardIssue[] {
|
|
|
203
237
|
code_locations: f.root_cause?.locations ?? [],
|
|
204
238
|
github_synced: Boolean(f.github?.issue_number),
|
|
205
239
|
github_issue_url: f.github?.issue_url,
|
|
240
|
+
github_issue_number: f.github?.issue_number,
|
|
206
241
|
github_state: f.github?.state,
|
|
242
|
+
sync_state: computeSyncState('verified', f.github),
|
|
207
243
|
duplicate_of: null,
|
|
208
244
|
match_signals: undefined,
|
|
209
245
|
reasoning: undefined,
|
|
@@ -220,6 +256,11 @@ function buildBoardIssues(): BoardIssue[] {
|
|
|
220
256
|
|
|
221
257
|
export default function IssuesPage() {
|
|
222
258
|
const issues = buildBoardIssues();
|
|
259
|
+
const config = readJsonFile<ConfigFile>('config.json');
|
|
260
|
+
const formatCtx = {
|
|
261
|
+
repositoryUrl: config?.settings?.report?.repository_url,
|
|
262
|
+
commit: config?.settings?.report?.initial_commit,
|
|
263
|
+
};
|
|
223
264
|
|
|
224
265
|
if (issues.length === 0) {
|
|
225
266
|
return (
|
|
@@ -235,5 +276,5 @@ export default function IssuesPage() {
|
|
|
235
276
|
);
|
|
236
277
|
}
|
|
237
278
|
|
|
238
|
-
return <IssuesBoardClient issues={issues} />;
|
|
279
|
+
return <IssuesBoardClient issues={issues} formatCtx={formatCtx} />;
|
|
239
280
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface OverleafSections {
|
|
6
|
+
executive_summary: string | null;
|
|
7
|
+
audited_files: string | null;
|
|
8
|
+
summary_of_findings: string | null;
|
|
9
|
+
findings: string | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type SectionKey = keyof OverleafSections;
|
|
13
|
+
|
|
14
|
+
const TABS: Array<{ key: SectionKey; label: string; file: string }> = [
|
|
15
|
+
{ key: 'executive_summary', label: 'Executive Summary', file: 'executive_summary.txt' },
|
|
16
|
+
{ key: 'audited_files', label: 'Audited Files', file: 'audited_files.txt' },
|
|
17
|
+
{ key: 'summary_of_findings', label: 'Summary of Issues', file: 'summary_of_findings.txt' },
|
|
18
|
+
{ key: 'findings', label: 'Issues', file: 'findings.txt' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export function OverleafClient({ sections }: { sections: OverleafSections }) {
|
|
22
|
+
const [active, setActive] = useState<SectionKey>('executive_summary');
|
|
23
|
+
const [copied, setCopied] = useState(false);
|
|
24
|
+
|
|
25
|
+
const content = sections[active];
|
|
26
|
+
|
|
27
|
+
const copy = async () => {
|
|
28
|
+
if (!content) return;
|
|
29
|
+
try {
|
|
30
|
+
await navigator.clipboard.writeText(content);
|
|
31
|
+
setCopied(true);
|
|
32
|
+
setTimeout(() => setCopied(false), 1500);
|
|
33
|
+
} catch {
|
|
34
|
+
/* clipboard blocked (insecure context) — no-op */
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div>
|
|
40
|
+
<header className="mb-6">
|
|
41
|
+
<h1 className="text-title font-semibold text-text-primary">Overleaf</h1>
|
|
42
|
+
<p className="mt-1 text-body text-text-secondary">
|
|
43
|
+
LaTeX report sections from <span className="font-mono text-caption">.hex/overleaf/</span>.
|
|
44
|
+
Copy each into the matching slot of the Nethermind Overleaf template.
|
|
45
|
+
</p>
|
|
46
|
+
</header>
|
|
47
|
+
|
|
48
|
+
{/* Segmented control */}
|
|
49
|
+
<div className="mb-4 flex flex-wrap gap-1.5">
|
|
50
|
+
{TABS.map((tab) => {
|
|
51
|
+
const isActive = tab.key === active;
|
|
52
|
+
const present = Boolean(sections[tab.key]);
|
|
53
|
+
return (
|
|
54
|
+
<button
|
|
55
|
+
key={tab.key}
|
|
56
|
+
type="button"
|
|
57
|
+
onClick={() => setActive(tab.key)}
|
|
58
|
+
className={`rounded-md px-3 py-1.5 text-caption font-medium ${
|
|
59
|
+
isActive
|
|
60
|
+
? 'bg-accent text-surface-0'
|
|
61
|
+
: 'bg-surface-2 text-text-secondary hover:text-text-primary'
|
|
62
|
+
} ${present ? '' : 'opacity-50'}`}
|
|
63
|
+
>
|
|
64
|
+
{tab.label}
|
|
65
|
+
</button>
|
|
66
|
+
);
|
|
67
|
+
})}
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div className="rounded-lg border border-border-subtle bg-surface-1">
|
|
71
|
+
<div className="flex items-center justify-between border-b border-border-subtle px-4 py-2.5">
|
|
72
|
+
<span className="font-mono text-caption text-text-tertiary">
|
|
73
|
+
{TABS.find((t) => t.key === active)?.file}
|
|
74
|
+
</span>
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
onClick={copy}
|
|
78
|
+
disabled={!content}
|
|
79
|
+
className="rounded-md border border-border-default px-2.5 py-1 text-caption text-text-secondary hover:bg-surface-2 hover:text-text-primary disabled:opacity-50"
|
|
80
|
+
>
|
|
81
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
{content ? (
|
|
85
|
+
<pre className="max-h-[70vh] overflow-auto p-4 font-mono text-caption leading-relaxed text-text-secondary whitespace-pre-wrap break-words">
|
|
86
|
+
{content}
|
|
87
|
+
</pre>
|
|
88
|
+
) : (
|
|
89
|
+
<p className="p-4 text-body text-text-tertiary">
|
|
90
|
+
This section has not been generated yet. Run{' '}
|
|
91
|
+
<span className="font-mono text-caption">/generate-overleaf</span>.
|
|
92
|
+
</p>
|
|
93
|
+
)}
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readMarkdownFile } from '@/lib/data';
|
|
2
|
+
import { NotYetGenerated } from '@/components/NotYetGenerated';
|
|
3
|
+
import { OverleafClient, type OverleafSections } from './OverleafClient';
|
|
4
|
+
|
|
5
|
+
// Read .hex/overleaf/ at request time, not at build time.
|
|
6
|
+
export const dynamic = 'force-dynamic';
|
|
7
|
+
|
|
8
|
+
export default function OverleafPage() {
|
|
9
|
+
const sections: OverleafSections = {
|
|
10
|
+
executive_summary: readMarkdownFile('overleaf/executive_summary.txt'),
|
|
11
|
+
audited_files: readMarkdownFile('overleaf/audited_files.txt'),
|
|
12
|
+
summary_of_findings: readMarkdownFile('overleaf/summary_of_findings.txt'),
|
|
13
|
+
findings: readMarkdownFile('overleaf/findings.txt'),
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const anyPresent = Object.values(sections).some(Boolean);
|
|
17
|
+
if (!anyPresent) {
|
|
18
|
+
return (
|
|
19
|
+
<div>
|
|
20
|
+
<header className="mb-6">
|
|
21
|
+
<h1 className="text-title font-semibold text-text-primary">Overleaf</h1>
|
|
22
|
+
<p className="mt-1 text-body text-text-secondary">
|
|
23
|
+
LaTeX report sections, ready to paste into the Nethermind Overleaf template.
|
|
24
|
+
</p>
|
|
25
|
+
</header>
|
|
26
|
+
<NotYetGenerated command="/generate-overleaf" />
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return <OverleafClient sections={sections} />;
|
|
32
|
+
}
|
package/dashboard/app/page.tsx
CHANGED
|
@@ -3,6 +3,13 @@ import { readJsonFile, readMarkdownFile } from '@/lib/data';
|
|
|
3
3
|
import { NotYetGenerated } from '@/components/NotYetGenerated';
|
|
4
4
|
import { MarkdownRenderer } from '@/components/MarkdownRenderer';
|
|
5
5
|
|
|
6
|
+
// Read .hex/ at request time, not at build time. Without this, next build
|
|
7
|
+
// prerenders this page with whatever .hex/ data was available in the build
|
|
8
|
+
// environment and ships static HTML — the dashboard then renders that
|
|
9
|
+
// stale (or empty) HTML for every audit project, ignoring the runtime cwd.
|
|
10
|
+
export const dynamic = 'force-dynamic';
|
|
11
|
+
|
|
12
|
+
|
|
6
13
|
interface ProjectConfig {
|
|
7
14
|
project: {
|
|
8
15
|
name: string;
|
|
@@ -2,6 +2,13 @@ import { readJsonFile, fileExists } from '@/lib/data';
|
|
|
2
2
|
import { NotYetGenerated } from '@/components/NotYetGenerated';
|
|
3
3
|
import { ProgressClient, type ProgressClientProps } from './ProgressClient';
|
|
4
4
|
|
|
5
|
+
// Read .hex/ at request time, not at build time. Without this, next build
|
|
6
|
+
// prerenders this page with whatever .hex/ data was available in the build
|
|
7
|
+
// environment and ships static HTML — the dashboard then renders that
|
|
8
|
+
// stale (or empty) HTML for every audit project, ignoring the runtime cwd.
|
|
9
|
+
export const dynamic = 'force-dynamic';
|
|
10
|
+
|
|
11
|
+
|
|
5
12
|
// ─── Types ──────────────────────────────────────────────────────────
|
|
6
13
|
|
|
7
14
|
interface PerContractStats {
|
|
@@ -2,6 +2,13 @@ import { readJsonFile } from '@/lib/data';
|
|
|
2
2
|
import { NotYetGenerated } from '@/components/NotYetGenerated';
|
|
3
3
|
import { StatsClient, type StatsClientProps } from './StatsClient';
|
|
4
4
|
|
|
5
|
+
// Read .hex/ at request time, not at build time. Without this, next build
|
|
6
|
+
// prerenders this page with whatever .hex/ data was available in the build
|
|
7
|
+
// environment and ships static HTML — the dashboard then renders that
|
|
8
|
+
// stale (or empty) HTML for every audit project, ignoring the runtime cwd.
|
|
9
|
+
export const dynamic = 'force-dynamic';
|
|
10
|
+
|
|
11
|
+
|
|
5
12
|
// ─── Types ──────────────────────────────────────────────────────────
|
|
6
13
|
|
|
7
14
|
interface Stats {
|
|
@@ -326,7 +326,13 @@ const NAV_GROUPS = [
|
|
|
326
326
|
{ href: '/flows', label: 'Flows', icon: IconFlows },
|
|
327
327
|
],
|
|
328
328
|
},
|
|
329
|
-
{
|
|
329
|
+
{
|
|
330
|
+
label: 'Issues',
|
|
331
|
+
items: [
|
|
332
|
+
{ href: '/issues', label: 'Board', icon: IconFindings },
|
|
333
|
+
{ href: '/overleaf', label: 'Overleaf', icon: IconReport },
|
|
334
|
+
],
|
|
335
|
+
},
|
|
330
336
|
];
|
|
331
337
|
|
|
332
338
|
export { NAV_GROUPS };
|
|
@@ -395,7 +401,7 @@ export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
|
|
395
401
|
<div className="px-5 pb-4 pt-6">
|
|
396
402
|
<div className="flex items-baseline gap-2">
|
|
397
403
|
<span className="text-title font-semibold text-text-primary">Hex</span>
|
|
398
|
-
<span className="text-caption text-text-tertiary">v0.4.
|
|
404
|
+
<span className="text-caption text-text-tertiary">v0.4.5</span>
|
|
399
405
|
</div>
|
|
400
406
|
</div>
|
|
401
407
|
|
package/dashboard/lib/data.ts
CHANGED
|
@@ -2,7 +2,10 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
|
|
4
4
|
function getProjectDir(): string {
|
|
5
|
-
|
|
5
|
+
// The `hex dashboard` CLI spawns Next.js with cwd = the user's audit project,
|
|
6
|
+
// so process.cwd() is authoritative. (The HEX_PROJECT_DIR env var was a
|
|
7
|
+
// vestige from the old monorepo layout and has been removed.)
|
|
8
|
+
return process.cwd();
|
|
6
9
|
}
|
|
7
10
|
|
|
8
11
|
function getOutputDir(): string {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { BoardIssue } from '@/app/issues/IssuesBoardClient';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Single source of truth for the finding markdown format used by the board's
|
|
5
|
+
* "Copy as HackMD" button (and matching the GitHub issue body the /sync-issues
|
|
6
|
+
* skill renders). Keep this aligned with src/skills/sync-issues.md.
|
|
7
|
+
*/
|
|
8
|
+
export interface FormatContext {
|
|
9
|
+
/** config.json settings.report.repository_url, if set */
|
|
10
|
+
repositoryUrl?: string;
|
|
11
|
+
/** config.json settings.report.initial_commit, if set */
|
|
12
|
+
commit?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fileLink(file: string, ctx: FormatContext): string {
|
|
16
|
+
if (ctx.repositoryUrl && ctx.commit) {
|
|
17
|
+
const base = ctx.repositoryUrl.replace(/\/+$/, '');
|
|
18
|
+
return `[\`${file}\`](${base}/blob/${ctx.commit}/${file})`;
|
|
19
|
+
}
|
|
20
|
+
// Empty link target — explicit, matches the GitHub issue template.
|
|
21
|
+
return `[\`${file}\`]()`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The five-field body shared by HackMD copy and the GitHub issue body. */
|
|
25
|
+
function bodyFields(issue: BoardIssue, ctx: FormatContext): string {
|
|
26
|
+
const seen = new Set<string>();
|
|
27
|
+
const files = issue.code_locations
|
|
28
|
+
.map((l) => l.file)
|
|
29
|
+
.filter((f) => f && !seen.has(f) && seen.add(f))
|
|
30
|
+
.map((f) => fileLink(f, ctx))
|
|
31
|
+
.join(', ');
|
|
32
|
+
const status = issue.resolution || 'Unresolved';
|
|
33
|
+
const update = issue.update_from_client ?? '';
|
|
34
|
+
return [
|
|
35
|
+
`**File(s)**: ${files}`,
|
|
36
|
+
``,
|
|
37
|
+
`**Description**: ${issue.description}`,
|
|
38
|
+
``,
|
|
39
|
+
`**Recommendation(s)**: ${issue.recommendation}`,
|
|
40
|
+
``,
|
|
41
|
+
`**Status**: ${status}`,
|
|
42
|
+
``,
|
|
43
|
+
`**Update from the client**: ${update}`,
|
|
44
|
+
].join('\n');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** HackMD-style: leading `## [Severity] Title` heading, then the body. */
|
|
48
|
+
export function findingToHackmd(issue: BoardIssue, ctx: FormatContext = {}): string {
|
|
49
|
+
return `## [${issue.severity}] ${issue.title}\n\n${bodyFields(issue, ctx)}\n`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* GitHub issue body: no heading (the GH issue title carries `[Severity] Title`),
|
|
54
|
+
* no trailing attribution. Returns the body WITHOUT the round-trip footer; the
|
|
55
|
+
* sync caller appends `<!-- hex-finding-id: <id> -->` separately.
|
|
56
|
+
*/
|
|
57
|
+
export function findingToGithubBody(issue: BoardIssue, ctx: FormatContext = {}): string {
|
|
58
|
+
return bodyFields(issue, ctx);
|
|
59
|
+
}
|
|
@@ -3,6 +3,10 @@ const nextConfig = {
|
|
|
3
3
|
reactStrictMode: true,
|
|
4
4
|
experimental: {
|
|
5
5
|
serverComponentsExternalPackages: ['chokidar'],
|
|
6
|
+
// Allow importing the canonical issue-mutation module from ../src/core/
|
|
7
|
+
// (shared with the `hex issue` CLI). The module depends only on node
|
|
8
|
+
// builtins, so nothing heavy leaks into the bundle.
|
|
9
|
+
externalDir: true,
|
|
6
10
|
},
|
|
7
11
|
};
|
|
8
12
|
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { SlitherDetectorResult } from '../parsers/slither.js';
|
|
2
2
|
import type { ASTExternalCall } from '../parsers/solidity-parser.js';
|
|
3
3
|
import type { ExternalCall } from '../types/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Whether Slither enrichment ran, and what it found:
|
|
6
|
+
* - 'with-findings': Slither ran and reported detector results (most authoritative)
|
|
7
|
+
* - 'clean': Slither ran but reported nothing for this codebase
|
|
8
|
+
* - 'unavailable': Slither did not run (binary missing, parse error, etc.)
|
|
9
|
+
*/
|
|
10
|
+
export type SlitherState = 'with-findings' | 'clean' | 'unavailable';
|
|
4
11
|
/**
|
|
5
12
|
* Build external calls from AST-extracted calls, optionally enriched with Slither data.
|
|
6
13
|
*/
|
|
7
|
-
export declare function buildExternalCalls(astCalls: ASTExternalCall[], stateVarsImmutability: Map<string, boolean>, scopeContracts: Set<string>, accessControlledSetters: Map<string, string>, slitherDetectors?: SlitherDetectorResult[]): ExternalCall[];
|
|
14
|
+
export declare function buildExternalCalls(astCalls: ASTExternalCall[], stateVarsImmutability: Map<string, boolean>, scopeContracts: Set<string>, accessControlledSetters: Map<string, string>, slitherDetectors?: SlitherDetectorResult[], slitherState?: SlitherState): ExternalCall[];
|
|
8
15
|
//# sourceMappingURL=external-calls.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external-calls.d.ts","sourceRoot":"","sources":["../../src/analysis/external-calls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"external-calls.d.ts","sourceRoot":"","sources":["../../src/analysis/external-calls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAiCtD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,OAAO,GAAG,aAAa,CAAC;AAErE;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,eAAe,EAAE,EAC3B,qBAAqB,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3C,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,EAC3B,uBAAuB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5C,gBAAgB,CAAC,EAAE,qBAAqB,EAAE,EAC1C,YAAY,GAAE,YAIG,GAChB,YAAY,EAAE,CA8FhB"}
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
// Call type classification heuristics
|
|
2
2
|
const CALL_TYPE_PATTERNS = {
|
|
3
|
-
token_transfer: [
|
|
4
|
-
|
|
3
|
+
token_transfer: [
|
|
4
|
+
'transfer',
|
|
5
|
+
'transferFrom',
|
|
6
|
+
'safeTransfer',
|
|
7
|
+
'safeTransferFrom',
|
|
8
|
+
'approve',
|
|
9
|
+
'send',
|
|
10
|
+
],
|
|
11
|
+
oracle_read: [
|
|
12
|
+
'getPrice',
|
|
13
|
+
'latestRoundData',
|
|
14
|
+
'latestAnswer',
|
|
15
|
+
'getRoundData',
|
|
16
|
+
'consult',
|
|
17
|
+
'observe',
|
|
18
|
+
],
|
|
5
19
|
flash_loan: ['flashLoan', 'flash', 'flashBorrow'],
|
|
6
|
-
swap: [
|
|
20
|
+
swap: [
|
|
21
|
+
'swap',
|
|
22
|
+
'swapExactTokensForTokens',
|
|
23
|
+
'swapTokensForExactTokens',
|
|
24
|
+
'exactInputSingle',
|
|
25
|
+
'exactInput',
|
|
26
|
+
],
|
|
7
27
|
liquidity: ['addLiquidity', 'removeLiquidity', 'mint', 'burn'],
|
|
8
28
|
delegate_call: ['delegatecall'],
|
|
9
29
|
low_level: ['call', 'staticcall'],
|
|
@@ -11,7 +31,11 @@ const CALL_TYPE_PATTERNS = {
|
|
|
11
31
|
/**
|
|
12
32
|
* Build external calls from AST-extracted calls, optionally enriched with Slither data.
|
|
13
33
|
*/
|
|
14
|
-
export function buildExternalCalls(astCalls, stateVarsImmutability, scopeContracts, accessControlledSetters, slitherDetectors
|
|
34
|
+
export function buildExternalCalls(astCalls, stateVarsImmutability, scopeContracts, accessControlledSetters, slitherDetectors, slitherState = slitherDetectors != null
|
|
35
|
+
? slitherDetectors.length > 0
|
|
36
|
+
? 'with-findings'
|
|
37
|
+
: 'clean'
|
|
38
|
+
: 'unavailable') {
|
|
15
39
|
// Build Slither enrichment maps if available
|
|
16
40
|
const slitherUnchecked = new Set();
|
|
17
41
|
const slitherReentrancy = new Set();
|
|
@@ -41,9 +65,29 @@ export function buildExternalCalls(astCalls, stateVarsImmutability, scopeContrac
|
|
|
41
65
|
const trustLevel = classifyTrustLevel(ac.target, stateVarsImmutability, scopeContracts, accessControlledSetters);
|
|
42
66
|
// Slither enrichment
|
|
43
67
|
const lineKey = `${ac.file}:${ac.lineStart}`;
|
|
44
|
-
const
|
|
68
|
+
const ran = slitherState !== 'unavailable';
|
|
45
69
|
const isUnchecked = slitherUnchecked.has(lineKey);
|
|
46
70
|
const isReentrancy = slitherReentrancy.has(lineKey);
|
|
71
|
+
// Confidence: a detector that actually fired on this call is the strongest
|
|
72
|
+
// signal; a clean Slither run that simply didn't flag it is medium; no
|
|
73
|
+
// Slither run at all is a low-confidence heuristic default.
|
|
74
|
+
const returnCheckedConfidence = isUnchecked
|
|
75
|
+
? 'high'
|
|
76
|
+
: slitherState === 'with-findings'
|
|
77
|
+
? 'high'
|
|
78
|
+
: slitherState === 'clean'
|
|
79
|
+
? 'medium'
|
|
80
|
+
: 'low';
|
|
81
|
+
const reentrancyConfidence = isReentrancy
|
|
82
|
+
? 'high'
|
|
83
|
+
: slitherState === 'with-findings'
|
|
84
|
+
? 'high'
|
|
85
|
+
: slitherState === 'clean'
|
|
86
|
+
? 'medium'
|
|
87
|
+
: 'low';
|
|
88
|
+
const ranReasoning = 'Slither ran without flagging this call';
|
|
89
|
+
const unavailableReturnReasoning = 'Slither unavailable — assuming checked (heuristic default)';
|
|
90
|
+
const unavailableReentrancyReasoning = 'Slither unavailable — reentrancy guard status unknown (heuristic default)';
|
|
47
91
|
calls.push({
|
|
48
92
|
contract: ac.contract,
|
|
49
93
|
function: ac.function,
|
|
@@ -56,16 +100,16 @@ export function buildExternalCalls(astCalls, stateVarsImmutability, scopeContrac
|
|
|
56
100
|
target: ac.target,
|
|
57
101
|
method: ac.method,
|
|
58
102
|
return_checked: {
|
|
59
|
-
value:
|
|
60
|
-
confidence:
|
|
61
|
-
derived_from:
|
|
62
|
-
...(
|
|
103
|
+
value: ran ? !isUnchecked : true,
|
|
104
|
+
confidence: returnCheckedConfidence,
|
|
105
|
+
derived_from: isUnchecked ? 'slither' : ran ? 'slither' : 'heuristic',
|
|
106
|
+
...(isUnchecked ? {} : { reasoning: ran ? ranReasoning : unavailableReturnReasoning }),
|
|
63
107
|
},
|
|
64
108
|
inside_reentrancy_guard: {
|
|
65
|
-
value:
|
|
66
|
-
confidence:
|
|
67
|
-
derived_from:
|
|
68
|
-
...(
|
|
109
|
+
value: ran ? !isReentrancy : false,
|
|
110
|
+
confidence: reentrancyConfidence,
|
|
111
|
+
derived_from: isReentrancy ? 'slither' : ran ? 'slither' : 'heuristic',
|
|
112
|
+
...(isReentrancy ? {} : { reasoning: ran ? ranReasoning : unavailableReentrancyReasoning }),
|
|
69
113
|
},
|
|
70
114
|
call_type: callType,
|
|
71
115
|
trust_level: trustLevel,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"external-calls.js","sourceRoot":"","sources":["../../src/analysis/external-calls.ts"],"names":[],"mappings":"AAIA,sCAAsC;AACtC,MAAM,kBAAkB,GAA6B;IACnD,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"external-calls.js","sourceRoot":"","sources":["../../src/analysis/external-calls.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calls.d.ts","sourceRoot":"","sources":["../../src/commands/calls.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"calls.d.ts","sourceRoot":"","sources":["../../src/commands/calls.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAcpC,eAAO,MAAM,YAAY,SA+GrB,CAAC"}
|
package/dist/commands/calls.js
CHANGED
|
@@ -30,19 +30,28 @@ export const callsCommand = new Command('calls')
|
|
|
30
30
|
allAstCalls.push(...calls);
|
|
31
31
|
}
|
|
32
32
|
logger.info(`AST extraction found ${allAstCalls.length} external calls`);
|
|
33
|
-
// Tier 2: Try Slither for enrichment (unchecked returns, reentrancy)
|
|
34
|
-
|
|
33
|
+
// Tier 2: Try Slither for enrichment (unchecked returns, reentrancy).
|
|
34
|
+
// Track three states explicitly so the analyzer can distinguish "Slither
|
|
35
|
+
// ran clean" (medium confidence) from "Slither never ran" (low confidence).
|
|
36
|
+
let slitherDetectors = [];
|
|
37
|
+
let slitherState = 'unavailable';
|
|
35
38
|
try {
|
|
36
39
|
spin.text = 'Running Slither detectors for enrichment...';
|
|
37
40
|
const result = await runSlither(config.project.project_dir, [
|
|
38
41
|
'.',
|
|
39
42
|
'--detect',
|
|
40
43
|
'unchecked-transfer,unchecked-lowlevel,reentrancy-eth,reentrancy-no-eth,reentrancy-benign,reentrancy-events,calls-loop',
|
|
41
|
-
'--json',
|
|
44
|
+
'--json',
|
|
45
|
+
'-',
|
|
42
46
|
]);
|
|
43
|
-
|
|
47
|
+
// Slither exits non-zero when it finds issues, so gate on parseable JSON
|
|
48
|
+
// output rather than exit code.
|
|
49
|
+
if (result.stdout) {
|
|
44
50
|
slitherDetectors = parseDetectors(JSON.parse(result.stdout));
|
|
45
|
-
|
|
51
|
+
slitherState = slitherDetectors.length > 0 ? 'with-findings' : 'clean';
|
|
52
|
+
logger.info(slitherState === 'clean'
|
|
53
|
+
? 'Slither ran clean (no detector findings) — Tier 2 fields use medium-confidence defaults'
|
|
54
|
+
: 'Slither enrichment available — enhanced return check and reentrancy data');
|
|
46
55
|
}
|
|
47
56
|
}
|
|
48
57
|
catch {
|
|
@@ -76,7 +85,7 @@ export const callsCommand = new Command('calls')
|
|
|
76
85
|
}
|
|
77
86
|
}
|
|
78
87
|
}
|
|
79
|
-
const calls = buildExternalCalls(allAstCalls, immutabilityMap, scopeContracts, accessControlledSetters, slitherDetectors);
|
|
88
|
+
const calls = buildExternalCalls(allAstCalls, immutabilityMap, scopeContracts, accessControlledSetters, slitherDetectors, slitherState);
|
|
80
89
|
const externalCalls = { calls };
|
|
81
90
|
const outPath = writeJsonOutput(outputDir, 'external-calls.json', externalCalls);
|
|
82
91
|
spin.succeed('External call analysis complete');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calls.js","sourceRoot":"","sources":["../../src/commands/calls.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"calls.js","sourceRoot":"","sources":["../../src/commands/calls.ts"],"names":[],"mappings":"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"}
|
|
@@ -34,15 +34,15 @@ export const dashboardCommand = new Command('dashboard')
|
|
|
34
34
|
}
|
|
35
35
|
// Use the next binary that ships with this package.
|
|
36
36
|
const nextBin = resolveNextBin(dashboardDir);
|
|
37
|
+
// Set cwd to the audit project so the dashboard's data loaders find .hex/
|
|
38
|
+
// via process.cwd(). This replaces the old HEX_PROJECT_DIR env-var bridge,
|
|
39
|
+
// which was a vestige from the monorepo days when the dashboard was started
|
|
40
|
+
// from a different working directory.
|
|
37
41
|
const child = spawn(nextBin, [mode, dashboardDir, '--port', port], {
|
|
38
42
|
stdio: 'inherit',
|
|
39
43
|
shell: true,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
HEX_PROJECT_DIR: projectDir,
|
|
43
|
-
SOLAUDIT_PROJECT_DIR: projectDir,
|
|
44
|
-
PORT: port,
|
|
45
|
-
},
|
|
44
|
+
cwd: projectDir,
|
|
45
|
+
env: { ...process.env, PORT: port },
|
|
46
46
|
});
|
|
47
47
|
if (opts.open !== false) {
|
|
48
48
|
setTimeout(() => {
|