clawhatch 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 +348 -0
- package/dist/checks/cloud-sync.d.ts +10 -0
- package/dist/checks/cloud-sync.d.ts.map +1 -0
- package/dist/checks/cloud-sync.js +62 -0
- package/dist/checks/cloud-sync.js.map +1 -0
- package/dist/checks/data-protection.d.ts +9 -0
- package/dist/checks/data-protection.d.ts.map +1 -0
- package/dist/checks/data-protection.js +197 -0
- package/dist/checks/data-protection.js.map +1 -0
- package/dist/checks/identity.d.ts +14 -0
- package/dist/checks/identity.d.ts.map +1 -0
- package/dist/checks/identity.js +327 -0
- package/dist/checks/identity.js.map +1 -0
- package/dist/checks/model.d.ts +10 -0
- package/dist/checks/model.d.ts.map +1 -0
- package/dist/checks/model.js +337 -0
- package/dist/checks/model.js.map +1 -0
- package/dist/checks/network.d.ts +9 -0
- package/dist/checks/network.d.ts.map +1 -0
- package/dist/checks/network.js +177 -0
- package/dist/checks/network.js.map +1 -0
- package/dist/checks/operational.d.ts +9 -0
- package/dist/checks/operational.d.ts.map +1 -0
- package/dist/checks/operational.js +158 -0
- package/dist/checks/operational.js.map +1 -0
- package/dist/checks/sandbox.d.ts +9 -0
- package/dist/checks/sandbox.d.ts.map +1 -0
- package/dist/checks/sandbox.js +135 -0
- package/dist/checks/sandbox.js.map +1 -0
- package/dist/checks/secrets.d.ts +9 -0
- package/dist/checks/secrets.d.ts.map +1 -0
- package/dist/checks/secrets.js +816 -0
- package/dist/checks/secrets.js.map +1 -0
- package/dist/checks/skills.d.ts +9 -0
- package/dist/checks/skills.d.ts.map +1 -0
- package/dist/checks/skills.js +303 -0
- package/dist/checks/skills.js.map +1 -0
- package/dist/checks/tools.d.ts +9 -0
- package/dist/checks/tools.d.ts.map +1 -0
- package/dist/checks/tools.js +397 -0
- package/dist/checks/tools.js.map +1 -0
- package/dist/discover.d.ts +22 -0
- package/dist/discover.d.ts.map +1 -0
- package/dist/discover.js +281 -0
- package/dist/discover.js.map +1 -0
- package/dist/fixer.d.ts +16 -0
- package/dist/fixer.d.ts.map +1 -0
- package/dist/fixer.js +361 -0
- package/dist/fixer.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +230 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +14 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +108 -0
- package/dist/init.js.map +1 -0
- package/dist/notify.d.ts +28 -0
- package/dist/notify.d.ts.map +1 -0
- package/dist/notify.js +217 -0
- package/dist/notify.js.map +1 -0
- package/dist/parsers/config.d.ts +16 -0
- package/dist/parsers/config.d.ts.map +1 -0
- package/dist/parsers/config.js +54 -0
- package/dist/parsers/config.js.map +1 -0
- package/dist/parsers/env.d.ts +6 -0
- package/dist/parsers/env.d.ts.map +1 -0
- package/dist/parsers/env.js +35 -0
- package/dist/parsers/env.js.map +1 -0
- package/dist/parsers/jsonl.d.ts +12 -0
- package/dist/parsers/jsonl.d.ts.map +1 -0
- package/dist/parsers/jsonl.js +61 -0
- package/dist/parsers/jsonl.js.map +1 -0
- package/dist/parsers/markdown.d.ts +17 -0
- package/dist/parsers/markdown.d.ts.map +1 -0
- package/dist/parsers/markdown.js +57 -0
- package/dist/parsers/markdown.js.map +1 -0
- package/dist/reporter-html.d.ts +9 -0
- package/dist/reporter-html.d.ts.map +1 -0
- package/dist/reporter-html.js +581 -0
- package/dist/reporter-html.js.map +1 -0
- package/dist/reporter.d.ts +10 -0
- package/dist/reporter.d.ts.map +1 -0
- package/dist/reporter.js +133 -0
- package/dist/reporter.js.map +1 -0
- package/dist/sanitize.d.ts +17 -0
- package/dist/sanitize.d.ts.map +1 -0
- package/dist/sanitize.js +83 -0
- package/dist/sanitize.js.map +1 -0
- package/dist/scanner.d.ts +18 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +236 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scoring.d.ts +17 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +47 -0
- package/dist/scoring.js.map +1 -0
- package/dist/telemetry.d.ts +16 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +52 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/threat-feed.d.ts +14 -0
- package/dist/threat-feed.d.ts.map +1 -0
- package/dist/threat-feed.js +133 -0
- package/dist/threat-feed.js.map +1 -0
- package/dist/types.d.ts +221 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +34 -0
- package/dist/utils.js.map +1 -0
- package/package.json +71 -0
package/dist/reporter.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal output reporter.
|
|
3
|
+
*
|
|
4
|
+
* Formats scan results as colored terminal output.
|
|
5
|
+
* Separates high-confidence "Findings" from low-confidence "Suggestions".
|
|
6
|
+
*/
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { Severity } from "./types.js";
|
|
9
|
+
import { getScoreGrade } from "./scoring.js";
|
|
10
|
+
const SEVERITY_ICONS = {
|
|
11
|
+
[Severity.Critical]: chalk.red("!!"),
|
|
12
|
+
[Severity.High]: chalk.yellow("!"),
|
|
13
|
+
[Severity.Medium]: chalk.cyan("~"),
|
|
14
|
+
[Severity.Low]: chalk.dim("-"),
|
|
15
|
+
};
|
|
16
|
+
const SEVERITY_LABELS = {
|
|
17
|
+
[Severity.Critical]: chalk.red.bold("CRITICAL"),
|
|
18
|
+
[Severity.High]: chalk.yellow.bold("HIGH"),
|
|
19
|
+
[Severity.Medium]: chalk.cyan("MEDIUM"),
|
|
20
|
+
[Severity.Low]: chalk.dim("LOW"),
|
|
21
|
+
};
|
|
22
|
+
function formatFinding(finding, index) {
|
|
23
|
+
const icon = SEVERITY_ICONS[finding.severity];
|
|
24
|
+
const lines = [];
|
|
25
|
+
lines.push(` ${icon} ${chalk.white.bold(finding.title)}`);
|
|
26
|
+
lines.push(` ${chalk.dim(finding.description)}`);
|
|
27
|
+
lines.push(` ${chalk.dim("Risk:")} ${finding.risk}`);
|
|
28
|
+
lines.push(` ${chalk.dim("Fix:")} ${finding.remediation}`);
|
|
29
|
+
if (finding.file) {
|
|
30
|
+
const loc = finding.line ? `${finding.file}:${finding.line}` : finding.file;
|
|
31
|
+
lines.push(` ${chalk.dim("File:")} ${loc}`);
|
|
32
|
+
}
|
|
33
|
+
if (finding.autoFixable) {
|
|
34
|
+
lines.push(` ${chalk.green("Auto-fixable with --fix")}`);
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
function groupBySeverity(findings) {
|
|
39
|
+
const groups = new Map();
|
|
40
|
+
const order = [Severity.Critical, Severity.High, Severity.Medium, Severity.Low];
|
|
41
|
+
for (const sev of order) {
|
|
42
|
+
groups.set(sev, []);
|
|
43
|
+
}
|
|
44
|
+
for (const f of findings) {
|
|
45
|
+
groups.get(f.severity).push(f);
|
|
46
|
+
}
|
|
47
|
+
return groups;
|
|
48
|
+
}
|
|
49
|
+
export function reportFindings(result) {
|
|
50
|
+
const { grade, label, color } = getScoreGrade(result.score);
|
|
51
|
+
const colorFn = chalk[color];
|
|
52
|
+
// Header
|
|
53
|
+
console.log("");
|
|
54
|
+
console.log(chalk.cyan.bold(" Clawhatch Security Scan"));
|
|
55
|
+
console.log(chalk.dim(" " + "=".repeat(50)));
|
|
56
|
+
console.log("");
|
|
57
|
+
// Score
|
|
58
|
+
console.log(` ${chalk.dim("Security Score:")} ${colorFn(`${result.score}/100`)} ${colorFn(`(${grade} — ${label})`)}`);
|
|
59
|
+
console.log("");
|
|
60
|
+
// Metadata
|
|
61
|
+
console.log(chalk.dim(` Platform: ${result.platform}`));
|
|
62
|
+
if (result.openclawVersion) {
|
|
63
|
+
console.log(chalk.dim(` OpenClaw: ${result.openclawVersion}`));
|
|
64
|
+
}
|
|
65
|
+
console.log(chalk.dim(` Checks: ${result.checksRun} run, ${result.checksPassed} passed, ${result.findings.length} findings`));
|
|
66
|
+
console.log(chalk.dim(` Duration: ${result.duration}ms`));
|
|
67
|
+
console.log(chalk.dim(` Scanned: ${result.filesScanned} files`));
|
|
68
|
+
console.log("");
|
|
69
|
+
console.log(chalk.dim(" " + "-".repeat(50)));
|
|
70
|
+
// Findings (high/medium confidence)
|
|
71
|
+
if (result.findings.length === 0) {
|
|
72
|
+
console.log("");
|
|
73
|
+
console.log(chalk.green.bold(" No security findings! Your setup looks solid."));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
const grouped = groupBySeverity(result.findings);
|
|
77
|
+
for (const [severity, findings] of grouped) {
|
|
78
|
+
if (findings.length === 0)
|
|
79
|
+
continue;
|
|
80
|
+
console.log("");
|
|
81
|
+
console.log(` ${SEVERITY_LABELS[severity]} (${findings.length} finding${findings.length > 1 ? "s" : ""})`);
|
|
82
|
+
console.log("");
|
|
83
|
+
for (let i = 0; i < findings.length; i++) {
|
|
84
|
+
console.log(formatFinding(findings[i], i));
|
|
85
|
+
if (i < findings.length - 1)
|
|
86
|
+
console.log("");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Suggestions (low confidence)
|
|
91
|
+
if (result.suggestions.length > 0) {
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log(chalk.dim(" " + "-".repeat(50)));
|
|
94
|
+
console.log("");
|
|
95
|
+
console.log(chalk.dim.bold(` SUGGESTIONS (${result.suggestions.length} — lower confidence, review manually)`));
|
|
96
|
+
console.log("");
|
|
97
|
+
for (const s of result.suggestions) {
|
|
98
|
+
console.log(` ${chalk.dim("~")} ${chalk.dim(s.title)}`);
|
|
99
|
+
console.log(` ${chalk.dim(s.remediation)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Footer
|
|
103
|
+
console.log("");
|
|
104
|
+
console.log(chalk.dim(" " + "=".repeat(50)));
|
|
105
|
+
console.log("");
|
|
106
|
+
const autoFixCount = result.findings.filter((f) => f.autoFixable).length;
|
|
107
|
+
if (autoFixCount > 0) {
|
|
108
|
+
console.log(chalk.green(` ${autoFixCount} issue(s) can be auto-fixed. Run with --fix`));
|
|
109
|
+
}
|
|
110
|
+
console.log(chalk.dim(" Run with --json for machine-readable output"));
|
|
111
|
+
console.log(chalk.dim(" Run with --deep for thorough session log scanning"));
|
|
112
|
+
console.log("");
|
|
113
|
+
}
|
|
114
|
+
export function reportJson(result) {
|
|
115
|
+
// Output clean JSON for piping to other tools
|
|
116
|
+
const output = {
|
|
117
|
+
...result,
|
|
118
|
+
// Flatten for convenience
|
|
119
|
+
summary: {
|
|
120
|
+
score: result.score,
|
|
121
|
+
grade: getScoreGrade(result.score).grade,
|
|
122
|
+
label: getScoreGrade(result.score).label,
|
|
123
|
+
critical: result.findings.filter((f) => f.severity === Severity.Critical).length,
|
|
124
|
+
high: result.findings.filter((f) => f.severity === Severity.High).length,
|
|
125
|
+
medium: result.findings.filter((f) => f.severity === Severity.Medium).length,
|
|
126
|
+
low: result.findings.filter((f) => f.severity === Severity.Low).length,
|
|
127
|
+
suggestions: result.suggestions.length,
|
|
128
|
+
autoFixable: result.findings.filter((f) => f.autoFixable).length,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
console.log(JSON.stringify(output, null, 2));
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.js","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize findings before JSON serialization or upload.
|
|
3
|
+
* Policy: Finding objects must NEVER contain secret values.
|
|
4
|
+
* Only metadata about secrets (length, entropy score, masked preview).
|
|
5
|
+
*/
|
|
6
|
+
import type { Finding } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Sanitize all findings in an array.
|
|
9
|
+
* Call this before any JSON.stringify() or upload operation.
|
|
10
|
+
*/
|
|
11
|
+
export declare function sanitizeFindings(findings: Finding[]): Finding[];
|
|
12
|
+
/**
|
|
13
|
+
* Redact file paths from findings for safe sharing.
|
|
14
|
+
* Replaces full paths with just the filename or a generic placeholder.
|
|
15
|
+
*/
|
|
16
|
+
export declare function redactPaths(findings: Finding[]): Finding[];
|
|
17
|
+
//# sourceMappingURL=sanitize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAsD1C;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAE/D;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAyB1D"}
|
package/dist/sanitize.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize findings before JSON serialization or upload.
|
|
3
|
+
* Policy: Finding objects must NEVER contain secret values.
|
|
4
|
+
* Only metadata about secrets (length, entropy score, masked preview).
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Patterns that might indicate a secret value leaked into a finding field.
|
|
8
|
+
*/
|
|
9
|
+
const SECRET_INDICATORS = [
|
|
10
|
+
/sk-[a-zA-Z0-9]{20,}/,
|
|
11
|
+
/sk-ant-[a-zA-Z0-9\-]{20,}/,
|
|
12
|
+
/AIza[a-zA-Z0-9_\-]{30,}/,
|
|
13
|
+
/AKIA[A-Z0-9]{16}/,
|
|
14
|
+
/(?:ghp|gho|ghu|ghs|ghr)_[a-zA-Z0-9]{30,}/,
|
|
15
|
+
/xox[bpras]-[a-zA-Z0-9\-]{10,}/,
|
|
16
|
+
/-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/,
|
|
17
|
+
/(?:sk|pk)_(?:live|test)_[a-zA-Z0-9]{20,}/,
|
|
18
|
+
/\d{8,10}:[a-zA-Z0-9_-]{35}/,
|
|
19
|
+
// FIX: Additional patterns to match secrets detected by the scanner
|
|
20
|
+
/whsec_[a-zA-Z0-9]{20,}/, // Stripe webhook secrets
|
|
21
|
+
/rk_live_[a-zA-Z0-9]{20,}/, // Stripe restricted keys
|
|
22
|
+
/[MN][a-zA-Z0-9]{23,}\.[a-zA-Z0-9_-]{6}\.[a-zA-Z0-9_-]{27,}/, // Discord tokens
|
|
23
|
+
];
|
|
24
|
+
function containsSecret(text) {
|
|
25
|
+
return SECRET_INDICATORS.some((pattern) => pattern.test(text));
|
|
26
|
+
}
|
|
27
|
+
function redactSecrets(text) {
|
|
28
|
+
let result = text;
|
|
29
|
+
for (const pattern of SECRET_INDICATORS) {
|
|
30
|
+
result = result.replace(new RegExp(pattern.source, "g"), "[REDACTED]");
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Sanitize a single finding — strip any secret values from all string fields.
|
|
36
|
+
*/
|
|
37
|
+
function sanitizeFinding(finding) {
|
|
38
|
+
return {
|
|
39
|
+
...finding,
|
|
40
|
+
title: containsSecret(finding.title)
|
|
41
|
+
? redactSecrets(finding.title)
|
|
42
|
+
: finding.title,
|
|
43
|
+
description: containsSecret(finding.description)
|
|
44
|
+
? redactSecrets(finding.description)
|
|
45
|
+
: finding.description,
|
|
46
|
+
risk: containsSecret(finding.risk)
|
|
47
|
+
? redactSecrets(finding.risk)
|
|
48
|
+
: finding.risk,
|
|
49
|
+
remediation: containsSecret(finding.remediation)
|
|
50
|
+
? redactSecrets(finding.remediation)
|
|
51
|
+
: finding.remediation,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Sanitize all findings in an array.
|
|
56
|
+
* Call this before any JSON.stringify() or upload operation.
|
|
57
|
+
*/
|
|
58
|
+
export function sanitizeFindings(findings) {
|
|
59
|
+
return findings.map(sanitizeFinding);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Redact file paths from findings for safe sharing.
|
|
63
|
+
* Replaces full paths with just the filename or a generic placeholder.
|
|
64
|
+
*/
|
|
65
|
+
export function redactPaths(findings) {
|
|
66
|
+
return findings.map((finding) => {
|
|
67
|
+
let description = finding.description;
|
|
68
|
+
// Redact full paths in description, keeping just filename
|
|
69
|
+
// Windows paths: C:\Users\...\filename -> [path]/filename
|
|
70
|
+
// Unix paths: /home/user/.../filename -> [path]/filename
|
|
71
|
+
description = description.replace(/[A-Z]:\\[^"'\s]+\\([^"'\s\\]+)/gi, "[path]/$1");
|
|
72
|
+
description = description.replace(/\/(?:home|Users|root|var|etc)[^"'\s]*\/([^"'\s/]+)/g, "[path]/$1");
|
|
73
|
+
return {
|
|
74
|
+
...finding,
|
|
75
|
+
description,
|
|
76
|
+
// Remove the file path entirely, keep just the line number concept
|
|
77
|
+
file: finding.file
|
|
78
|
+
? `[redacted]/${finding.file.split(/[/\\]/).pop()}`
|
|
79
|
+
: undefined,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=sanitize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH;;GAEG;AACH,MAAM,iBAAiB,GAAG;IACxB,qBAAqB;IACrB,2BAA2B;IAC3B,yBAAyB;IACzB,kBAAkB;IAClB,0CAA0C;IAC1C,+BAA+B;IAC/B,+CAA+C;IAC/C,0CAA0C;IAC1C,4BAA4B;IAC5B,oEAAoE;IACpE,wBAAwB,EAAiB,yBAAyB;IAClE,0BAA0B,EAAe,yBAAyB;IAClE,4DAA4D,EAAE,iBAAiB;CAChF,CAAC;AAEF,SAAS,cAAc,CAAC,IAAY;IAClC,OAAO,iBAAiB,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AACjE,CAAC;AAED,SAAS,aAAa,CAAC,IAAY;IACjC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,OAAO,IAAI,iBAAiB,EAAE,CAAC;QACxC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,YAAY,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,OAAgB;IACvC,OAAO;QACL,GAAG,OAAO;QACV,KAAK,EAAE,cAAc,CAAC,OAAO,CAAC,KAAK,CAAC;YAClC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC;YAC9B,CAAC,CAAC,OAAO,CAAC,KAAK;QACjB,WAAW,EAAE,cAAc,CAAC,OAAO,CAAC,WAAW,CAAC;YAC9C,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,WAAW,CAAC;YACpC,CAAC,CAAC,OAAO,CAAC,WAAW;QACvB,IAAI,EAAE,cAAc,CAAC,OAAO,CAAC,IAAI,CAAC;YAChC,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC;YAC7B,CAAC,CAAC,OAAO,CAAC,IAAI;QAChB,WAAW,EAAE,cAAc,CAAC,OAAO,CAAC,WAAW,CAAC;YAC9C,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,WAAW,CAAC;YACpC,CAAC,CAAC,OAAO,CAAC,WAAW;KACxB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAmB;IAClD,OAAO,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,QAAmB;IAC7C,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,IAAI,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;QAEtC,0DAA0D;QAC1D,0DAA0D;QAC1D,yDAAyD;QACzD,WAAW,GAAG,WAAW,CAAC,OAAO,CAC/B,kCAAkC,EAClC,WAAW,CACZ,CAAC;QACF,WAAW,GAAG,WAAW,CAAC,OAAO,CAC/B,qDAAqD,EACrD,WAAW,CACZ,CAAC;QAEF,OAAO;YACL,GAAG,OAAO;YACV,WAAW;YACX,mEAAmE;YACnE,IAAI,EAAE,OAAO,CAAC,IAAI;gBAChB,CAAC,CAAC,cAAc,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBACnD,CAAC,CAAC,SAAS;SACd,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main scanner orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline:
|
|
5
|
+
* 1. Discover files
|
|
6
|
+
* 2. Parse config, env, markdown
|
|
7
|
+
* 3. Run all check categories
|
|
8
|
+
* 4. Score results
|
|
9
|
+
* 5. Sanitize findings
|
|
10
|
+
* 6. Separate findings from suggestions (by confidence)
|
|
11
|
+
* 7. Return ScanResult
|
|
12
|
+
*/
|
|
13
|
+
import type { ScanOptions, ScanResult } from "./types.js";
|
|
14
|
+
/**
|
|
15
|
+
* Run the full security scan.
|
|
16
|
+
*/
|
|
17
|
+
export declare function scan(options: ScanOptions): Promise<ScanResult>;
|
|
18
|
+
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAoBH,OAAO,KAAK,EAAW,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA0EnE;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CA0LpE"}
|
package/dist/scanner.js
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main scanner orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* Pipeline:
|
|
5
|
+
* 1. Discover files
|
|
6
|
+
* 2. Parse config, env, markdown
|
|
7
|
+
* 3. Run all check categories
|
|
8
|
+
* 4. Score results
|
|
9
|
+
* 5. Sanitize findings
|
|
10
|
+
* 6. Separate findings from suggestions (by confidence)
|
|
11
|
+
* 7. Return ScanResult
|
|
12
|
+
*/
|
|
13
|
+
import { execFile } from "node:child_process";
|
|
14
|
+
import { promisify } from "node:util";
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import { findOpenClawDir, discoverFiles } from "./discover.js";
|
|
17
|
+
import { parseConfig, readConfigRaw } from "./parsers/config.js";
|
|
18
|
+
import { runIdentityChecks } from "./checks/identity.js";
|
|
19
|
+
import { runNetworkChecks } from "./checks/network.js";
|
|
20
|
+
import { runSandboxChecks } from "./checks/sandbox.js";
|
|
21
|
+
import { runSecretChecks } from "./checks/secrets.js";
|
|
22
|
+
import { runModelChecks } from "./checks/model.js";
|
|
23
|
+
import { runCloudSyncCheck } from "./checks/cloud-sync.js";
|
|
24
|
+
import { runToolChecks } from "./checks/tools.js";
|
|
25
|
+
import { runSkillChecks } from "./checks/skills.js";
|
|
26
|
+
import { runDataProtectionChecks } from "./checks/data-protection.js";
|
|
27
|
+
import { runOperationalChecks } from "./checks/operational.js";
|
|
28
|
+
import { calculateScore } from "./scoring.js";
|
|
29
|
+
import { sanitizeFindings } from "./sanitize.js";
|
|
30
|
+
const execFileAsync = promisify(execFile);
|
|
31
|
+
/**
|
|
32
|
+
* Deduplicate findings by check ID.
|
|
33
|
+
* When the same check fires for multiple files (e.g., SECRET-005 for each credential file),
|
|
34
|
+
* aggregate them into a single finding with updated description showing the count and file list.
|
|
35
|
+
*/
|
|
36
|
+
function deduplicateFindings(findings) {
|
|
37
|
+
const grouped = new Map();
|
|
38
|
+
for (const f of findings) {
|
|
39
|
+
const existing = grouped.get(f.id) || [];
|
|
40
|
+
existing.push(f);
|
|
41
|
+
grouped.set(f.id, existing);
|
|
42
|
+
}
|
|
43
|
+
const deduplicated = [];
|
|
44
|
+
for (const [id, group] of grouped) {
|
|
45
|
+
if (group.length === 1) {
|
|
46
|
+
deduplicated.push(group[0]);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Aggregate multiple findings with same ID
|
|
50
|
+
const first = group[0];
|
|
51
|
+
const files = group.map((f) => f.file).filter(Boolean);
|
|
52
|
+
const uniqueFiles = [...new Set(files)];
|
|
53
|
+
// Build aggregated description
|
|
54
|
+
let description = first.description;
|
|
55
|
+
if (uniqueFiles.length > 1) {
|
|
56
|
+
const fileList = uniqueFiles.length <= 3
|
|
57
|
+
? uniqueFiles.map((f) => f.split(/[/\\]/).pop()).join(", ")
|
|
58
|
+
: `${uniqueFiles.slice(0, 3).map((f) => f.split(/[/\\]/).pop()).join(", ")}... and ${uniqueFiles.length - 3} more`;
|
|
59
|
+
description = `${first.title} (${group.length} occurrences in: ${fileList})`;
|
|
60
|
+
}
|
|
61
|
+
deduplicated.push({
|
|
62
|
+
...first,
|
|
63
|
+
description,
|
|
64
|
+
// Keep the first file for reference, but note there are more
|
|
65
|
+
file: uniqueFiles[0],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return deduplicated;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* TOTAL_CHECKS: This is the "marketed" number of checks (100-point audit).
|
|
73
|
+
* The actual number of check IDs varies based on config and files found,
|
|
74
|
+
* since some checks only fire conditionally. We track checksRun separately
|
|
75
|
+
* from this constant, which represents the maximum possible checks in a
|
|
76
|
+
* comprehensive scan. The score formula uses actual findings, not this number.
|
|
77
|
+
*/
|
|
78
|
+
const TOTAL_CHECKS = 100;
|
|
79
|
+
/**
|
|
80
|
+
* Detect OpenClaw version by running `openclaw --version`.
|
|
81
|
+
*/
|
|
82
|
+
async function detectVersion() {
|
|
83
|
+
try {
|
|
84
|
+
const { stdout } = await execFileAsync("openclaw", ["--version"], {
|
|
85
|
+
timeout: 5000,
|
|
86
|
+
});
|
|
87
|
+
return stdout.trim();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Run the full security scan.
|
|
95
|
+
*/
|
|
96
|
+
export async function scan(options) {
|
|
97
|
+
const startTime = Date.now();
|
|
98
|
+
// Use stderr for progress messages when JSON output is requested
|
|
99
|
+
const log = options.json
|
|
100
|
+
? (...args) => console.error(...args)
|
|
101
|
+
: (...args) => console.log(...args);
|
|
102
|
+
// Step 0: Find OpenClaw installation
|
|
103
|
+
const openclawDir = await findOpenClawDir(options.openclawPath);
|
|
104
|
+
if (!openclawDir) {
|
|
105
|
+
console.error(chalk.red("\n OpenClaw installation not found."));
|
|
106
|
+
console.error(chalk.dim(" Try: clawhatch scan --path /custom/path"));
|
|
107
|
+
console.error(chalk.dim(" Common locations:"));
|
|
108
|
+
if (process.platform === "win32") {
|
|
109
|
+
console.error(chalk.dim(" Windows: %APPDATA%\\openclaw"));
|
|
110
|
+
}
|
|
111
|
+
console.error(chalk.dim(" macOS/Linux: ~/.openclaw"));
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
// Step 0.5: Detect version
|
|
115
|
+
const version = await detectVersion();
|
|
116
|
+
if (version) {
|
|
117
|
+
log(chalk.dim(` OpenClaw version: ${version}`));
|
|
118
|
+
}
|
|
119
|
+
// Step 1: Discover files
|
|
120
|
+
log(chalk.dim(" Discovering files..."));
|
|
121
|
+
const { files, symlinkWarnings } = await discoverFiles(openclawDir, options.workspacePath ?? null);
|
|
122
|
+
// Report symlink warnings
|
|
123
|
+
for (const warning of symlinkWarnings) {
|
|
124
|
+
log(chalk.yellow(` Warning: ${warning}`));
|
|
125
|
+
}
|
|
126
|
+
let filesScanned = 0;
|
|
127
|
+
if (files.configPath)
|
|
128
|
+
filesScanned++;
|
|
129
|
+
if (files.envPath)
|
|
130
|
+
filesScanned++;
|
|
131
|
+
filesScanned += files.credentialFiles.length;
|
|
132
|
+
filesScanned += files.authProfileFiles.length;
|
|
133
|
+
filesScanned += files.sessionLogFiles.length;
|
|
134
|
+
filesScanned += files.workspaceMarkdownFiles.length;
|
|
135
|
+
filesScanned += files.skillFiles.length;
|
|
136
|
+
filesScanned += files.customCommandFiles.length;
|
|
137
|
+
filesScanned += files.skillPackageFiles.length;
|
|
138
|
+
filesScanned += files.privateKeyFiles.length;
|
|
139
|
+
filesScanned += files.sshKeyFiles.length;
|
|
140
|
+
if (!files.configPath) {
|
|
141
|
+
log(chalk.yellow(" Warning: openclaw.json not found — config-based checks will be limited"));
|
|
142
|
+
}
|
|
143
|
+
if (!options.workspacePath) {
|
|
144
|
+
log(chalk.dim(" Tip: Run with --workspace <path> for full scan including SOUL.md, skills, etc."));
|
|
145
|
+
}
|
|
146
|
+
// Step 2: Parse files
|
|
147
|
+
log(chalk.dim(" Parsing configuration..."));
|
|
148
|
+
const config = files.configPath
|
|
149
|
+
? await parseConfig(files.configPath)
|
|
150
|
+
: null;
|
|
151
|
+
const configRaw = files.configPath
|
|
152
|
+
? await readConfigRaw(files.configPath)
|
|
153
|
+
: null;
|
|
154
|
+
// FIX: Warn if config file exists but couldn't be parsed (corrupt JSON, encoding issues, etc.)
|
|
155
|
+
if (files.configPath && !config) {
|
|
156
|
+
log(chalk.yellow(" Warning: openclaw.json exists but could not be parsed — config-based checks will be skipped"));
|
|
157
|
+
}
|
|
158
|
+
// Step 3: Run all checks
|
|
159
|
+
log(chalk.dim(" Running 100 security checks..."));
|
|
160
|
+
const allFindings = [];
|
|
161
|
+
if (config) {
|
|
162
|
+
// Identity & Access (checks 1-15)
|
|
163
|
+
const identityFindings = await runIdentityChecks(config, {
|
|
164
|
+
credentialFiles: files.credentialFiles,
|
|
165
|
+
authProfileFiles: files.authProfileFiles,
|
|
166
|
+
});
|
|
167
|
+
allFindings.push(...identityFindings);
|
|
168
|
+
// Network Exposure (checks 16-25)
|
|
169
|
+
const networkFindings = await runNetworkChecks(config);
|
|
170
|
+
allFindings.push(...networkFindings);
|
|
171
|
+
// Sandbox Configuration (checks 26-33)
|
|
172
|
+
const sandboxFindings = await runSandboxChecks(config);
|
|
173
|
+
allFindings.push(...sandboxFindings);
|
|
174
|
+
}
|
|
175
|
+
// Secret Scanning (checks 34-43)
|
|
176
|
+
if (config || files.workspaceMarkdownFiles.length > 0) {
|
|
177
|
+
const secretFindings = await runSecretChecks(config || {}, configRaw, files, options.deep);
|
|
178
|
+
allFindings.push(...secretFindings);
|
|
179
|
+
}
|
|
180
|
+
// Model Security (checks 44-50)
|
|
181
|
+
if (config) {
|
|
182
|
+
const soulMdPath = files.workspaceMarkdownFiles.find((f) => f.toLowerCase().endsWith("soul.md")) ?? null;
|
|
183
|
+
const modelFindings = await runModelChecks(config, soulMdPath);
|
|
184
|
+
allFindings.push(...modelFindings);
|
|
185
|
+
}
|
|
186
|
+
// Tool Security (checks TOOLS-001 to TOOLS-020)
|
|
187
|
+
if (config) {
|
|
188
|
+
const toolFindings = await runToolChecks(config, files);
|
|
189
|
+
allFindings.push(...toolFindings);
|
|
190
|
+
}
|
|
191
|
+
// Skill Security (checks SKILLS-001 to SKILLS-012)
|
|
192
|
+
if (config || files.skillFiles.length > 0) {
|
|
193
|
+
const skillFindings = await runSkillChecks(config || {}, files);
|
|
194
|
+
allFindings.push(...skillFindings);
|
|
195
|
+
}
|
|
196
|
+
// Data Protection (checks DATA-001 to DATA-010)
|
|
197
|
+
{
|
|
198
|
+
const dataFindings = await runDataProtectionChecks(config || {}, files);
|
|
199
|
+
allFindings.push(...dataFindings);
|
|
200
|
+
}
|
|
201
|
+
// Operational Security (checks OPS-001 to OPS-007)
|
|
202
|
+
{
|
|
203
|
+
const opsFindings = await runOperationalChecks(config || {}, files);
|
|
204
|
+
allFindings.push(...opsFindings);
|
|
205
|
+
}
|
|
206
|
+
// Cloud Sync Detection (check 51)
|
|
207
|
+
const cloudFindings = await runCloudSyncCheck(openclawDir);
|
|
208
|
+
allFindings.push(...cloudFindings);
|
|
209
|
+
// Step 4: Deduplicate findings by check ID
|
|
210
|
+
// When the same issue (e.g., SECRET-005 for loose permissions) appears in multiple files,
|
|
211
|
+
// aggregate them into a single finding with a count, rather than N separate findings.
|
|
212
|
+
const deduplicatedFindings = deduplicateFindings(allFindings);
|
|
213
|
+
// Step 5: Separate findings from suggestions
|
|
214
|
+
const findings = deduplicatedFindings.filter((f) => f.confidence !== "low");
|
|
215
|
+
const suggestions = deduplicatedFindings.filter((f) => f.confidence === "low");
|
|
216
|
+
// Step 6: Sanitize (strip any accidental secret values)
|
|
217
|
+
const sanitizedFindings = sanitizeFindings(findings);
|
|
218
|
+
const sanitizedSuggestions = sanitizeFindings(suggestions);
|
|
219
|
+
// Step 7: Calculate score (only high-confidence findings count)
|
|
220
|
+
const score = calculateScore(sanitizedFindings);
|
|
221
|
+
const duration = Date.now() - startTime;
|
|
222
|
+
return {
|
|
223
|
+
timestamp: new Date().toISOString(),
|
|
224
|
+
openclawVersion: version,
|
|
225
|
+
score,
|
|
226
|
+
findings: sanitizedFindings,
|
|
227
|
+
suggestions: sanitizedSuggestions,
|
|
228
|
+
filesScanned,
|
|
229
|
+
checksRun: TOTAL_CHECKS,
|
|
230
|
+
// FIX: Clamp to 0 — more findings than checks is possible since some checks emit multiple findings
|
|
231
|
+
checksPassed: Math.max(0, TOTAL_CHECKS - sanitizedFindings.length),
|
|
232
|
+
duration,
|
|
233
|
+
platform: process.platform,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scanner.js","sourceRoot":"","sources":["../src/scanner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score calculation engine.
|
|
3
|
+
*
|
|
4
|
+
* Formula:
|
|
5
|
+
* base = 100
|
|
6
|
+
* penalties: CRITICAL=-15, HIGH=-8, MEDIUM=-3, LOW=-1
|
|
7
|
+
* score = max(0, base - sum(penalties))
|
|
8
|
+
* Cap: any CRITICAL finding hard-caps score at 40
|
|
9
|
+
*/
|
|
10
|
+
import { type Finding } from "./types.js";
|
|
11
|
+
export declare function calculateScore(findings: Finding[]): number;
|
|
12
|
+
export declare function getScoreGrade(score: number): {
|
|
13
|
+
grade: string;
|
|
14
|
+
label: string;
|
|
15
|
+
color: "green" | "yellow" | "red" | "magenta";
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,YAAY,CAAC;AAWpD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,CAmB1D;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,SAAS,CAAC;CAC/C,CASA"}
|
package/dist/scoring.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score calculation engine.
|
|
3
|
+
*
|
|
4
|
+
* Formula:
|
|
5
|
+
* base = 100
|
|
6
|
+
* penalties: CRITICAL=-15, HIGH=-8, MEDIUM=-3, LOW=-1
|
|
7
|
+
* score = max(0, base - sum(penalties))
|
|
8
|
+
* Cap: any CRITICAL finding hard-caps score at 40
|
|
9
|
+
*/
|
|
10
|
+
import { Severity } from "./types.js";
|
|
11
|
+
const PENALTIES = {
|
|
12
|
+
[Severity.Critical]: 15,
|
|
13
|
+
[Severity.High]: 8,
|
|
14
|
+
[Severity.Medium]: 3,
|
|
15
|
+
[Severity.Low]: 1,
|
|
16
|
+
};
|
|
17
|
+
const CRITICAL_CAP = 40;
|
|
18
|
+
export function calculateScore(findings) {
|
|
19
|
+
const base = 100;
|
|
20
|
+
let totalPenalty = 0;
|
|
21
|
+
let hasCritical = false;
|
|
22
|
+
for (const finding of findings) {
|
|
23
|
+
totalPenalty += PENALTIES[finding.severity];
|
|
24
|
+
if (finding.severity === Severity.Critical) {
|
|
25
|
+
hasCritical = true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
let score = Math.max(0, base - totalPenalty);
|
|
29
|
+
if (hasCritical && score > CRITICAL_CAP) {
|
|
30
|
+
score = CRITICAL_CAP;
|
|
31
|
+
}
|
|
32
|
+
return score;
|
|
33
|
+
}
|
|
34
|
+
export function getScoreGrade(score) {
|
|
35
|
+
if (score >= 90)
|
|
36
|
+
return { grade: "A+", label: "Excellent", color: "green" };
|
|
37
|
+
if (score >= 80)
|
|
38
|
+
return { grade: "A", label: "Good", color: "green" };
|
|
39
|
+
if (score >= 70)
|
|
40
|
+
return { grade: "B", label: "Acceptable", color: "yellow" };
|
|
41
|
+
if (score >= 50)
|
|
42
|
+
return { grade: "C", label: "Needs Work", color: "yellow" };
|
|
43
|
+
if (score >= 30)
|
|
44
|
+
return { grade: "D", label: "Poor", color: "red" };
|
|
45
|
+
return { grade: "F", label: "Critical", color: "magenta" };
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.js","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,QAAQ,EAAgB,MAAM,YAAY,CAAC;AAEpD,MAAM,SAAS,GAA6B;IAC1C,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,EAAE;IACvB,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;IAClB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;IACpB,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;CAClB,CAAC;AAEF,MAAM,YAAY,GAAG,EAAE,CAAC;AAExB,MAAM,UAAU,cAAc,CAAC,QAAmB;IAChD,MAAM,IAAI,GAAG,GAAG,CAAC;IACjB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,WAAW,GAAG,KAAK,CAAC;IAExB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,YAAY,IAAI,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,CAAC,QAAQ,EAAE,CAAC;YAC3C,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;IAED,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,YAAY,CAAC,CAAC;IAE7C,IAAI,WAAW,IAAI,KAAK,GAAG,YAAY,EAAE,CAAC;QACxC,KAAK,GAAG,YAAY,CAAC;IACvB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IAKzC,IAAI,KAAK,IAAI,EAAE;QACb,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAC7D,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IACtE,IAAI,KAAK,IAAI,EAAE;QACb,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAC9D,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAC7E,IAAI,KAAK,IAAI,EAAE;QAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACpE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry module — anonymizes scan results and uploads threat reports.
|
|
3
|
+
* Only shares check IDs, severities, and categories. Never sends file paths,
|
|
4
|
+
* descriptions, or remediation text.
|
|
5
|
+
*/
|
|
6
|
+
import type { ScanResult, ThreatReport } from "./types.js";
|
|
7
|
+
/** Stable per-machine identifier (first 16 hex chars of SHA-256 of hostname:username). */
|
|
8
|
+
export declare function getInstanceId(): string;
|
|
9
|
+
/** Strip a ScanResult down to only safe-to-share metadata. */
|
|
10
|
+
export declare function anonymizeScanResult(result: ScanResult): ThreatReport;
|
|
11
|
+
/** POST a ThreatReport to the community API. Never throws. */
|
|
12
|
+
export declare function uploadThreatReport(report: ThreatReport, apiUrl: string): Promise<{
|
|
13
|
+
success: boolean;
|
|
14
|
+
error?: string;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE3D,0FAA0F;AAC1F,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AAED,8DAA8D;AAC9D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CAepE;AAED,8DAA8D;AAC9D,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAsB/C"}
|