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.
- package/LICENSE +21 -0
- package/README.md +220 -0
- package/dist/cli/commands/scan.d.ts +5 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +47 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +23 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/engine/context.d.ts +6 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +33 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/exposure.d.ts +22 -0
- package/dist/engine/exposure.d.ts.map +1 -0
- package/dist/engine/exposure.js +100 -0
- package/dist/engine/exposure.js.map +1 -0
- package/dist/engine/heuristics.d.ts +11 -0
- package/dist/engine/heuristics.d.ts.map +1 -0
- package/dist/engine/heuristics.js +219 -0
- package/dist/engine/heuristics.js.map +1 -0
- package/dist/engine/types.d.ts +46 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +5 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/system/collectors/cloudflare.d.ts +8 -0
- package/dist/system/collectors/cloudflare.d.ts.map +1 -0
- package/dist/system/collectors/cloudflare.js +32 -0
- package/dist/system/collectors/cloudflare.js.map +1 -0
- package/dist/system/collectors/firewall.d.ts +10 -0
- package/dist/system/collectors/firewall.d.ts.map +1 -0
- package/dist/system/collectors/firewall.js +69 -0
- package/dist/system/collectors/firewall.js.map +1 -0
- package/dist/system/collectors/network.d.ts +10 -0
- package/dist/system/collectors/network.d.ts.map +1 -0
- package/dist/system/collectors/network.js +18 -0
- package/dist/system/collectors/network.js.map +1 -0
- package/dist/system/collectors/services.d.ts +6 -0
- package/dist/system/collectors/services.d.ts.map +1 -0
- package/dist/system/collectors/services.js +31 -0
- package/dist/system/collectors/services.js.map +1 -0
- package/dist/system/collectors/tailscale.d.ts +10 -0
- package/dist/system/collectors/tailscale.d.ts.map +1 -0
- package/dist/system/collectors/tailscale.js +39 -0
- package/dist/system/collectors/tailscale.js.map +1 -0
- package/dist/system/parsers/ip-parser.d.ts +25 -0
- package/dist/system/parsers/ip-parser.d.ts.map +1 -0
- package/dist/system/parsers/ip-parser.js +100 -0
- package/dist/system/parsers/ip-parser.js.map +1 -0
- package/dist/system/parsers/ss-parser.d.ts +15 -0
- package/dist/system/parsers/ss-parser.d.ts.map +1 -0
- package/dist/system/parsers/ss-parser.js +153 -0
- package/dist/system/parsers/ss-parser.js.map +1 -0
- package/dist/system/shell.d.ts +27 -0
- package/dist/system/shell.d.ts.map +1 -0
- package/dist/system/shell.js +56 -0
- package/dist/system/shell.js.map +1 -0
- package/dist/ui/findings.d.ts +6 -0
- package/dist/ui/findings.d.ts.map +1 -0
- package/dist/ui/findings.js +91 -0
- package/dist/ui/findings.js.map +1 -0
- package/dist/ui/renderer.d.ts +18 -0
- package/dist/ui/renderer.d.ts.map +1 -0
- package/dist/ui/renderer.js +33 -0
- package/dist/ui/renderer.js.map +1 -0
- package/dist/ui/summary.d.ts +17 -0
- package/dist/ui/summary.d.ts.map +1 -0
- package/dist/ui/summary.js +39 -0
- package/dist/ui/summary.js.map +1 -0
- package/dist/ui/theme.d.ts +39 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +47 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Risk heuristics - composable security rules
|
|
3
|
+
* Each rule returns Finding | null
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Run all heuristics on service exposures
|
|
7
|
+
* Returns findings sorted by severity (critical > warning > info)
|
|
8
|
+
*/
|
|
9
|
+
export function analyzeExposures(exposures, context) {
|
|
10
|
+
const findings = [];
|
|
11
|
+
// Run heuristics in severity order
|
|
12
|
+
for (const exposure of exposures) {
|
|
13
|
+
// Critical rules
|
|
14
|
+
const tunnelBypass = checkTunnelBypass(exposure);
|
|
15
|
+
if (tunnelBypass)
|
|
16
|
+
findings.push(tunnelBypass);
|
|
17
|
+
const aiServiceExposure = checkHighRiskAIService(exposure);
|
|
18
|
+
if (aiServiceExposure)
|
|
19
|
+
findings.push(aiServiceExposure);
|
|
20
|
+
// Warning rules
|
|
21
|
+
const publicExposure = checkPublicExposure(exposure);
|
|
22
|
+
if (publicExposure)
|
|
23
|
+
findings.push(publicExposure);
|
|
24
|
+
const tailscaleUnused = checkTailscaleUnused(exposure, context);
|
|
25
|
+
if (tailscaleUnused)
|
|
26
|
+
findings.push(tailscaleUnused);
|
|
27
|
+
}
|
|
28
|
+
// Add informational findings about context
|
|
29
|
+
const contextFindings = generateContextFindings(context);
|
|
30
|
+
findings.push(...contextFindings);
|
|
31
|
+
// Deduplicate findings (same rule + same port = one finding)
|
|
32
|
+
const deduplicated = deduplicateFindings(findings);
|
|
33
|
+
// Sort by severity
|
|
34
|
+
return sortBySeverity(deduplicated);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Rule 1: Tunnel bypass (CRITICAL)
|
|
38
|
+
* Service has both cloudflare-tunnel AND public-internet exposure
|
|
39
|
+
*/
|
|
40
|
+
function checkTunnelBypass(exposure) {
|
|
41
|
+
const { service, paths } = exposure;
|
|
42
|
+
if (paths.includes("cloudflare-tunnel") && paths.includes("public-internet")) {
|
|
43
|
+
return {
|
|
44
|
+
severity: "critical",
|
|
45
|
+
title: "Tunnel bypass detected",
|
|
46
|
+
description: `Port ${service.port} (${service.process}) is accessible via both Cloudflare Tunnel and directly from the public internet. The tunnel does not protect this service.`,
|
|
47
|
+
recommendation: "Bind the service to localhost (127.0.0.1) or restrict access via firewall to ensure traffic only flows through the tunnel.",
|
|
48
|
+
service,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Rule 2: High-risk AI service exposure (CRITICAL)
|
|
55
|
+
* AI/ML service exposed to public internet
|
|
56
|
+
*/
|
|
57
|
+
function checkHighRiskAIService(exposure) {
|
|
58
|
+
const { service, paths } = exposure;
|
|
59
|
+
if (!paths.includes("public-internet")) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
const highRiskProcesses = ["ollama", "llama", "python", "node", "uvicorn", "fastapi"];
|
|
63
|
+
const isHighRisk = highRiskProcesses.some((risk) => service.process.toLowerCase().includes(risk));
|
|
64
|
+
if (isHighRisk) {
|
|
65
|
+
return {
|
|
66
|
+
severity: "critical",
|
|
67
|
+
title: "High-risk service publicly exposed",
|
|
68
|
+
description: `Port ${service.port} (${service.process}) is reachable from the public internet. This process may be running AI models, APIs, or development servers that should not be publicly accessible.`,
|
|
69
|
+
recommendation: "Bind to localhost, use a VPN (like Tailscale), or configure firewall rules to restrict access.",
|
|
70
|
+
service,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Rule 3: Public exposure (WARNING)
|
|
77
|
+
* Service exposed to public internet (but not high-risk)
|
|
78
|
+
*/
|
|
79
|
+
function checkPublicExposure(exposure) {
|
|
80
|
+
const { service, paths } = exposure;
|
|
81
|
+
if (!paths.includes("public-internet")) {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// Skip if already flagged as critical
|
|
85
|
+
const highRiskProcesses = ["ollama", "llama", "python", "node", "uvicorn", "fastapi"];
|
|
86
|
+
const isHighRisk = highRiskProcesses.some((risk) => service.process.toLowerCase().includes(risk));
|
|
87
|
+
if (isHighRisk) {
|
|
88
|
+
return null; // Already handled by checkHighRiskAIService
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
severity: "warning",
|
|
92
|
+
title: "Service exposed to public internet",
|
|
93
|
+
description: `Port ${service.port} (${service.process}) is accessible from the public internet.`,
|
|
94
|
+
recommendation: "Verify this service should be publicly accessible. If not, bind to localhost or use firewall rules.",
|
|
95
|
+
service,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Rule 4: Tailscale unused (WARNING)
|
|
100
|
+
* Tailscale is connected but service uses public internet instead
|
|
101
|
+
*/
|
|
102
|
+
function checkTailscaleUnused(exposure, context) {
|
|
103
|
+
const { service, paths } = exposure;
|
|
104
|
+
if (!context.tailscale.connected) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
if (!paths.includes("public-internet")) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
if (paths.includes("tailscale")) {
|
|
111
|
+
return null; // Already using Tailscale
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
severity: "warning",
|
|
115
|
+
title: "Tailscale available but not used",
|
|
116
|
+
description: `Port ${service.port} (${service.process}) is publicly accessible, but Tailscale is connected. Consider using Tailscale for secure access instead.`,
|
|
117
|
+
recommendation: `Bind the service to the Tailscale interface or use Tailscale's subnet routing.`,
|
|
118
|
+
service,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Deduplicate findings by (severity, title, port)
|
|
123
|
+
* Prevents duplicate warnings for the same port (e.g. IPv4 + IPv6)
|
|
124
|
+
*/
|
|
125
|
+
function deduplicateFindings(findings) {
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
const deduplicated = [];
|
|
128
|
+
for (const finding of findings) {
|
|
129
|
+
const key = `${finding.severity}:${finding.title}:${finding.service?.port ?? 0}`;
|
|
130
|
+
if (!seen.has(key)) {
|
|
131
|
+
seen.add(key);
|
|
132
|
+
deduplicated.push(finding);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return deduplicated;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Generate informational findings about network context
|
|
139
|
+
*/
|
|
140
|
+
function generateContextFindings(context) {
|
|
141
|
+
const findings = [];
|
|
142
|
+
// Firewall status
|
|
143
|
+
if (!context.firewall.statusKnown) {
|
|
144
|
+
// Could not determine firewall status (likely needs sudo)
|
|
145
|
+
findings.push({
|
|
146
|
+
severity: "warning",
|
|
147
|
+
title: "Firewall status could not be determined",
|
|
148
|
+
description: "Unable to read UFW status. This usually happens when running without root.",
|
|
149
|
+
recommendation: "Run with sudo to see accurate UFW firewall status.",
|
|
150
|
+
icon: "warning",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
else if (context.firewall.enabled) {
|
|
154
|
+
// Firewall is active
|
|
155
|
+
findings.push({
|
|
156
|
+
severity: "info",
|
|
157
|
+
title: "Firewall is enabled",
|
|
158
|
+
description: `UFW firewall is active with default inbound policy: ${context.firewall.defaultInbound}.`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
// Firewall is not active (negative finding)
|
|
163
|
+
findings.push({
|
|
164
|
+
severity: "warning",
|
|
165
|
+
title: "No firewall detected",
|
|
166
|
+
description: "UFW firewall is not active. All ports may be accessible from the internet.",
|
|
167
|
+
recommendation: "Consider enabling UFW to control inbound traffic: sudo ufw enable",
|
|
168
|
+
icon: "warning",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
// Tailscale status
|
|
172
|
+
if (context.tailscale.connected) {
|
|
173
|
+
findings.push({
|
|
174
|
+
severity: "info",
|
|
175
|
+
title: "Tailscale is connected",
|
|
176
|
+
description: "Tailscale VPN is active and available for secure access.",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
else if (context.tailscale.installed) {
|
|
180
|
+
// Tailscale is installed but not connected
|
|
181
|
+
findings.push({
|
|
182
|
+
severity: "warning",
|
|
183
|
+
title: "Tailscale is not connected",
|
|
184
|
+
description: "Tailscale VPN is not active. Secure private access to this machine is unavailable.",
|
|
185
|
+
recommendation: "Start Tailscale for secure access: tailscale up",
|
|
186
|
+
icon: "warning",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// Tailscale is not installed
|
|
191
|
+
findings.push({
|
|
192
|
+
severity: "info",
|
|
193
|
+
title: "Tailscale not installed",
|
|
194
|
+
description: "Tailscale VPN is not installed. Consider installing it for secure private access to this machine.",
|
|
195
|
+
recommendation: "Install from https://tailscale.com or your package manager.",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
// Cloudflare tunnel
|
|
199
|
+
if (context.cloudflare.tunnelDetected) {
|
|
200
|
+
findings.push({
|
|
201
|
+
severity: "info",
|
|
202
|
+
title: "Cloudflare Tunnel detected",
|
|
203
|
+
description: "A Cloudflare Tunnel is present. Ensure services are only accessible through the tunnel.",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return findings;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Sort findings by severity: critical > warning > info
|
|
210
|
+
*/
|
|
211
|
+
function sortBySeverity(findings) {
|
|
212
|
+
const severityOrder = {
|
|
213
|
+
critical: 0,
|
|
214
|
+
warning: 1,
|
|
215
|
+
info: 2,
|
|
216
|
+
};
|
|
217
|
+
return findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=heuristics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"heuristics.js","sourceRoot":"","sources":["../../src/engine/heuristics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core data models for Safecrab security scanner
|
|
3
|
+
*/
|
|
4
|
+
export interface ListeningService {
|
|
5
|
+
port: number;
|
|
6
|
+
protocol: "tcp" | "udp";
|
|
7
|
+
process: string;
|
|
8
|
+
pid: number;
|
|
9
|
+
interfaces: string[];
|
|
10
|
+
boundIp: string;
|
|
11
|
+
}
|
|
12
|
+
export interface NetworkContext {
|
|
13
|
+
firewall: {
|
|
14
|
+
enabled: boolean;
|
|
15
|
+
defaultInbound: "allow" | "deny" | "unknown";
|
|
16
|
+
statusKnown: boolean;
|
|
17
|
+
};
|
|
18
|
+
tailscale: {
|
|
19
|
+
installed: boolean;
|
|
20
|
+
connected: boolean;
|
|
21
|
+
interface?: string;
|
|
22
|
+
};
|
|
23
|
+
cloudflare: {
|
|
24
|
+
tunnelDetected: boolean;
|
|
25
|
+
};
|
|
26
|
+
interfaces: {
|
|
27
|
+
name: string;
|
|
28
|
+
ips: string[];
|
|
29
|
+
isPublic: boolean;
|
|
30
|
+
}[];
|
|
31
|
+
}
|
|
32
|
+
export type ExposurePath = "public-internet" | "tailscale" | "cloudflare-tunnel" | "localhost-only";
|
|
33
|
+
export interface ServiceExposure {
|
|
34
|
+
service: ListeningService;
|
|
35
|
+
paths: ExposurePath[];
|
|
36
|
+
}
|
|
37
|
+
export type FindingSeverity = "info" | "warning" | "critical";
|
|
38
|
+
export interface Finding {
|
|
39
|
+
severity: FindingSeverity;
|
|
40
|
+
title: string;
|
|
41
|
+
description: string;
|
|
42
|
+
recommendation?: string;
|
|
43
|
+
service?: ListeningService;
|
|
44
|
+
icon?: "tick" | "warning";
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,KAAK,GAAG,KAAK,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE;QACR,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;QAC7C,WAAW,EAAE,OAAO,CAAC;KACtB,CAAC;IACF,SAAS,EAAE;QACT,SAAS,EAAE,OAAO,CAAC;QACnB,SAAS,EAAE,OAAO,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,EAAE;QACV,cAAc,EAAE,OAAO,CAAC;KACzB,CAAC;IACF,UAAU,EAAE;QACV,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,EAAE,CAAC;QACd,QAAQ,EAAE,OAAO,CAAC;KACnB,EAAE,CAAC;CACL;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,WAAW,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;AAEpG,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,gBAAgB,CAAC;IAC1B,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,CAAC;AAE9D,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/engine/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safecrab - Security scanner for Linux VPS environments
|
|
3
|
+
* Package entry point for programmatic usage
|
|
4
|
+
*/
|
|
5
|
+
export type { ListeningService, NetworkContext, ExposurePath, ServiceExposure, Finding, FindingSeverity, } from "./engine/types.js";
|
|
6
|
+
export { collectListeningServices } from "./system/collectors/services.js";
|
|
7
|
+
export { buildNetworkContext } from "./engine/context.js";
|
|
8
|
+
export { resolveAllExposures } from "./engine/exposure.js";
|
|
9
|
+
export { analyzeExposures } from "./engine/heuristics.js";
|
|
10
|
+
export { renderReport, getExitCode } from "./ui/renderer.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,OAAO,EACP,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safecrab - Security scanner for Linux VPS environments
|
|
3
|
+
* Package entry point for programmatic usage
|
|
4
|
+
*/
|
|
5
|
+
export { collectListeningServices } from "./system/collectors/services.js";
|
|
6
|
+
export { buildNetworkContext } from "./engine/context.js";
|
|
7
|
+
export { resolveAllExposures } from "./engine/exposure.js";
|
|
8
|
+
export { analyzeExposures } from "./engine/heuristics.js";
|
|
9
|
+
export { renderReport, getExitCode } from "./ui/renderer.js";
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,OAAO,EAAE,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../../src/system/collectors/cloudflare.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CA2BzE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector for Cloudflare Tunnel detection
|
|
3
|
+
*/
|
|
4
|
+
import { constants, access } from "node:fs/promises";
|
|
5
|
+
import { execCommand } from "../shell.js";
|
|
6
|
+
export async function collectCloudflareStatus() {
|
|
7
|
+
try {
|
|
8
|
+
// Method 1: Check for cloudflared process
|
|
9
|
+
const psResult = await execCommand("ps", ["aux"], { ignoreErrors: true });
|
|
10
|
+
const hasProcess = psResult.success && psResult.stdout.includes("cloudflared");
|
|
11
|
+
// Method 2: Check for cloudflared config directory
|
|
12
|
+
let hasConfig = false;
|
|
13
|
+
try {
|
|
14
|
+
await access("/etc/cloudflared", constants.F_OK);
|
|
15
|
+
hasConfig = true;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
hasConfig = false;
|
|
19
|
+
}
|
|
20
|
+
// Tunnel is detected if either condition is true
|
|
21
|
+
const tunnelDetected = hasProcess || hasConfig;
|
|
22
|
+
return {
|
|
23
|
+
tunnelDetected,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
return {
|
|
28
|
+
tunnelDetected: false,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=cloudflare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../../src/system/collectors/cloudflare.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM1C,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,IAAI,CAAC;QACH,0CAA0C;QAC1C,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QAE1E,MAAM,UAAU,GAAG,QAAQ,CAAC,OAAO,IAAI,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAE/E,mDAAmD;QACnD,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,kBAAkB,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;YACjD,SAAS,GAAG,IAAI,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS,GAAG,KAAK,CAAC;QACpB,CAAC;QAED,iDAAiD;QACjD,MAAM,cAAc,GAAG,UAAU,IAAI,SAAS,CAAC;QAE/C,OAAO;YACL,cAAc;SACf,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,cAAc,EAAE,KAAK;SACtB,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector for firewall status (UFW)
|
|
3
|
+
*/
|
|
4
|
+
export interface FirewallStatus {
|
|
5
|
+
enabled: boolean;
|
|
6
|
+
defaultInbound: "allow" | "deny" | "unknown";
|
|
7
|
+
statusKnown: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function collectFirewallStatus(): Promise<FirewallStatus>;
|
|
10
|
+
//# sourceMappingURL=firewall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firewall.d.ts","sourceRoot":"","sources":["../../../src/system/collectors/firewall.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;IAC7C,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,cAAc,CAAC,CAgErE"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector for firewall status (UFW)
|
|
3
|
+
*/
|
|
4
|
+
import { commandExists, execCommand } from "../shell.js";
|
|
5
|
+
export async function collectFirewallStatus() {
|
|
6
|
+
try {
|
|
7
|
+
// Check if UFW is installed
|
|
8
|
+
const ufwExists = await commandExists("ufw");
|
|
9
|
+
if (!ufwExists) {
|
|
10
|
+
// No firewall detected - assume allow all
|
|
11
|
+
return {
|
|
12
|
+
enabled: false,
|
|
13
|
+
defaultInbound: "allow",
|
|
14
|
+
statusKnown: true,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// Get UFW status
|
|
18
|
+
const result = await execCommand("ufw", ["status", "verbose"], {
|
|
19
|
+
ignoreErrors: true,
|
|
20
|
+
});
|
|
21
|
+
if (!result.success) {
|
|
22
|
+
// UFW command failed (likely permission denied without sudo)
|
|
23
|
+
return {
|
|
24
|
+
enabled: false,
|
|
25
|
+
defaultInbound: "unknown",
|
|
26
|
+
statusKnown: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const output = result.stdout.toLowerCase();
|
|
30
|
+
// Check if active
|
|
31
|
+
const isActive = output.includes("status: active") || output.includes("status:active");
|
|
32
|
+
// Parse default policy
|
|
33
|
+
let defaultInbound = "unknown";
|
|
34
|
+
if (output.includes("default: deny (incoming)")) {
|
|
35
|
+
defaultInbound = "deny";
|
|
36
|
+
}
|
|
37
|
+
else if (output.includes("default: allow (incoming)")) {
|
|
38
|
+
defaultInbound = "allow";
|
|
39
|
+
}
|
|
40
|
+
else if (output.includes("default:deny(incoming)")) {
|
|
41
|
+
defaultInbound = "deny";
|
|
42
|
+
}
|
|
43
|
+
else if (output.includes("default:allow(incoming)")) {
|
|
44
|
+
defaultInbound = "allow";
|
|
45
|
+
}
|
|
46
|
+
else if (isActive) {
|
|
47
|
+
// UFW is active but can't parse policy - conservative assumption
|
|
48
|
+
defaultInbound = "deny";
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// UFW inactive - traffic flows freely
|
|
52
|
+
defaultInbound = "allow";
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
enabled: isActive,
|
|
56
|
+
defaultInbound,
|
|
57
|
+
statusKnown: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
// Best-effort: could not determine status
|
|
62
|
+
return {
|
|
63
|
+
enabled: false,
|
|
64
|
+
defaultInbound: "allow",
|
|
65
|
+
statusKnown: false,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=firewall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"firewall.js","sourceRoot":"","sources":["../../../src/system/collectors/firewall.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQzD,MAAM,CAAC,KAAK,UAAU,qBAAqB;IACzC,IAAI,CAAC;QACH,4BAA4B;QAC5B,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,0CAA0C;YAC1C,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,cAAc,EAAE,OAAO;gBACvB,WAAW,EAAE,IAAI;aAClB,CAAC;QACJ,CAAC;QAED,iBAAiB;QACjB,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE;YAC7D,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,6DAA6D;YAC7D,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,cAAc,EAAE,SAAS;gBACzB,WAAW,EAAE,KAAK;aACnB,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;QAE3C,kBAAkB;QAClB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;QAEvF,uBAAuB;QACvB,IAAI,cAAc,GAAiC,SAAS,CAAC;QAE7D,IAAI,MAAM,CAAC,QAAQ,CAAC,0BAA0B,CAAC,EAAE,CAAC;YAChD,cAAc,GAAG,MAAM,CAAC;QAC1B,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,2BAA2B,CAAC,EAAE,CAAC;YACxD,cAAc,GAAG,OAAO,CAAC;QAC3B,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,wBAAwB,CAAC,EAAE,CAAC;YACrD,cAAc,GAAG,MAAM,CAAC;QAC1B,CAAC;aAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,CAAC;YACtD,cAAc,GAAG,OAAO,CAAC;QAC3B,CAAC;aAAM,IAAI,QAAQ,EAAE,CAAC;YACpB,iEAAiE;YACjE,cAAc,GAAG,MAAM,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,sCAAsC;YACtC,cAAc,GAAG,OAAO,CAAC;QAC3B,CAAC;QAED,OAAO;YACL,OAAO,EAAE,QAAQ;YACjB,cAAc;YACd,WAAW,EAAE,IAAI;SAClB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,0CAA0C;QAC1C,OAAO;YACL,OAAO,EAAE,KAAK;YACd,cAAc,EAAE,OAAO;YACvB,WAAW,EAAE,KAAK;SACnB,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector for network interface information
|
|
3
|
+
*/
|
|
4
|
+
export interface NetworkInterface {
|
|
5
|
+
name: string;
|
|
6
|
+
ips: string[];
|
|
7
|
+
isPublic: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function collectNetworkInterfaces(): Promise<NetworkInterface[]>;
|
|
10
|
+
//# sourceMappingURL=network.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../../src/system/collectors/network.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAY5E"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector for network interface information
|
|
3
|
+
*/
|
|
4
|
+
import { getInterfaces } from "../parsers/ip-parser.js";
|
|
5
|
+
import { execCommand } from "../shell.js";
|
|
6
|
+
export async function collectNetworkInterfaces() {
|
|
7
|
+
try {
|
|
8
|
+
const result = await execCommand("ip", ["addr"], { ignoreErrors: true });
|
|
9
|
+
if (!result.success) {
|
|
10
|
+
return [];
|
|
11
|
+
}
|
|
12
|
+
return getInterfaces(result.stdout);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=network.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network.js","sourceRoot":"","sources":["../../../src/system/collectors/network.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ1C,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QAEzE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,OAAO,aAAa,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../../../src/system/collectors/services.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAK9D,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA0B5E"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector for listening services via ss -tulnp
|
|
3
|
+
*/
|
|
4
|
+
import { parseIPAddr } from "../parsers/ip-parser.js";
|
|
5
|
+
import { parseSSOutput } from "../parsers/ss-parser.js";
|
|
6
|
+
import { execCommand } from "../shell.js";
|
|
7
|
+
export async function collectListeningServices() {
|
|
8
|
+
try {
|
|
9
|
+
// First, get IP to interface mapping
|
|
10
|
+
const ipAddrResult = await execCommand("ip", ["addr"], {
|
|
11
|
+
ignoreErrors: true,
|
|
12
|
+
});
|
|
13
|
+
const interfaceMap = ipAddrResult.success
|
|
14
|
+
? parseIPAddr(ipAddrResult.stdout)
|
|
15
|
+
: new Map();
|
|
16
|
+
// Get listening services
|
|
17
|
+
const ssResult = await execCommand("ss", ["-tulnp"], {
|
|
18
|
+
ignoreErrors: true,
|
|
19
|
+
});
|
|
20
|
+
if (!ssResult.success) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const services = parseSSOutput(ssResult.stdout, interfaceMap);
|
|
24
|
+
return services;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
// Best-effort: return empty array on failure
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=services.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"services.js","sourceRoot":"","sources":["../../../src/system/collectors/services.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAC5C,IAAI,CAAC;QACH,qCAAqC;QACrC,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,CAAC,EAAE;YACrD,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QAEH,MAAM,YAAY,GAAG,YAAY,CAAC,OAAO;YACvC,CAAC,CAAC,WAAW,CAAC,YAAY,CAAC,MAAM,CAAC;YAClC,CAAC,CAAC,IAAI,GAAG,EAAoB,CAAC;QAEhC,yBAAyB;QACzB,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,EAAE;YACnD,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;YACtB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAC9D,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,6CAA6C;QAC7C,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector for Tailscale detection
|
|
3
|
+
*/
|
|
4
|
+
export interface TailscaleStatus {
|
|
5
|
+
installed: boolean;
|
|
6
|
+
connected: boolean;
|
|
7
|
+
interface?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function collectTailscaleStatus(): Promise<TailscaleStatus>;
|
|
10
|
+
//# sourceMappingURL=tailscale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailscale.d.ts","sourceRoot":"","sources":["../../../src/system/collectors/tailscale.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAsB,sBAAsB,IAAI,OAAO,CAAC,eAAe,CAAC,CAsCvE"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collector for Tailscale detection
|
|
3
|
+
*/
|
|
4
|
+
import { commandExists, execCommand } from "../shell.js";
|
|
5
|
+
export async function collectTailscaleStatus() {
|
|
6
|
+
try {
|
|
7
|
+
// Check if tailscale command exists
|
|
8
|
+
const installed = await commandExists("tailscale");
|
|
9
|
+
if (!installed) {
|
|
10
|
+
return {
|
|
11
|
+
installed: false,
|
|
12
|
+
connected: false,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
// Check connection status
|
|
16
|
+
const statusResult = await execCommand("tailscale", ["status"], {
|
|
17
|
+
ignoreErrors: true,
|
|
18
|
+
});
|
|
19
|
+
// Connected if command succeeds and has output
|
|
20
|
+
const connected = statusResult.success && statusResult.stdout.length > 0;
|
|
21
|
+
// Check for tailscale0 interface
|
|
22
|
+
const ipResult = await execCommand("ip", ["addr", "show", "tailscale0"], {
|
|
23
|
+
ignoreErrors: true,
|
|
24
|
+
});
|
|
25
|
+
const hasInterface = ipResult.success;
|
|
26
|
+
return {
|
|
27
|
+
installed: true,
|
|
28
|
+
connected,
|
|
29
|
+
interface: hasInterface ? "tailscale0" : undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
return {
|
|
34
|
+
installed: false,
|
|
35
|
+
connected: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=tailscale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tailscale.js","sourceRoot":"","sources":["../../../src/system/collectors/tailscale.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQzD,MAAM,CAAC,KAAK,UAAU,sBAAsB;IAC1C,IAAI,CAAC;QACH,oCAAoC;QACpC,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,WAAW,CAAC,CAAC;QAEnD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO;gBACL,SAAS,EAAE,KAAK;gBAChB,SAAS,EAAE,KAAK;aACjB,CAAC;QACJ,CAAC;QAED,0BAA0B;QAC1B,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,EAAE;YAC9D,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QAEH,+CAA+C;QAC/C,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAEzE,iCAAiC;QACjC,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE;YACvE,YAAY,EAAE,IAAI;SACnB,CAAC,CAAC;QAEH,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC;QAEtC,OAAO;YACL,SAAS,EAAE,IAAI;YACf,SAAS;YACT,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;SACnD,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,SAAS,EAAE,KAAK;YAChB,SAAS,EAAE,KAAK;SACjB,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
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 declare function parseIPAddr(output: string): Map<string, string[]>;
|
|
17
|
+
/**
|
|
18
|
+
* Get list of all network interfaces from ip addr output
|
|
19
|
+
*/
|
|
20
|
+
export declare function getInterfaces(output: string): Array<{
|
|
21
|
+
name: string;
|
|
22
|
+
ips: string[];
|
|
23
|
+
isPublic: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
//# sourceMappingURL=ip-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ip-parser.d.ts","sourceRoot":"","sources":["../../../src/system/parsers/ip-parser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAyCjE;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,KAAK,CAAC;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC,CAuBD"}
|