openhacker 0.1.0 → 0.1.1
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/package.json +1 -1
- package/src/cli.js +151 -28
- package/templates/agent/.env.example +0 -7
- package/templates/agent/README.md +1 -2
- package/templates/agent/agent/agent.ts +1 -5
- package/templates/agent/agent/channels/eve.ts +7 -0
- package/templates/agent/agent/instructions.md +7 -45
- package/templates/agent/app/globals.css +65 -197
- package/templates/agent/app/layout.tsx +2 -22
- package/templates/agent/app/page.tsx +80 -102
- package/templates/agent/package.json +2 -3
- package/templates/agent/agent/lib/auth.ts +0 -23
- package/templates/agent/agent/lib/github.ts +0 -74
- package/templates/agent/agent/lib/osv.ts +0 -152
- package/templates/agent/agent/lib/scan.ts +0 -153
- package/templates/agent/agent/lib/store.ts +0 -151
- package/templates/agent/agent/lib/types.ts +0 -63
- package/templates/agent/agent/schedules/daily_audit.ts +0 -20
- package/templates/agent/agent/tools/check_advisories.ts +0 -27
- package/templates/agent/agent/tools/list_targets.ts +0 -21
- package/templates/agent/agent/tools/read_repo_file.ts +0 -31
- package/templates/agent/agent/tools/report_finding.ts +0 -59
- package/templates/agent/agent/tools/run_dependency_scan.ts +0 -16
- package/templates/agent/app/_components/ui.tsx +0 -29
- package/templates/agent/app/actions.ts +0 -120
- package/templates/agent/app/api/scan/route.ts +0 -34
- package/templates/agent/app/login/page.tsx +0 -40
- package/templates/agent/app/settings/page.tsx +0 -92
- package/templates/agent/app/targets/[id]/page.tsx +0 -127
- package/templates/agent/proxy.ts +0 -21
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
import { getStore } from "@/agent/lib/store";
|
|
2
|
-
import { saveSettings } from "../actions";
|
|
3
|
-
|
|
4
|
-
export const dynamic = "force-dynamic";
|
|
5
|
-
|
|
6
|
-
const MODELS = [
|
|
7
|
-
"anthropic/claude-sonnet-4.6",
|
|
8
|
-
"anthropic/claude-opus-4.8",
|
|
9
|
-
"openai/gpt-5.5",
|
|
10
|
-
"google/gemini-2.5-pro",
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
export default async function SettingsPage() {
|
|
14
|
-
const settings = await getStore().getSettings();
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<main className="container">
|
|
18
|
-
<h1>Settings</h1>
|
|
19
|
-
<p className="sub">Model, integrations, and remediation policy for this instance.</p>
|
|
20
|
-
|
|
21
|
-
<form action={saveSettings}>
|
|
22
|
-
<div className="panel">
|
|
23
|
-
<h2 style={{ marginTop: 0 }}>Model</h2>
|
|
24
|
-
<label htmlFor="model">Gateway model (routed via Vercel AI Gateway)</label>
|
|
25
|
-
<select id="model" name="model" defaultValue={settings.model}>
|
|
26
|
-
{MODELS.map((m) => (
|
|
27
|
-
<option key={m} value={m}>
|
|
28
|
-
{m}
|
|
29
|
-
</option>
|
|
30
|
-
))}
|
|
31
|
-
</select>
|
|
32
|
-
<p className="mono-sm" style={{ marginTop: 8 }}>
|
|
33
|
-
Note: the eve agent reads its model from the <code>OPENHACKER_MODEL</code> env var at
|
|
34
|
-
boot. Set that in Vercel to change the deep-analysis model (a redeploy applies it).
|
|
35
|
-
</p>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<div className="panel">
|
|
39
|
-
<h2 style={{ marginTop: 0 }}>Remediation</h2>
|
|
40
|
-
<div className="check">
|
|
41
|
-
<input
|
|
42
|
-
id="autoRemediate"
|
|
43
|
-
name="autoRemediate"
|
|
44
|
-
type="checkbox"
|
|
45
|
-
defaultChecked={settings.autoRemediate}
|
|
46
|
-
/>
|
|
47
|
-
<label htmlFor="autoRemediate">
|
|
48
|
-
Open remediation PRs automatically for proven findings (never auto-merges)
|
|
49
|
-
</label>
|
|
50
|
-
</div>
|
|
51
|
-
</div>
|
|
52
|
-
|
|
53
|
-
<div className="panel">
|
|
54
|
-
<h2 style={{ marginTop: 0 }}>Integrations</h2>
|
|
55
|
-
<div className="check" style={{ marginBottom: 12 }}>
|
|
56
|
-
<input
|
|
57
|
-
id="githubConnected"
|
|
58
|
-
name="githubConnected"
|
|
59
|
-
type="checkbox"
|
|
60
|
-
defaultChecked={settings.integrations.github.connected}
|
|
61
|
-
/>
|
|
62
|
-
<label htmlFor="githubConnected">GitHub connected (for remediation PRs)</label>
|
|
63
|
-
</div>
|
|
64
|
-
<div className="check" style={{ marginBottom: 12 }}>
|
|
65
|
-
<input
|
|
66
|
-
id="hackeroneConnected"
|
|
67
|
-
name="hackeroneConnected"
|
|
68
|
-
type="checkbox"
|
|
69
|
-
defaultChecked={settings.integrations.hackerone.connected}
|
|
70
|
-
/>
|
|
71
|
-
<label htmlFor="hackeroneConnected">
|
|
72
|
-
HackerOne connected (validate inbound reports)
|
|
73
|
-
</label>
|
|
74
|
-
</div>
|
|
75
|
-
<label htmlFor="hackeroneHandle">HackerOne program handle</label>
|
|
76
|
-
<input
|
|
77
|
-
id="hackeroneHandle"
|
|
78
|
-
name="hackeroneHandle"
|
|
79
|
-
type="text"
|
|
80
|
-
placeholder="acme"
|
|
81
|
-
defaultValue={settings.integrations.hackerone.handle ?? ""}
|
|
82
|
-
/>
|
|
83
|
-
<p className="mono-sm" style={{ marginTop: 8 }}>
|
|
84
|
-
Integration wiring is stubbed in this build; toggles persist the intended config.
|
|
85
|
-
</p>
|
|
86
|
-
</div>
|
|
87
|
-
|
|
88
|
-
<button type="submit">Save settings</button>
|
|
89
|
-
</form>
|
|
90
|
-
</main>
|
|
91
|
-
);
|
|
92
|
-
}
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import Link from "next/link";
|
|
2
|
-
import { notFound } from "next/navigation";
|
|
3
|
-
import { getStore } from "@/agent/lib/store";
|
|
4
|
-
import type { Severity } from "@/agent/lib/types";
|
|
5
|
-
import { SeverityBadge } from "../../_components/ui";
|
|
6
|
-
import { deleteTarget, scanTarget, setFindingStatus } from "../../actions";
|
|
7
|
-
|
|
8
|
-
export const dynamic = "force-dynamic";
|
|
9
|
-
|
|
10
|
-
const SEV_RANK: Record<Severity, number> = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
11
|
-
const STATUSES = ["open", "triaged", "fixed", "ignored", "false_positive"] as const;
|
|
12
|
-
|
|
13
|
-
export default async function TargetPage({ params }: { params: Promise<{ id: string }> }) {
|
|
14
|
-
const { id } = await params;
|
|
15
|
-
const store = getStore();
|
|
16
|
-
const target = await store.getTarget(id);
|
|
17
|
-
if (!target) notFound();
|
|
18
|
-
|
|
19
|
-
const findings = (await store.listFindings(id)).sort(
|
|
20
|
-
(a, b) => SEV_RANK[a.severity] - SEV_RANK[b.severity] || a.title.localeCompare(b.title),
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<main className="container">
|
|
25
|
-
<p className="mono-sm">
|
|
26
|
-
<Link href="/">← Targets</Link>
|
|
27
|
-
</p>
|
|
28
|
-
<div style={{ display: "flex", alignItems: "center", gap: 16 }}>
|
|
29
|
-
<div className="grow" style={{ flex: 1 }}>
|
|
30
|
-
<h1>{target.name}</h1>
|
|
31
|
-
<p className="sub">
|
|
32
|
-
{target.repo}@{target.branch}
|
|
33
|
-
{target.autoRemediate ? " · auto-remediate on" : ""}
|
|
34
|
-
{target.lastScanAt ? ` · last scan ${new Date(target.lastScanAt).toLocaleString()}` : ""}
|
|
35
|
-
</p>
|
|
36
|
-
</div>
|
|
37
|
-
<div className="actions">
|
|
38
|
-
<form action={scanTarget} className="inline">
|
|
39
|
-
<input type="hidden" name="id" value={target.id} />
|
|
40
|
-
<button type="submit">Scan now</button>
|
|
41
|
-
</form>
|
|
42
|
-
<form action={deleteTarget} className="inline">
|
|
43
|
-
<input type="hidden" name="id" value={target.id} />
|
|
44
|
-
<button type="submit" className="btn-danger">
|
|
45
|
-
Delete
|
|
46
|
-
</button>
|
|
47
|
-
</form>
|
|
48
|
-
</div>
|
|
49
|
-
</div>
|
|
50
|
-
|
|
51
|
-
{target.lastScanStatus === "error" ? (
|
|
52
|
-
<div className="banner">Last scan failed: {target.lastScanError}</div>
|
|
53
|
-
) : null}
|
|
54
|
-
|
|
55
|
-
<h2>Findings ({findings.length})</h2>
|
|
56
|
-
{findings.length === 0 ? (
|
|
57
|
-
<div className="empty">
|
|
58
|
-
No findings recorded yet. Run a scan to check this repository's dependencies.
|
|
59
|
-
</div>
|
|
60
|
-
) : (
|
|
61
|
-
<table>
|
|
62
|
-
<thead>
|
|
63
|
-
<tr>
|
|
64
|
-
<th>Severity</th>
|
|
65
|
-
<th>Finding</th>
|
|
66
|
-
<th>Advisory</th>
|
|
67
|
-
<th>Remediation</th>
|
|
68
|
-
<th>Status</th>
|
|
69
|
-
</tr>
|
|
70
|
-
</thead>
|
|
71
|
-
<tbody>
|
|
72
|
-
{findings.map((f) => (
|
|
73
|
-
<tr key={f.id}>
|
|
74
|
-
<td>
|
|
75
|
-
<SeverityBadge severity={f.severity} />
|
|
76
|
-
</td>
|
|
77
|
-
<td>
|
|
78
|
-
<div>{f.title}</div>
|
|
79
|
-
{f.proof.evidence ? <div className="mono-sm">{f.proof.evidence}</div> : null}
|
|
80
|
-
<div className="mono-sm">
|
|
81
|
-
{f.category}
|
|
82
|
-
{f.location ? ` · ${f.location.file}` : ""} · proof: {f.proof.status}
|
|
83
|
-
</div>
|
|
84
|
-
</td>
|
|
85
|
-
<td className="mono-sm">
|
|
86
|
-
{(f.advisoryIds ?? []).map((a, i) => (
|
|
87
|
-
<div key={a}>
|
|
88
|
-
{f.references?.[i] ? (
|
|
89
|
-
<a href={f.references[i]} target="_blank" rel="noreferrer">
|
|
90
|
-
{a}
|
|
91
|
-
</a>
|
|
92
|
-
) : (
|
|
93
|
-
a
|
|
94
|
-
)}
|
|
95
|
-
</div>
|
|
96
|
-
))}
|
|
97
|
-
</td>
|
|
98
|
-
<td className="mono-sm">
|
|
99
|
-
{f.remediation?.summary}
|
|
100
|
-
{f.remediation?.fixedVersion ? (
|
|
101
|
-
<div>→ {f.remediation.fixedVersion}</div>
|
|
102
|
-
) : null}
|
|
103
|
-
</td>
|
|
104
|
-
<td>
|
|
105
|
-
<form action={setFindingStatus} className="actions">
|
|
106
|
-
<input type="hidden" name="targetId" value={target.id} />
|
|
107
|
-
<input type="hidden" name="findingId" value={f.id} />
|
|
108
|
-
<select name="status" defaultValue={f.status}>
|
|
109
|
-
{STATUSES.map((s) => (
|
|
110
|
-
<option key={s} value={s}>
|
|
111
|
-
{s.replace("_", " ")}
|
|
112
|
-
</option>
|
|
113
|
-
))}
|
|
114
|
-
</select>
|
|
115
|
-
<button type="submit" className="btn-ghost">
|
|
116
|
-
Set
|
|
117
|
-
</button>
|
|
118
|
-
</form>
|
|
119
|
-
</td>
|
|
120
|
-
</tr>
|
|
121
|
-
))}
|
|
122
|
-
</tbody>
|
|
123
|
-
</table>
|
|
124
|
-
)}
|
|
125
|
-
</main>
|
|
126
|
-
);
|
|
127
|
-
}
|
package/templates/agent/proxy.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { NextResponse, type NextRequest } from "next/server";
|
|
2
|
-
import { SESSION_COOKIE, authEnabled, expectedToken } from "./agent/lib/auth";
|
|
3
|
-
|
|
4
|
-
export async function proxy(req: NextRequest) {
|
|
5
|
-
if (!authEnabled()) return NextResponse.next();
|
|
6
|
-
|
|
7
|
-
const token = req.cookies.get(SESSION_COOKIE)?.value;
|
|
8
|
-
const expected = await expectedToken();
|
|
9
|
-
if (token && token === expected) return NextResponse.next();
|
|
10
|
-
|
|
11
|
-
const url = req.nextUrl.clone();
|
|
12
|
-
url.pathname = "/login";
|
|
13
|
-
url.searchParams.set("next", req.nextUrl.pathname);
|
|
14
|
-
return NextResponse.redirect(url);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Protect the dashboard. Excludes Next internals, the login page, the eve agent
|
|
18
|
-
// routes (guarded by eve's own auth), and the API (guarded by a bearer token).
|
|
19
|
-
export const config = {
|
|
20
|
-
matcher: ["/((?!_next/static|_next/image|favicon.ico|login|eve|api).*)"],
|
|
21
|
-
};
|