@kairosinternational/watchman 0.1.2 → 0.2.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/dist/engine.d.ts +6 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +17 -1
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/telemetry.d.ts +38 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +70 -0
- package/dist/telemetry.js.map +1 -0
- package/kairosinternational-watchman-0.1.2.tgz +0 -0
- package/package.json +6 -1
- package/src/engine.ts +27 -1
- package/src/index.ts +8 -0
- package/src/telemetry.ts +124 -0
package/dist/engine.d.ts
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import type { WatchmanConfig, Scanner, WatchmanReport } from './types/index.js';
|
|
2
|
+
import type { TelemetryReporter } from './telemetry.js';
|
|
3
|
+
export interface ScanOptions {
|
|
4
|
+
telemetry?: TelemetryReporter;
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
}
|
|
2
7
|
export declare class WatchmanEngine {
|
|
3
8
|
private scanners;
|
|
4
9
|
private config;
|
|
5
10
|
constructor(config: WatchmanConfig);
|
|
6
11
|
register(scanner: Scanner): void;
|
|
7
|
-
scan(): Promise<WatchmanReport>;
|
|
12
|
+
scan(options?: ScanOptions): Promise<WatchmanReport>;
|
|
8
13
|
}
|
|
9
14
|
//# sourceMappingURL=engine.d.ts.map
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EAEd,OAAO,EAIP,cAAc,EAEf,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EAEd,OAAO,EAIP,cAAc,EAEf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAkDD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,EAAE,cAAc;IAIlC,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAI1B,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC;CA0E3D"}
|
package/dist/engine.js
CHANGED
|
@@ -49,10 +49,13 @@ class WatchmanEngine {
|
|
|
49
49
|
register(scanner) {
|
|
50
50
|
this.scanners.set(scanner.id, scanner);
|
|
51
51
|
}
|
|
52
|
-
async scan() {
|
|
52
|
+
async scan(options) {
|
|
53
53
|
const start = performance.now();
|
|
54
54
|
const files = await collectFiles(this.config.projectRoot, this.config.exclude ?? []);
|
|
55
55
|
const results = [];
|
|
56
|
+
const telemetry = options?.telemetry;
|
|
57
|
+
const sessionId = options?.sessionId;
|
|
58
|
+
const canReport = telemetry && sessionId;
|
|
56
59
|
for (const [id, scanner] of this.scanners) {
|
|
57
60
|
const scannerConfig = this.config.scanners[id];
|
|
58
61
|
if (!scannerConfig?.enabled)
|
|
@@ -73,6 +76,19 @@ class WatchmanEngine {
|
|
|
73
76
|
},
|
|
74
77
|
};
|
|
75
78
|
await scanner.scan(ctx);
|
|
79
|
+
// Fire-and-forget telemetry for each finding
|
|
80
|
+
if (canReport) {
|
|
81
|
+
for (const f of findings) {
|
|
82
|
+
telemetry.reportFinding({
|
|
83
|
+
scanner: f.scanner,
|
|
84
|
+
rule: f.rule,
|
|
85
|
+
severity: f.severity,
|
|
86
|
+
target: f.location?.file,
|
|
87
|
+
message: f.message,
|
|
88
|
+
evidence: f.evidence,
|
|
89
|
+
}, sessionId);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
76
92
|
results.push({
|
|
77
93
|
scanner: id,
|
|
78
94
|
findings,
|
package/dist/engine.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":";;;AAAA,+CAAiD;AACjD,yCAA2C;AAC3C,6CAAyC;
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { WatchmanEngine } from './engine.js';
|
|
2
|
+
export type { ScanOptions } from './engine.js';
|
|
3
|
+
export { createTelemetryReporter } from './telemetry.js';
|
|
4
|
+
export type { TelemetryConfig, TelemetryFinding, TelemetryReporter, StartSessionOptions, } from './telemetry.js';
|
|
2
5
|
export type { Severity, ScannerId, RuleConfig, ScannerConfig, WatchmanConfig, SourceLocation, Finding, ScanResult, WatchmanReport, ScanContext, Rule, Scanner, } from './types/index.js';
|
|
3
6
|
export { DEFAULT_CONFIG } from './types/index.js';
|
|
4
7
|
export { aiToolIntegrityScanner } from './scanners/ai-tool-integrity/index.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EACV,QAAQ,EACR,SAAS,EACT,UAAU,EACV,aAAa,EACb,cAAc,EACd,cAAc,EACd,OAAO,EACP,UAAU,EACV,cAAc,EACd,WAAW,EACX,IAAI,EACJ,OAAO,GACR,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAC/E,OAAO,EAAE,sBAAsB,EAAE,MAAM,sCAAsC,CAAC;AAC9E,OAAO,EAAE,0BAA0B,EAAE,MAAM,0CAA0C,CAAC;AACtF,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAG5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,+DAA+D,CAAC;AACrG,YAAY,EAAE,kBAAkB,EAAE,MAAM,+DAA+D,CAAC;AACxG,OAAO,EAAE,iBAAiB,EAAE,MAAM,2DAA2D,CAAC;AAC9F,YAAY,EAAE,eAAe,EAAE,MAAM,2DAA2D,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SECRET_SIGNATURES = exports.INJECTION_SIGNATURES = exports.runtimeMonitorScanner = exports.dependencyIntegrityScanner = exports.secretsExposureScanner = exports.aiToolIntegrityScanner = exports.DEFAULT_CONFIG = exports.WatchmanEngine = void 0;
|
|
3
|
+
exports.SECRET_SIGNATURES = exports.INJECTION_SIGNATURES = exports.runtimeMonitorScanner = exports.dependencyIntegrityScanner = exports.secretsExposureScanner = exports.aiToolIntegrityScanner = exports.DEFAULT_CONFIG = exports.createTelemetryReporter = exports.WatchmanEngine = void 0;
|
|
4
4
|
var engine_js_1 = require("./engine.js");
|
|
5
5
|
Object.defineProperty(exports, "WatchmanEngine", { enumerable: true, get: function () { return engine_js_1.WatchmanEngine; } });
|
|
6
|
+
var telemetry_js_1 = require("./telemetry.js");
|
|
7
|
+
Object.defineProperty(exports, "createTelemetryReporter", { enumerable: true, get: function () { return telemetry_js_1.createTelemetryReporter; } });
|
|
6
8
|
var index_js_1 = require("./types/index.js");
|
|
7
9
|
Object.defineProperty(exports, "DEFAULT_CONFIG", { enumerable: true, get: function () { return index_js_1.DEFAULT_CONFIG; } });
|
|
8
10
|
var index_js_2 = require("./scanners/ai-tool-integrity/index.js");
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAA6C;AAApC,2GAAA,cAAc,OAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,yCAA6C;AAApC,2GAAA,cAAc,OAAA;AAEvB,+CAAyD;AAAhD,uHAAA,uBAAuB,OAAA;AAsBhC,6CAAkD;AAAzC,0GAAA,cAAc,OAAA;AAEvB,kEAA+E;AAAtE,kHAAA,sBAAsB,OAAA;AAC/B,iEAA8E;AAArE,kHAAA,sBAAsB,OAAA;AAC/B,qEAAsF;AAA7E,sHAAA,0BAA0B,OAAA;AACnC,gEAA4E;AAAnE,iHAAA,qBAAqB,OAAA;AAE9B,4DAA4D;AAC5D,yGAAqG;AAA5F,+HAAA,oBAAoB,OAAA;AAE7B,kGAA8F;AAArF,yHAAA,iBAAiB,OAAA"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry reporter — fire-and-forget finding/session reporting.
|
|
3
|
+
*
|
|
4
|
+
* Edge Runtime compatible: uses only global fetch + AbortSignal.
|
|
5
|
+
* A failed telemetry call never delays or breaks the host app request.
|
|
6
|
+
*/
|
|
7
|
+
import type { Severity } from './types/config.types.js';
|
|
8
|
+
export interface TelemetryConfig {
|
|
9
|
+
/** Watchman API base URL, e.g. "https://watchman.guide" */
|
|
10
|
+
endpoint: string;
|
|
11
|
+
/** Tenant API key (raw, not hashed) */
|
|
12
|
+
apiKey: string;
|
|
13
|
+
}
|
|
14
|
+
/** Shape accepted by reportFinding — adapters map their finding type to this. */
|
|
15
|
+
export interface TelemetryFinding {
|
|
16
|
+
scanner: string;
|
|
17
|
+
rule: string;
|
|
18
|
+
severity: Severity;
|
|
19
|
+
target?: string;
|
|
20
|
+
label?: string;
|
|
21
|
+
message: string;
|
|
22
|
+
evidence?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface StartSessionOptions {
|
|
25
|
+
commitSha?: string;
|
|
26
|
+
branch?: string;
|
|
27
|
+
environment?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface TelemetryReporter {
|
|
30
|
+
/** Report a single finding to the Watchman API. Fire-and-forget. */
|
|
31
|
+
reportFinding: (finding: TelemetryFinding, sessionId: string) => void;
|
|
32
|
+
/** Open a scan session. Returns the session ID (or null on failure). */
|
|
33
|
+
startSession: (projectSlug: string, options?: StartSessionOptions) => Promise<string | null>;
|
|
34
|
+
/** Close a scan session. Fire-and-forget. */
|
|
35
|
+
endSession: (sessionId: string) => void;
|
|
36
|
+
}
|
|
37
|
+
export declare function createTelemetryReporter(config: TelemetryConfig): TelemetryReporter;
|
|
38
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAIxD,MAAM,WAAW,eAAe;IAC9B,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,CAAC;IACjB,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,iFAAiF;AACjF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,aAAa,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACtE,wEAAwE;IACxE,YAAY,EAAE,CACZ,WAAW,EAAE,MAAM,EACnB,OAAO,CAAC,EAAE,mBAAmB,KAC1B,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5B,6CAA6C;IAC7C,UAAU,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACzC;AAQD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,eAAe,GACtB,iBAAiB,CAoEnB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Telemetry reporter — fire-and-forget finding/session reporting.
|
|
4
|
+
*
|
|
5
|
+
* Edge Runtime compatible: uses only global fetch + AbortSignal.
|
|
6
|
+
* A failed telemetry call never delays or breaks the host app request.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createTelemetryReporter = createTelemetryReporter;
|
|
10
|
+
// ── Constants ───────────────────────────────────────────────────────
|
|
11
|
+
const TIMEOUT_MS = 5_000;
|
|
12
|
+
// ── Factory ─────────────────────────────────────────────────────────
|
|
13
|
+
function createTelemetryReporter(config) {
|
|
14
|
+
const { endpoint, apiKey } = config;
|
|
15
|
+
// Normalise base URL (strip trailing slash)
|
|
16
|
+
const base = endpoint.replace(/\/+$/, '');
|
|
17
|
+
/** POST helper — swallows all errors. */
|
|
18
|
+
async function post(path, body) {
|
|
19
|
+
try {
|
|
20
|
+
const res = await fetch(`${base}${path}`, {
|
|
21
|
+
method: 'POST',
|
|
22
|
+
headers: {
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
Authorization: `Bearer ${apiKey}`,
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify(body),
|
|
27
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
28
|
+
});
|
|
29
|
+
if (!res.ok)
|
|
30
|
+
return null;
|
|
31
|
+
return (await res.json());
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Network failure, timeout, JSON parse error — all swallowed.
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
// ── Public API ──────────────────────────────────────────────────
|
|
39
|
+
function reportFinding(finding, sessionId) {
|
|
40
|
+
// Fire-and-forget — caller does not await.
|
|
41
|
+
void post('/api/findings', {
|
|
42
|
+
session_id: sessionId,
|
|
43
|
+
scanner: finding.scanner,
|
|
44
|
+
rule: finding.rule,
|
|
45
|
+
severity: finding.severity,
|
|
46
|
+
target: finding.target ?? null,
|
|
47
|
+
label: finding.label ?? null,
|
|
48
|
+
details: {
|
|
49
|
+
message: finding.message,
|
|
50
|
+
evidence: finding.evidence ?? null,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function startSession(projectSlug, options) {
|
|
55
|
+
const result = await post('/api/sessions/start', {
|
|
56
|
+
project_slug: projectSlug,
|
|
57
|
+
commit_sha: options?.commitSha ?? null,
|
|
58
|
+
branch: options?.branch ?? null,
|
|
59
|
+
environment: options?.environment ?? null,
|
|
60
|
+
});
|
|
61
|
+
return result?.id ?? null;
|
|
62
|
+
}
|
|
63
|
+
function endSession(sessionId) {
|
|
64
|
+
void post('/api/sessions/end', {
|
|
65
|
+
session_id: sessionId,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return { reportFinding, startSession, endSession };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":";AAAA;;;;;GAKG;;AAgDH,0DAsEC;AA5ED,uEAAuE;AAEvE,MAAM,UAAU,GAAG,KAAK,CAAC;AAEzB,uEAAuE;AAEvE,SAAgB,uBAAuB,CACrC,MAAuB;IAEvB,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAEpC,4CAA4C;IAC5C,MAAM,IAAI,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAE1C,yCAAyC;IACzC,KAAK,UAAU,IAAI,CACjB,IAAY,EACZ,IAA6B;QAE7B,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE;gBACxC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,UAAU,MAAM,EAAE;iBAClC;gBACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAC1B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC;aACxC,CAAC,CAAC;YAEH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YACzB,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAM,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;YAC9D,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,mEAAmE;IAEnE,SAAS,aAAa,CAAC,OAAyB,EAAE,SAAiB;QACjE,2CAA2C;QAC3C,KAAK,IAAI,CAAC,eAAe,EAAE;YACzB,UAAU,EAAE,SAAS;YACrB,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,IAAI;YAC9B,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;YAC5B,OAAO,EAAE;gBACP,OAAO,EAAE,OAAO,CAAC,OAAO;gBACxB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,IAAI;aACnC;SACF,CAAC,CAAC;IACL,CAAC;IAED,KAAK,UAAU,YAAY,CACzB,WAAmB,EACnB,OAA6B;QAE7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAiB,qBAAqB,EAAE;YAC/D,YAAY,EAAE,WAAW;YACzB,UAAU,EAAE,OAAO,EAAE,SAAS,IAAI,IAAI;YACtC,MAAM,EAAE,OAAO,EAAE,MAAM,IAAI,IAAI;YAC/B,WAAW,EAAE,OAAO,EAAE,WAAW,IAAI,IAAI;SAC1C,CAAC,CAAC;QACH,OAAO,MAAM,EAAE,EAAE,IAAI,IAAI,CAAC;IAC5B,CAAC;IAED,SAAS,UAAU,CAAC,SAAiB;QACnC,KAAK,IAAI,CAAC,mBAAmB,EAAE;YAC7B,UAAU,EAAE,SAAS;SACtB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;AACrD,CAAC"}
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kairosinternational/watchman",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Developer security engine — silent threat detection",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"types": "./dist/patterns.d.ts",
|
|
15
15
|
"import": "./dist/patterns.js",
|
|
16
16
|
"default": "./dist/patterns.js"
|
|
17
|
+
},
|
|
18
|
+
"./telemetry": {
|
|
19
|
+
"types": "./dist/telemetry.d.ts",
|
|
20
|
+
"import": "./dist/telemetry.js",
|
|
21
|
+
"default": "./dist/telemetry.js"
|
|
17
22
|
}
|
|
18
23
|
},
|
|
19
24
|
"scripts": {
|
package/src/engine.ts
CHANGED
|
@@ -11,6 +11,12 @@ import type {
|
|
|
11
11
|
WatchmanReport,
|
|
12
12
|
Severity,
|
|
13
13
|
} from './types/index.js';
|
|
14
|
+
import type { TelemetryReporter } from './telemetry.js';
|
|
15
|
+
|
|
16
|
+
export interface ScanOptions {
|
|
17
|
+
telemetry?: TelemetryReporter;
|
|
18
|
+
sessionId?: string;
|
|
19
|
+
}
|
|
14
20
|
|
|
15
21
|
const SEVERITY_ORDER: Record<Severity, number> = {
|
|
16
22
|
critical: 4,
|
|
@@ -72,13 +78,16 @@ export class WatchmanEngine {
|
|
|
72
78
|
this.scanners.set(scanner.id, scanner);
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
async scan(): Promise<WatchmanReport> {
|
|
81
|
+
async scan(options?: ScanOptions): Promise<WatchmanReport> {
|
|
76
82
|
const start = performance.now();
|
|
77
83
|
const files = await collectFiles(
|
|
78
84
|
this.config.projectRoot,
|
|
79
85
|
this.config.exclude ?? [],
|
|
80
86
|
);
|
|
81
87
|
const results: ScanResult[] = [];
|
|
88
|
+
const telemetry = options?.telemetry;
|
|
89
|
+
const sessionId = options?.sessionId;
|
|
90
|
+
const canReport = telemetry && sessionId;
|
|
82
91
|
|
|
83
92
|
for (const [id, scanner] of this.scanners) {
|
|
84
93
|
const scannerConfig = this.config.scanners[id];
|
|
@@ -103,6 +112,23 @@ export class WatchmanEngine {
|
|
|
103
112
|
|
|
104
113
|
await scanner.scan(ctx);
|
|
105
114
|
|
|
115
|
+
// Fire-and-forget telemetry for each finding
|
|
116
|
+
if (canReport) {
|
|
117
|
+
for (const f of findings) {
|
|
118
|
+
telemetry.reportFinding(
|
|
119
|
+
{
|
|
120
|
+
scanner: f.scanner,
|
|
121
|
+
rule: f.rule,
|
|
122
|
+
severity: f.severity,
|
|
123
|
+
target: f.location?.file,
|
|
124
|
+
message: f.message,
|
|
125
|
+
evidence: f.evidence,
|
|
126
|
+
},
|
|
127
|
+
sessionId,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
106
132
|
results.push({
|
|
107
133
|
scanner: id,
|
|
108
134
|
findings,
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
export { WatchmanEngine } from './engine.js';
|
|
2
|
+
export type { ScanOptions } from './engine.js';
|
|
3
|
+
export { createTelemetryReporter } from './telemetry.js';
|
|
4
|
+
export type {
|
|
5
|
+
TelemetryConfig,
|
|
6
|
+
TelemetryFinding,
|
|
7
|
+
TelemetryReporter,
|
|
8
|
+
StartSessionOptions,
|
|
9
|
+
} from './telemetry.js';
|
|
2
10
|
|
|
3
11
|
export type {
|
|
4
12
|
Severity,
|
package/src/telemetry.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telemetry reporter — fire-and-forget finding/session reporting.
|
|
3
|
+
*
|
|
4
|
+
* Edge Runtime compatible: uses only global fetch + AbortSignal.
|
|
5
|
+
* A failed telemetry call never delays or breaks the host app request.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Severity } from './types/config.types.js';
|
|
9
|
+
|
|
10
|
+
// ── Types ───────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface TelemetryConfig {
|
|
13
|
+
/** Watchman API base URL, e.g. "https://watchman.guide" */
|
|
14
|
+
endpoint: string;
|
|
15
|
+
/** Tenant API key (raw, not hashed) */
|
|
16
|
+
apiKey: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Shape accepted by reportFinding — adapters map their finding type to this. */
|
|
20
|
+
export interface TelemetryFinding {
|
|
21
|
+
scanner: string;
|
|
22
|
+
rule: string;
|
|
23
|
+
severity: Severity;
|
|
24
|
+
target?: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
message: string;
|
|
27
|
+
evidence?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface StartSessionOptions {
|
|
31
|
+
commitSha?: string;
|
|
32
|
+
branch?: string;
|
|
33
|
+
environment?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface TelemetryReporter {
|
|
37
|
+
/** Report a single finding to the Watchman API. Fire-and-forget. */
|
|
38
|
+
reportFinding: (finding: TelemetryFinding, sessionId: string) => void;
|
|
39
|
+
/** Open a scan session. Returns the session ID (or null on failure). */
|
|
40
|
+
startSession: (
|
|
41
|
+
projectSlug: string,
|
|
42
|
+
options?: StartSessionOptions,
|
|
43
|
+
) => Promise<string | null>;
|
|
44
|
+
/** Close a scan session. Fire-and-forget. */
|
|
45
|
+
endSession: (sessionId: string) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ── Constants ───────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
const TIMEOUT_MS = 5_000;
|
|
51
|
+
|
|
52
|
+
// ── Factory ─────────────────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
export function createTelemetryReporter(
|
|
55
|
+
config: TelemetryConfig,
|
|
56
|
+
): TelemetryReporter {
|
|
57
|
+
const { endpoint, apiKey } = config;
|
|
58
|
+
|
|
59
|
+
// Normalise base URL (strip trailing slash)
|
|
60
|
+
const base = endpoint.replace(/\/+$/, '');
|
|
61
|
+
|
|
62
|
+
/** POST helper — swallows all errors. */
|
|
63
|
+
async function post<T = unknown>(
|
|
64
|
+
path: string,
|
|
65
|
+
body: Record<string, unknown>,
|
|
66
|
+
): Promise<T | null> {
|
|
67
|
+
try {
|
|
68
|
+
const res = await fetch(`${base}${path}`, {
|
|
69
|
+
method: 'POST',
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': 'application/json',
|
|
72
|
+
Authorization: `Bearer ${apiKey}`,
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify(body),
|
|
75
|
+
signal: AbortSignal.timeout(TIMEOUT_MS),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!res.ok) return null;
|
|
79
|
+
return (await res.json()) as T;
|
|
80
|
+
} catch {
|
|
81
|
+
// Network failure, timeout, JSON parse error — all swallowed.
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Public API ──────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
function reportFinding(finding: TelemetryFinding, sessionId: string): void {
|
|
89
|
+
// Fire-and-forget — caller does not await.
|
|
90
|
+
void post('/api/findings', {
|
|
91
|
+
session_id: sessionId,
|
|
92
|
+
scanner: finding.scanner,
|
|
93
|
+
rule: finding.rule,
|
|
94
|
+
severity: finding.severity,
|
|
95
|
+
target: finding.target ?? null,
|
|
96
|
+
label: finding.label ?? null,
|
|
97
|
+
details: {
|
|
98
|
+
message: finding.message,
|
|
99
|
+
evidence: finding.evidence ?? null,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function startSession(
|
|
105
|
+
projectSlug: string,
|
|
106
|
+
options?: StartSessionOptions,
|
|
107
|
+
): Promise<string | null> {
|
|
108
|
+
const result = await post<{ id: string }>('/api/sessions/start', {
|
|
109
|
+
project_slug: projectSlug,
|
|
110
|
+
commit_sha: options?.commitSha ?? null,
|
|
111
|
+
branch: options?.branch ?? null,
|
|
112
|
+
environment: options?.environment ?? null,
|
|
113
|
+
});
|
|
114
|
+
return result?.id ?? null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function endSession(sessionId: string): void {
|
|
118
|
+
void post('/api/sessions/end', {
|
|
119
|
+
session_id: sessionId,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return { reportFinding, startSession, endSession };
|
|
124
|
+
}
|