safecrab 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +220 -0
  3. package/dist/cli/commands/scan.d.ts +5 -0
  4. package/dist/cli/commands/scan.d.ts.map +1 -0
  5. package/dist/cli/commands/scan.js +47 -0
  6. package/dist/cli/commands/scan.js.map +1 -0
  7. package/dist/cli/index.d.ts +6 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli/index.js +23 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/engine/context.d.ts +6 -0
  12. package/dist/engine/context.d.ts.map +1 -0
  13. package/dist/engine/context.js +33 -0
  14. package/dist/engine/context.js.map +1 -0
  15. package/dist/engine/exposure.d.ts +22 -0
  16. package/dist/engine/exposure.d.ts.map +1 -0
  17. package/dist/engine/exposure.js +100 -0
  18. package/dist/engine/exposure.js.map +1 -0
  19. package/dist/engine/heuristics.d.ts +11 -0
  20. package/dist/engine/heuristics.d.ts.map +1 -0
  21. package/dist/engine/heuristics.js +219 -0
  22. package/dist/engine/heuristics.js.map +1 -0
  23. package/dist/engine/types.d.ts +46 -0
  24. package/dist/engine/types.d.ts.map +1 -0
  25. package/dist/engine/types.js +5 -0
  26. package/dist/engine/types.js.map +1 -0
  27. package/dist/index.d.ts +11 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +10 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/system/collectors/cloudflare.d.ts +8 -0
  32. package/dist/system/collectors/cloudflare.d.ts.map +1 -0
  33. package/dist/system/collectors/cloudflare.js +32 -0
  34. package/dist/system/collectors/cloudflare.js.map +1 -0
  35. package/dist/system/collectors/firewall.d.ts +10 -0
  36. package/dist/system/collectors/firewall.d.ts.map +1 -0
  37. package/dist/system/collectors/firewall.js +69 -0
  38. package/dist/system/collectors/firewall.js.map +1 -0
  39. package/dist/system/collectors/network.d.ts +10 -0
  40. package/dist/system/collectors/network.d.ts.map +1 -0
  41. package/dist/system/collectors/network.js +18 -0
  42. package/dist/system/collectors/network.js.map +1 -0
  43. package/dist/system/collectors/services.d.ts +6 -0
  44. package/dist/system/collectors/services.d.ts.map +1 -0
  45. package/dist/system/collectors/services.js +31 -0
  46. package/dist/system/collectors/services.js.map +1 -0
  47. package/dist/system/collectors/tailscale.d.ts +10 -0
  48. package/dist/system/collectors/tailscale.d.ts.map +1 -0
  49. package/dist/system/collectors/tailscale.js +39 -0
  50. package/dist/system/collectors/tailscale.js.map +1 -0
  51. package/dist/system/parsers/ip-parser.d.ts +25 -0
  52. package/dist/system/parsers/ip-parser.d.ts.map +1 -0
  53. package/dist/system/parsers/ip-parser.js +100 -0
  54. package/dist/system/parsers/ip-parser.js.map +1 -0
  55. package/dist/system/parsers/ss-parser.d.ts +15 -0
  56. package/dist/system/parsers/ss-parser.d.ts.map +1 -0
  57. package/dist/system/parsers/ss-parser.js +153 -0
  58. package/dist/system/parsers/ss-parser.js.map +1 -0
  59. package/dist/system/shell.d.ts +27 -0
  60. package/dist/system/shell.d.ts.map +1 -0
  61. package/dist/system/shell.js +56 -0
  62. package/dist/system/shell.js.map +1 -0
  63. package/dist/ui/findings.d.ts +6 -0
  64. package/dist/ui/findings.d.ts.map +1 -0
  65. package/dist/ui/findings.js +91 -0
  66. package/dist/ui/findings.js.map +1 -0
  67. package/dist/ui/renderer.d.ts +18 -0
  68. package/dist/ui/renderer.d.ts.map +1 -0
  69. package/dist/ui/renderer.js +33 -0
  70. package/dist/ui/renderer.js.map +1 -0
  71. package/dist/ui/summary.d.ts +17 -0
  72. package/dist/ui/summary.d.ts.map +1 -0
  73. package/dist/ui/summary.js +39 -0
  74. package/dist/ui/summary.js.map +1 -0
  75. package/dist/ui/theme.d.ts +39 -0
  76. package/dist/ui/theme.d.ts.map +1 -0
  77. package/dist/ui/theme.js +47 -0
  78. package/dist/ui/theme.js.map +1 -0
  79. package/package.json +54 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Parser for ip addr output
3
+ * Maps IP addresses to network interface names
4
+ */
5
+ /**
6
+ * Parse ip addr output to create a mapping of IP addresses to interface names
7
+ *
8
+ * Example output:
9
+ * 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
10
+ * inet 127.0.0.1/8 scope host lo
11
+ * 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP
12
+ * inet 192.168.1.100/24 brd 192.168.1.255 scope global eth0
13
+ * 3: tailscale0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1280 qdisc fq_codel state UNKNOWN
14
+ * inet 100.64.0.1/32 scope global tailscale0
15
+ */
16
+ export function parseIPAddr(output) {
17
+ const ipToInterfaces = new Map();
18
+ const lines = output.split("\n");
19
+ let currentInterface = null;
20
+ for (const line of lines) {
21
+ const trimmed = line.trim();
22
+ // Interface line: "2: eth0: <BROADCAST,..."
23
+ const ifaceMatch = trimmed.match(/^\d+:\s+([^:]+):/);
24
+ if (ifaceMatch) {
25
+ currentInterface = ifaceMatch[1]?.trim() ?? null;
26
+ continue;
27
+ }
28
+ // IPv4 address line: "inet 192.168.1.100/24 ..."
29
+ const inet4Match = trimmed.match(/^inet\s+([0-9.]+)/);
30
+ if (inet4Match && currentInterface) {
31
+ const ip = inet4Match[1];
32
+ if (ip) {
33
+ const interfaces = ipToInterfaces.get(ip) ?? [];
34
+ interfaces.push(currentInterface);
35
+ ipToInterfaces.set(ip, interfaces);
36
+ }
37
+ continue;
38
+ }
39
+ // IPv6 address line: "inet6 fe80::1/64 ..."
40
+ const inet6Match = trimmed.match(/^inet6\s+([0-9a-f:]+)/);
41
+ if (inet6Match && currentInterface) {
42
+ const ip = inet6Match[1];
43
+ if (ip) {
44
+ const interfaces = ipToInterfaces.get(ip) ?? [];
45
+ interfaces.push(currentInterface);
46
+ ipToInterfaces.set(ip, interfaces);
47
+ }
48
+ }
49
+ }
50
+ return ipToInterfaces;
51
+ }
52
+ /**
53
+ * Get list of all network interfaces from ip addr output
54
+ */
55
+ export function getInterfaces(output) {
56
+ const interfaces = [];
57
+ const ipMap = parseIPAddr(output);
58
+ // Group by interface name
59
+ const interfaceMap = new Map();
60
+ for (const [ip, ifaces] of ipMap.entries()) {
61
+ for (const iface of ifaces) {
62
+ const ips = interfaceMap.get(iface) ?? [];
63
+ ips.push(ip);
64
+ interfaceMap.set(iface, ips);
65
+ }
66
+ }
67
+ for (const [name, ips] of interfaceMap.entries()) {
68
+ interfaces.push({
69
+ name,
70
+ ips,
71
+ isPublic: isPublicInterface(name, ips),
72
+ });
73
+ }
74
+ return interfaces;
75
+ }
76
+ /**
77
+ * Determine if an interface is public-facing
78
+ * Private interfaces: lo, docker*, tailscale*, cloudflare*
79
+ */
80
+ function isPublicInterface(name, ips) {
81
+ // Loopback
82
+ if (name === "lo" || name.startsWith("lo:")) {
83
+ return false;
84
+ }
85
+ // Virtual/tunnel interfaces
86
+ if (name.startsWith("docker") ||
87
+ name.startsWith("tailscale") ||
88
+ name.startsWith("cloudflare") ||
89
+ name.startsWith("veth") ||
90
+ name.startsWith("br-")) {
91
+ return false;
92
+ }
93
+ // Check if all IPs are localhost
94
+ const allLocalhost = ips.every((ip) => ip.startsWith("127.") || ip === "::1");
95
+ if (allLocalhost) {
96
+ return false;
97
+ }
98
+ return true;
99
+ }
100
+ //# sourceMappingURL=ip-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ip-parser.js","sourceRoot":"","sources":["../../../src/system/parsers/ip-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Parser for ss -tulnp output
3
+ * Extracts listening services with port, protocol, process info
4
+ */
5
+ import type { ListeningService } from "../../engine/types.js";
6
+ /**
7
+ * Parse ss -tulnp output into structured ListeningService objects
8
+ *
9
+ * Example ss output:
10
+ * Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
11
+ * tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
12
+ * udp UNCONN 0 0 127.0.0.1:53 0.0.0.0:* users:(("systemd-resolve",pid=567,fd=12))
13
+ */
14
+ export declare function parseSSOutput(output: string, interfaceMap: Map<string, string[]>): ListeningService[];
15
+ //# sourceMappingURL=ss-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ss-parser.d.ts","sourceRoot":"","sources":["../../../src/system/parsers/ss-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAW9D;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,GAClC,gBAAgB,EAAE,CAiBpB"}
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Parser for ss -tulnp output
3
+ * Extracts listening services with port, protocol, process info
4
+ */
5
+ import { z } from "zod";
6
+ const ListeningServiceSchema = z.object({
7
+ port: z.number(),
8
+ protocol: z.enum(["tcp", "udp"]),
9
+ process: z.string(),
10
+ pid: z.number(),
11
+ interfaces: z.array(z.string()),
12
+ boundIp: z.string(),
13
+ });
14
+ /**
15
+ * Parse ss -tulnp output into structured ListeningService objects
16
+ *
17
+ * Example ss output:
18
+ * Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
19
+ * tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=1234,fd=3))
20
+ * udp UNCONN 0 0 127.0.0.1:53 0.0.0.0:* users:(("systemd-resolve",pid=567,fd=12))
21
+ */
22
+ export function parseSSOutput(output, interfaceMap) {
23
+ const services = [];
24
+ const lines = output.split("\n");
25
+ for (const line of lines) {
26
+ // Skip header and empty lines
27
+ if (!line.trim() || line.startsWith("Netid") || line.startsWith("State")) {
28
+ continue;
29
+ }
30
+ const service = parseSSLine(line, interfaceMap);
31
+ if (service) {
32
+ services.push(service);
33
+ }
34
+ }
35
+ return services;
36
+ }
37
+ function parseSSLine(line, interfaceMap) {
38
+ // Split by whitespace
39
+ const parts = line.trim().split(/\s+/);
40
+ if (parts.length < 5) {
41
+ return null;
42
+ }
43
+ // Extract protocol (tcp or udp)
44
+ const protocol = parts[0]?.toLowerCase();
45
+ if (protocol !== "tcp" && protocol !== "udp") {
46
+ return null;
47
+ }
48
+ // Extract local address:port (usually index 4 for ss -tulnp)
49
+ // Format: "0.0.0.0:22" or "[::]:80" or "127.0.0.1:8080"
50
+ let localAddrPart = parts[4];
51
+ // Handle different formats in ss output
52
+ if (!localAddrPart?.includes(":")) {
53
+ // Try to find the Local Address:Port part
54
+ for (let i = 3; i < parts.length; i++) {
55
+ if (parts[i]?.includes(":")) {
56
+ localAddrPart = parts[i];
57
+ break;
58
+ }
59
+ }
60
+ }
61
+ if (!localAddrPart) {
62
+ return null;
63
+ }
64
+ const { ip, port } = parseAddress(localAddrPart);
65
+ if (port === null) {
66
+ return null;
67
+ }
68
+ // Extract process info from the Process column
69
+ // Format: users:(("sshd",pid=1234,fd=3))
70
+ const processMatch = line.match(/users:\(\("([^"]+)",pid=(\d+)/);
71
+ const processName = processMatch?.[1] ?? "unknown";
72
+ const pid = processMatch?.[2] ? Number.parseInt(processMatch[2], 10) : 0;
73
+ // Map IP to interfaces
74
+ const interfaces = mapIpToInterfaces(ip, interfaceMap);
75
+ const service = {
76
+ port,
77
+ protocol: protocol,
78
+ process: processName,
79
+ pid,
80
+ interfaces,
81
+ boundIp: ip,
82
+ };
83
+ // Validate with zod
84
+ const result = ListeningServiceSchema.safeParse(service);
85
+ if (!result.success) {
86
+ return null;
87
+ }
88
+ return service;
89
+ }
90
+ /**
91
+ * Parse address:port string
92
+ * Handles IPv4, IPv6, and wildcard addresses
93
+ */
94
+ function parseAddress(addr) {
95
+ // IPv6 format: [::]:80 or [::1]:8080
96
+ const ipv6Match = addr.match(/^\[([^\]]+)\]:(\d+)$/);
97
+ if (ipv6Match) {
98
+ const port = Number.parseInt(ipv6Match[2] ?? "", 10);
99
+ return {
100
+ ip: ipv6Match[1] ?? "::",
101
+ port: Number.isNaN(port) ? null : port,
102
+ };
103
+ }
104
+ // IPv4 format: 0.0.0.0:22 or 127.0.0.1:8080
105
+ const ipv4Match = addr.match(/^([^:]+):(\d+)$/);
106
+ if (ipv4Match) {
107
+ const port = Number.parseInt(ipv4Match[2] ?? "", 10);
108
+ return {
109
+ ip: ipv4Match[1] ?? "0.0.0.0",
110
+ port: Number.isNaN(port) ? null : port,
111
+ };
112
+ }
113
+ // Handle asterisk wildcard (some versions of ss)
114
+ if (addr.startsWith("*:")) {
115
+ const portStr = addr.slice(2);
116
+ const port = Number.parseInt(portStr, 10);
117
+ return {
118
+ ip: "0.0.0.0",
119
+ port: Number.isNaN(port) ? null : port,
120
+ };
121
+ }
122
+ return { ip: "0.0.0.0", port: null };
123
+ }
124
+ /**
125
+ * Map bound IP to network interface names
126
+ *
127
+ * Rules:
128
+ * - 0.0.0.0 or :: → all public interfaces (eth0, ens3, etc.) + lo
129
+ * - 127.0.0.1 or ::1 → localhost only
130
+ * - Specific IP → lookup in interface map
131
+ */
132
+ function mapIpToInterfaces(ip, interfaceMap) {
133
+ // Localhost
134
+ if (ip === "127.0.0.1" || ip === "::1" || ip === "localhost") {
135
+ return ["lo"];
136
+ }
137
+ // Wildcard - bind to all interfaces
138
+ if (ip === "0.0.0.0" || ip === "::" || ip === "*") {
139
+ const allInterfaces = [];
140
+ for (const ifaces of interfaceMap.values()) {
141
+ allInterfaces.push(...ifaces);
142
+ }
143
+ return allInterfaces.length > 0 ? allInterfaces : ["lo"];
144
+ }
145
+ // Specific IP - look up which interface(s) have this IP
146
+ const interfaces = interfaceMap.get(ip);
147
+ if (interfaces && interfaces.length > 0) {
148
+ return interfaces;
149
+ }
150
+ // Unknown - assume localhost for safety
151
+ return ["lo"];
152
+ }
153
+ //# sourceMappingURL=ss-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ss-parser.js","sourceRoot":"","sources":["../../../src/system/parsers/ss-parser.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Centralized shell execution wrapper
3
+ * This is the ONLY place raw shell calls happen
4
+ */
5
+ export interface CommandResult {
6
+ stdout: string;
7
+ stderr: string;
8
+ exitCode: number;
9
+ success: boolean;
10
+ }
11
+ export interface ShellOptions {
12
+ timeout?: number;
13
+ ignoreErrors?: boolean;
14
+ }
15
+ /**
16
+ * Execute a shell command with error handling
17
+ */
18
+ export declare function execCommand(command: string, args?: string[], options?: ShellOptions): Promise<CommandResult>;
19
+ /**
20
+ * Check if running with root privileges
21
+ */
22
+ export declare function isRoot(): boolean;
23
+ /**
24
+ * Check if a command exists in PATH
25
+ */
26
+ export declare function commandExists(command: string): Promise<boolean>;
27
+ //# sourceMappingURL=shell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/system/shell.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,MAAM,EAAO,EACnB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED;;GAEG;AACH,wBAAgB,MAAM,IAAI,OAAO,CAOhC;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGrE"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Centralized shell execution wrapper
3
+ * This is the ONLY place raw shell calls happen
4
+ */
5
+ import { execa } from "execa";
6
+ /**
7
+ * Execute a shell command with error handling
8
+ */
9
+ export async function execCommand(command, args = [], options = {}) {
10
+ const timeout = options.timeout ?? 5000;
11
+ const ignoreErrors = options.ignoreErrors ?? false;
12
+ try {
13
+ const result = await execa(command, args, {
14
+ timeout,
15
+ reject: false, // Don't throw on non-zero exit codes
16
+ shell: false,
17
+ });
18
+ return {
19
+ stdout: result.stdout,
20
+ stderr: result.stderr,
21
+ exitCode: result.exitCode ?? 0,
22
+ success: result.exitCode === 0,
23
+ };
24
+ }
25
+ catch (error) {
26
+ if (ignoreErrors) {
27
+ return {
28
+ stdout: "",
29
+ stderr: error instanceof Error ? error.message : String(error),
30
+ exitCode: 1,
31
+ success: false,
32
+ };
33
+ }
34
+ throw error;
35
+ }
36
+ }
37
+ /**
38
+ * Check if running with root privileges
39
+ */
40
+ export function isRoot() {
41
+ try {
42
+ return process.getuid?.() === 0;
43
+ }
44
+ catch {
45
+ // getuid not available (e.g., on Windows)
46
+ return false;
47
+ }
48
+ }
49
+ /**
50
+ * Check if a command exists in PATH
51
+ */
52
+ export async function commandExists(command) {
53
+ const result = await execCommand("which", [command], { ignoreErrors: true });
54
+ return result.success;
55
+ }
56
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/system/shell.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAc9B;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAAe,EACf,OAAiB,EAAE,EACnB,UAAwB,EAAE;IAE1B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC;IACxC,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,KAAK,CAAC;IAEnD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACxC,OAAO;YACP,MAAM,EAAE,KAAK,EAAE,qCAAqC;YACpD,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;QAEH,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ,IAAI,CAAC;YAC9B,OAAO,EAAE,MAAM,CAAC,QAAQ,KAAK,CAAC;SAC/B,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,YAAY,EAAE,CAAC;YACjB,OAAO;gBACL,MAAM,EAAE,EAAE;gBACV,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;gBAC9D,QAAQ,EAAE,CAAC;gBACX,OAAO,EAAE,KAAK;aACf,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,MAAM;IACpB,IAAI,CAAC;QACH,OAAO,OAAO,CAAC,MAAM,EAAE,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;QAC1C,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAe;IACjD,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7E,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Findings rendering - grouped by severity
3
+ */
4
+ import type { Finding } from "../engine/types.js";
5
+ export declare function renderFindings(findings: Finding[]): string;
6
+ //# sourceMappingURL=findings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../../src/ui/findings.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAGlD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAwC1D"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Findings rendering - grouped by severity
3
+ */
4
+ import { colors, icons, spacing } from "./theme.js";
5
+ export function renderFindings(findings) {
6
+ if (findings.length === 0) {
7
+ return `${spacing.section}${colors.success(`${icons.tick} No security issues detected.`)}`;
8
+ }
9
+ const lines = [];
10
+ // Group by severity
11
+ const critical = findings.filter((f) => f.severity === "critical");
12
+ const warnings = findings.filter((f) => f.severity === "warning");
13
+ const info = findings.filter((f) => f.severity === "info");
14
+ // Render critical findings
15
+ if (critical.length > 0) {
16
+ lines.push(spacing.section);
17
+ lines.push(colors.critical.bold("CRITICAL"));
18
+ for (const finding of critical) {
19
+ lines.push(renderFinding(finding));
20
+ }
21
+ }
22
+ // Render warnings
23
+ if (warnings.length > 0) {
24
+ lines.push(spacing.section);
25
+ lines.push(colors.warning.bold("WARNINGS"));
26
+ for (const finding of warnings) {
27
+ lines.push(renderFinding(finding));
28
+ }
29
+ }
30
+ // Render info
31
+ if (info.length > 0) {
32
+ lines.push(spacing.section);
33
+ lines.push(colors.info.bold("INFO"));
34
+ for (const finding of info) {
35
+ lines.push(renderFinding(finding));
36
+ }
37
+ }
38
+ return lines.join(spacing.line);
39
+ }
40
+ function renderFinding(finding) {
41
+ const lines = [];
42
+ // Icon + title
43
+ const icon = getIcon(finding);
44
+ const colorFn = getColor(finding.severity);
45
+ lines.push(colorFn(`${icon} ${finding.title}`));
46
+ // Description with indentation
47
+ const descLines = finding.description.split("\n");
48
+ for (const line of descLines) {
49
+ lines.push(`${spacing.indent}${line}`);
50
+ }
51
+ // Recommendation if present
52
+ if (finding.recommendation) {
53
+ lines.push("");
54
+ lines.push(colors.dim(`${spacing.indent}Recommendation:`));
55
+ lines.push(`${spacing.doubleIndent}${finding.recommendation}`);
56
+ }
57
+ return lines.join(spacing.line);
58
+ }
59
+ function getIcon(finding) {
60
+ // Use icon override if specified
61
+ if (finding.icon === "warning") {
62
+ return icons.warning;
63
+ }
64
+ if (finding.icon === "tick") {
65
+ return icons.tick;
66
+ }
67
+ // Default icon based on severity
68
+ switch (finding.severity) {
69
+ case "critical":
70
+ return icons.cross;
71
+ case "warning":
72
+ return icons.warning;
73
+ case "info":
74
+ return icons.tick;
75
+ default:
76
+ return icons.info;
77
+ }
78
+ }
79
+ function getColor(severity) {
80
+ switch (severity) {
81
+ case "critical":
82
+ return colors.critical;
83
+ case "warning":
84
+ return colors.warning;
85
+ case "info":
86
+ return colors.info;
87
+ default:
88
+ return (text) => text;
89
+ }
90
+ }
91
+ //# sourceMappingURL=findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.js","sourceRoot":"","sources":["../../src/ui/findings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Main report renderer
3
+ */
4
+ import type { Finding, ListeningService } from "../engine/types.js";
5
+ export interface ScanReport {
6
+ services: ListeningService[];
7
+ findings: Finding[];
8
+ }
9
+ /**
10
+ * Render the complete security scan report
11
+ */
12
+ export declare function renderReport(report: ScanReport): string;
13
+ /**
14
+ * Determine exit code based on findings
15
+ * Returns 1 if critical findings exist, 0 otherwise
16
+ */
17
+ export declare function getExitCode(findings: Finding[]): number;
18
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/ui/renderer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAMpE,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,CAkBvD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAGvD"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Main report renderer
3
+ */
4
+ import { isRoot } from "../system/shell.js";
5
+ import { renderFindings } from "./findings.js";
6
+ import { calculateStats, renderSummary } from "./summary.js";
7
+ import { dim, spacing } from "./theme.js";
8
+ /**
9
+ * Render the complete security scan report
10
+ */
11
+ export function renderReport(report) {
12
+ const { services, findings } = report;
13
+ const root = isRoot();
14
+ const lines = [];
15
+ // Summary section
16
+ const stats = calculateStats(services, findings, root);
17
+ lines.push(renderSummary(stats));
18
+ // Findings sections
19
+ lines.push(renderFindings(findings));
20
+ // Footer - trust-building message
21
+ lines.push(spacing.section);
22
+ lines.push(dim("No changes were made to your system."));
23
+ return lines.join(spacing.line);
24
+ }
25
+ /**
26
+ * Determine exit code based on findings
27
+ * Returns 1 if critical findings exist, 0 otherwise
28
+ */
29
+ export function getExitCode(findings) {
30
+ const hasCritical = findings.some((f) => f.severity === "critical");
31
+ return hasCritical ? 1 : 0;
32
+ }
33
+ //# sourceMappingURL=renderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/ui/renderer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAO1C;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAAkB;IAC7C,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IACtC,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC;IAEtB,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,kBAAkB;IAClB,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;IACvD,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IAEjC,oBAAoB;IACpB,KAAK,CAAC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC,CAAC;IAErC,kCAAkC;IAClC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,CAAC;IAExD,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAmB;IAC7C,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC;IACpE,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Summary section rendering
3
+ */
4
+ import type { Finding, ListeningService } from "../engine/types.js";
5
+ export interface SummaryStats {
6
+ totalServices: number;
7
+ publiclyReachable: number;
8
+ criticalFindings: number;
9
+ warningFindings: number;
10
+ isRoot: boolean;
11
+ }
12
+ export declare function renderSummary(stats: SummaryStats): string;
13
+ /**
14
+ * Calculate summary statistics from services and findings
15
+ */
16
+ export declare function calculateStats(services: ListeningService[], findings: Finding[], isRoot: boolean): SummaryStats;
17
+ //# sourceMappingURL=summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/ui/summary.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAGpE,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CA6BzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,gBAAgB,EAAE,EAC5B,QAAQ,EAAE,OAAO,EAAE,EACnB,MAAM,EAAE,OAAO,GACd,YAAY,CAYd"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Summary section rendering
3
+ */
4
+ import { bullet, colors, header, icons, spacing } from "./theme.js";
5
+ export function renderSummary(stats) {
6
+ const lines = [];
7
+ // Title
8
+ lines.push(`${icons.crab} ${colors.crab.bold("Safecrab Security Scan")}`);
9
+ lines.push("");
10
+ // Root warning if not running as root
11
+ if (!stats.isRoot) {
12
+ lines.push(colors.warning(`${icons.warning} Running without root may hide some services and can show incorrect firewall or process info.`));
13
+ lines.push(colors.dim(" For full visibility, run Safecrab with sudo."));
14
+ lines.push("");
15
+ }
16
+ // Summary statistics
17
+ lines.push(header("Summary:"));
18
+ lines.push(bullet(`${stats.totalServices} services detected`));
19
+ lines.push(bullet(`${stats.publiclyReachable} publicly reachable`));
20
+ lines.push(bullet(`${stats.criticalFindings} critical issues`));
21
+ if (stats.warningFindings > 0) {
22
+ lines.push(bullet(`${stats.warningFindings} warnings`));
23
+ }
24
+ return lines.join(spacing.line);
25
+ }
26
+ /**
27
+ * Calculate summary statistics from services and findings
28
+ */
29
+ export function calculateStats(services, findings, isRoot) {
30
+ const publiclyReachable = new Set(findings.filter((f) => f.service && f.severity !== "info").map((f) => f.service?.port)).size;
31
+ return {
32
+ totalServices: services.length,
33
+ publiclyReachable,
34
+ criticalFindings: findings.filter((f) => f.severity === "critical").length,
35
+ warningFindings: findings.filter((f) => f.severity === "warning").length,
36
+ isRoot,
37
+ };
38
+ }
39
+ //# sourceMappingURL=summary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary.js","sourceRoot":"","sources":["../../src/ui/summary.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAUpE,MAAM,UAAU,aAAa,CAAC,KAAmB;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,QAAQ;IACR,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,wBAAwB,CAAC,EAAE,CAAC,CAAC;IAC1E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,sCAAsC;IACtC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;QAClB,KAAK,CAAC,IAAI,CACR,MAAM,CAAC,OAAO,CACZ,GAAG,KAAK,CAAC,OAAO,+FAA+F,CAChH,CACF,CAAC;QACF,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC,CAAC;QAC1E,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IAED,qBAAqB;IACrB,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;IAC/B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,aAAa,oBAAoB,CAAC,CAAC,CAAC;IAC/D,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,iBAAiB,qBAAqB,CAAC,CAAC,CAAC;IACpE,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,gBAAgB,kBAAkB,CAAC,CAAC,CAAC;IAEhE,IAAI,KAAK,CAAC,eAAe,GAAG,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,eAAe,WAAW,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA4B,EAC5B,QAAmB,EACnB,MAAe;IAEf,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAC/B,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC,CACvF,CAAC,IAAI,CAAC;IAEP,OAAO;QACL,aAAa,EAAE,QAAQ,CAAC,MAAM;QAC9B,iBAAiB;QACjB,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CAAC,MAAM;QAC1E,eAAe,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,MAAM;QACxE,MAAM;KACP,CAAC;AACJ,CAAC"}