@telora/daemon 0.15.42 → 0.15.47
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/build-info.json +2 -2
- package/dist/focus-completion.d.ts.map +1 -1
- package/dist/focus-completion.js +21 -6
- package/dist/focus-completion.js.map +1 -1
- package/dist/focus-engine.d.ts.map +1 -1
- package/dist/focus-engine.js +9 -9
- package/dist/focus-engine.js.map +1 -1
- package/dist/focus-merge.d.ts.map +1 -1
- package/dist/focus-merge.js +2 -0
- package/dist/focus-merge.js.map +1 -1
- package/dist/security-auto-inject.d.ts +17 -32
- package/dist/security-auto-inject.d.ts.map +1 -1
- package/dist/security-auto-inject.js +13 -49
- package/dist/security-auto-inject.js.map +1 -1
- package/dist/security-finding-gate.d.ts +74 -0
- package/dist/security-finding-gate.d.ts.map +1 -0
- package/dist/security-finding-gate.js +82 -0
- package/dist/security-finding-gate.js.map +1 -0
- package/dist/security-rescan-resolution.d.ts +1 -27
- package/dist/security-rescan-resolution.d.ts.map +1 -1
- package/dist/security-rescan-resolution.js +1 -38
- package/dist/security-rescan-resolution.js.map +1 -1
- package/dist/security-scan-engine.d.ts +37 -24
- package/dist/security-scan-engine.d.ts.map +1 -1
- package/dist/security-scan-engine.js +41 -72
- package/dist/security-scan-engine.js.map +1 -1
- package/dist/spawner-lifecycle.d.ts +2 -0
- package/dist/spawner-lifecycle.d.ts.map +1 -1
- package/dist/spawner-lifecycle.js +3 -2
- package/dist/spawner-lifecycle.js.map +1 -1
- package/dist/team-prompt-base.d.ts.map +1 -1
- package/dist/team-prompt-base.js +22 -0
- package/dist/team-prompt-base.js.map +1 -1
- package/dist/verification-engine.d.ts +9 -0
- package/dist/verification-engine.d.ts.map +1 -1
- package/dist/verification-engine.js +29 -3
- package/dist/verification-engine.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic check evaluator for the Security workflow verify-closed gate.
|
|
3
|
+
*
|
|
4
|
+
* The Security workflow declares its verify-closed stage with
|
|
5
|
+
* `agent_directive.gate = { kind: 'deterministic', check:
|
|
6
|
+
* 'security.finding_no_longer_present' }`. The gate's contract: a security
|
|
7
|
+
* remediation injection cannot be verified (retired) unless the linked
|
|
8
|
+
* security_findings row has flipped to status='resolved' (the re-scan
|
|
9
|
+
* resolution sweep clears the indicator).
|
|
10
|
+
*
|
|
11
|
+
* If the gate fails, the linked finding is flipped to status='escalated'
|
|
12
|
+
* with escalation_reason='verify_gate_failed'. After the D1 schema
|
|
13
|
+
* collapse, the Security workflow no longer carries a 'Reopen (failed)'
|
|
14
|
+
* transition — a failed verify-closed escalates and stays.
|
|
15
|
+
*
|
|
16
|
+
* Wired into verification-engine.ts: before calling verifyInjectionOnPass
|
|
17
|
+
* on a 'passed' outcome, the engine consults this gate. A 'passed' outcome
|
|
18
|
+
* is demoted to 'failed' when the finding is still open/remediating.
|
|
19
|
+
*
|
|
20
|
+
* @module security-finding-gate
|
|
21
|
+
*/
|
|
22
|
+
export type FindingStatusForGate = 'open' | 'remediating' | 'resolved' | 'escalated';
|
|
23
|
+
export interface FindingLookupResult {
|
|
24
|
+
findingId: string;
|
|
25
|
+
status: FindingStatusForGate;
|
|
26
|
+
}
|
|
27
|
+
export interface SecurityFindingGateDeps {
|
|
28
|
+
/**
|
|
29
|
+
* Look up the security_findings row whose linked_injection_id matches the
|
|
30
|
+
* given injection node id. Returns null when no finding is linked
|
|
31
|
+
* (non-security injection -- gate is a no-op).
|
|
32
|
+
*/
|
|
33
|
+
lookupFindingByInjection: (injectionNodeId: string) => Promise<FindingLookupResult | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Flip the finding to status='escalated' and append a security_finding_audit
|
|
36
|
+
* row with action='escalated'. Idempotent at the DB layer.
|
|
37
|
+
*/
|
|
38
|
+
escalateFinding: (findingId: string, reason: string) => Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
export type SecurityFindingGateResult = {
|
|
41
|
+
passed: true;
|
|
42
|
+
findingId: null;
|
|
43
|
+
reason?: string;
|
|
44
|
+
} | {
|
|
45
|
+
passed: true;
|
|
46
|
+
findingId: string;
|
|
47
|
+
reason?: string;
|
|
48
|
+
} | {
|
|
49
|
+
passed: false;
|
|
50
|
+
findingId: string;
|
|
51
|
+
reason: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Evaluate the `security.finding_no_longer_present` deterministic check for
|
|
55
|
+
* a given injection node.
|
|
56
|
+
*
|
|
57
|
+
* Outcomes:
|
|
58
|
+
* - No finding linked to this injection -> passes as a no-op.
|
|
59
|
+
* - Linked finding has status 'resolved' -> passes.
|
|
60
|
+
* - Linked finding has any other status -> fails, returns the findingId
|
|
61
|
+
* so the caller can escalate.
|
|
62
|
+
*
|
|
63
|
+
* The function does NOT mutate any state -- escalation is the caller's
|
|
64
|
+
* responsibility via `escalateOnGateFail`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function evaluateFindingNoLongerPresent(injectionNodeId: string, deps: SecurityFindingGateDeps): Promise<SecurityFindingGateResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Best-effort escalation helper: when the gate fails, flip the linked
|
|
69
|
+
* finding to status='escalated'. Logs and swallows on error so a failed
|
|
70
|
+
* escalation does not throw out of the verification path.
|
|
71
|
+
*/
|
|
72
|
+
export declare function escalateOnGateFail(findingId: string, reason: string, deps: SecurityFindingGateDeps): Promise<void>;
|
|
73
|
+
export declare function buildDefaultSecurityFindingGateDeps(): SecurityFindingGateDeps;
|
|
74
|
+
//# sourceMappingURL=security-finding-gate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-finding-gate.d.ts","sourceRoot":"","sources":["../src/security-finding-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAQH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,aAAa,GAAG,UAAU,GAAG,WAAW,CAAC;AAErF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,wBAAwB,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,OAAO,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;IAC3F;;;OAGG;IACH,eAAe,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE;AAED,MAAM,MAAM,yBAAyB,GACjC;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,IAAI,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAClD;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAMzD;;;;;;;;;;;;GAYG;AACH,wBAAsB,8BAA8B,CAClD,eAAe,EAAE,MAAM,EACvB,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,yBAAyB,CAAC,CAapC;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,uBAAuB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAQf;AAMD,wBAAgB,mCAAmC,IAAI,uBAAuB,CAc7E"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic check evaluator for the Security workflow verify-closed gate.
|
|
3
|
+
*
|
|
4
|
+
* The Security workflow declares its verify-closed stage with
|
|
5
|
+
* `agent_directive.gate = { kind: 'deterministic', check:
|
|
6
|
+
* 'security.finding_no_longer_present' }`. The gate's contract: a security
|
|
7
|
+
* remediation injection cannot be verified (retired) unless the linked
|
|
8
|
+
* security_findings row has flipped to status='resolved' (the re-scan
|
|
9
|
+
* resolution sweep clears the indicator).
|
|
10
|
+
*
|
|
11
|
+
* If the gate fails, the linked finding is flipped to status='escalated'
|
|
12
|
+
* with escalation_reason='verify_gate_failed'. After the D1 schema
|
|
13
|
+
* collapse, the Security workflow no longer carries a 'Reopen (failed)'
|
|
14
|
+
* transition — a failed verify-closed escalates and stays.
|
|
15
|
+
*
|
|
16
|
+
* Wired into verification-engine.ts: before calling verifyInjectionOnPass
|
|
17
|
+
* on a 'passed' outcome, the engine consults this gate. A 'passed' outcome
|
|
18
|
+
* is demoted to 'failed' when the finding is still open/remediating.
|
|
19
|
+
*
|
|
20
|
+
* @module security-finding-gate
|
|
21
|
+
*/
|
|
22
|
+
import { callApi } from './queries/shared.js';
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Pure evaluator
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* Evaluate the `security.finding_no_longer_present` deterministic check for
|
|
28
|
+
* a given injection node.
|
|
29
|
+
*
|
|
30
|
+
* Outcomes:
|
|
31
|
+
* - No finding linked to this injection -> passes as a no-op.
|
|
32
|
+
* - Linked finding has status 'resolved' -> passes.
|
|
33
|
+
* - Linked finding has any other status -> fails, returns the findingId
|
|
34
|
+
* so the caller can escalate.
|
|
35
|
+
*
|
|
36
|
+
* The function does NOT mutate any state -- escalation is the caller's
|
|
37
|
+
* responsibility via `escalateOnGateFail`.
|
|
38
|
+
*/
|
|
39
|
+
export async function evaluateFindingNoLongerPresent(injectionNodeId, deps) {
|
|
40
|
+
const lookup = await deps.lookupFindingByInjection(injectionNodeId);
|
|
41
|
+
if (!lookup) {
|
|
42
|
+
return { passed: true, findingId: null, reason: 'no_linked_finding' };
|
|
43
|
+
}
|
|
44
|
+
if (lookup.status === 'resolved') {
|
|
45
|
+
return { passed: true, findingId: lookup.findingId, reason: 'finding_resolved' };
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
passed: false,
|
|
49
|
+
findingId: lookup.findingId,
|
|
50
|
+
reason: `finding_status_is_${lookup.status}`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Best-effort escalation helper: when the gate fails, flip the linked
|
|
55
|
+
* finding to status='escalated'. Logs and swallows on error so a failed
|
|
56
|
+
* escalation does not throw out of the verification path.
|
|
57
|
+
*/
|
|
58
|
+
export async function escalateOnGateFail(findingId, reason, deps) {
|
|
59
|
+
try {
|
|
60
|
+
await deps.escalateFinding(findingId, reason);
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
console.warn(`[security-finding-gate] escalation failed for finding ${findingId}: ${err.message}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Default deps -- daemon-side wiring via callApi
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
export function buildDefaultSecurityFindingGateDeps() {
|
|
70
|
+
return {
|
|
71
|
+
lookupFindingByInjection: async (injectionNodeId) => {
|
|
72
|
+
const res = await callApi('daemon_lookup_finding_by_injection', { injectionNodeId });
|
|
73
|
+
if (!res?.findingId || !res.status)
|
|
74
|
+
return null;
|
|
75
|
+
return { findingId: res.findingId, status: res.status };
|
|
76
|
+
},
|
|
77
|
+
escalateFinding: async (findingId, reason) => {
|
|
78
|
+
await callApi('daemon_escalate_security_finding', { findingId, reason });
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=security-finding-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-finding-gate.js","sourceRoot":"","sources":["../src/security-finding-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAgC9C,8EAA8E;AAC9E,iBAAiB;AACjB,8EAA8E;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,8BAA8B,CAClD,eAAuB,EACvB,IAA6B;IAE7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,wBAAwB,CAAC,eAAe,CAAC,CAAC;IACpE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;IACxE,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACnF,CAAC;IACD,OAAO;QACL,MAAM,EAAE,KAAK;QACb,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,MAAM,EAAE,qBAAqB,MAAM,CAAC,MAAM,EAAE;KAC7C,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAiB,EACjB,MAAc,EACd,IAA6B;IAE7B,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CACV,yDAAyD,SAAS,KAAM,GAAa,CAAC,OAAO,EAAE,CAChG,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,iDAAiD;AACjD,8EAA8E;AAE9E,MAAM,UAAU,mCAAmC;IACjD,OAAO;QACL,wBAAwB,EAAE,KAAK,EAAE,eAAe,EAAE,EAAE;YAClD,MAAM,GAAG,GAAG,MAAM,OAAO,CACvB,oCAAoC,EACpC,EAAE,eAAe,EAAE,CACpB,CAAC;YACF,IAAI,CAAC,GAAG,EAAE,SAAS,IAAI,CAAC,GAAG,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC;YAChD,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC;QAC1D,CAAC;QACD,eAAe,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE;YAC3C,MAAM,OAAO,CAAC,kCAAkC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;QAC3E,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* auto-verify the injection.
|
|
6
6
|
*
|
|
7
7
|
* Called by the scanner engine immediately after a run finishes writing
|
|
8
|
-
* its findings, before
|
|
9
|
-
* tick begins.
|
|
8
|
+
* its findings, before the next tick begins.
|
|
10
9
|
*
|
|
11
10
|
* @module security-rescan-resolution
|
|
12
11
|
*/
|
|
@@ -56,29 +55,4 @@ export interface ScanRunFindingSet {
|
|
|
56
55
|
*/
|
|
57
56
|
export declare function resolveStaleFindings(productId: string, observedSets: ScanRunFindingSet[], deps: ResolutionDeps): Promise<string[]>;
|
|
58
57
|
export declare function buildDefaultResolutionDeps(): ResolutionDeps;
|
|
59
|
-
export interface SuppressionExpirySweepDeps {
|
|
60
|
-
/** Returns findings where status='suppressed' and suppression.expires_at < now(). */
|
|
61
|
-
listExpiredSuppressions: () => Promise<Array<{
|
|
62
|
-
id: string;
|
|
63
|
-
organizationId: string;
|
|
64
|
-
}>>;
|
|
65
|
-
/** Set status='open', suppression=null. */
|
|
66
|
-
unsuppressFinding: (findingId: string) => Promise<void>;
|
|
67
|
-
/** Append audit row with action='unsuppressed' and reason='suppression_expired'. */
|
|
68
|
-
writeAudit: (input: {
|
|
69
|
-
findingId: string;
|
|
70
|
-
organizationId: string;
|
|
71
|
-
action: 'unsuppressed';
|
|
72
|
-
payload: Record<string, unknown>;
|
|
73
|
-
}) => Promise<void>;
|
|
74
|
-
}
|
|
75
|
-
/**
|
|
76
|
-
* Sweep suppressions whose expires_at has passed and flip them back to
|
|
77
|
-
* 'open'. Idempotent: a suppression already expired is a no-op on the
|
|
78
|
-
* second call because the predicate now matches status='open'.
|
|
79
|
-
*
|
|
80
|
-
* @returns the finding ids whose suppression was lifted.
|
|
81
|
-
*/
|
|
82
|
-
export declare function runSuppressionExpirySweep(deps: SuppressionExpirySweepDeps): Promise<string[]>;
|
|
83
|
-
export declare function buildDefaultSuppressionExpirySweepDeps(): SuppressionExpirySweepDeps;
|
|
84
58
|
//# sourceMappingURL=security-rescan-resolution.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-rescan-resolution.d.ts","sourceRoot":"","sources":["../src/security-rescan-resolution.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"security-rescan-resolution.d.ts","sourceRoot":"","sources":["../src/security-rescan-resolution.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IACzF,8DAA8D;IAC9D,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,2CAA2C;IAC3C,UAAU,EAAE,CAAC,KAAK,EAAE;QAClB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;QACvB,MAAM,EAAE,UAAU,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAClC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB;;;OAGG;IACH,0BAA0B,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAChF,uEAAuE;IACvE,eAAe,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,iBAAiB,EAAE,EACjC,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,MAAM,EAAE,CAAC,CAqCnB;AAMD,wBAAgB,0BAA0B,IAAI,cAAc,CA0B3D"}
|
|
@@ -5,8 +5,7 @@
|
|
|
5
5
|
* auto-verify the injection.
|
|
6
6
|
*
|
|
7
7
|
* Called by the scanner engine immediately after a run finishes writing
|
|
8
|
-
* its findings, before
|
|
9
|
-
* tick begins.
|
|
8
|
+
* its findings, before the next tick begins.
|
|
10
9
|
*
|
|
11
10
|
* @module security-rescan-resolution
|
|
12
11
|
*/
|
|
@@ -75,40 +74,4 @@ export function buildDefaultResolutionDeps() {
|
|
|
75
74
|
},
|
|
76
75
|
};
|
|
77
76
|
}
|
|
78
|
-
/**
|
|
79
|
-
* Sweep suppressions whose expires_at has passed and flip them back to
|
|
80
|
-
* 'open'. Idempotent: a suppression already expired is a no-op on the
|
|
81
|
-
* second call because the predicate now matches status='open'.
|
|
82
|
-
*
|
|
83
|
-
* @returns the finding ids whose suppression was lifted.
|
|
84
|
-
*/
|
|
85
|
-
export async function runSuppressionExpirySweep(deps) {
|
|
86
|
-
const expired = await deps.listExpiredSuppressions();
|
|
87
|
-
const lifted = [];
|
|
88
|
-
for (const finding of expired) {
|
|
89
|
-
await deps.unsuppressFinding(finding.id);
|
|
90
|
-
await deps.writeAudit({
|
|
91
|
-
findingId: finding.id,
|
|
92
|
-
organizationId: finding.organizationId,
|
|
93
|
-
action: 'unsuppressed',
|
|
94
|
-
payload: { reason: 'suppression_expired' },
|
|
95
|
-
});
|
|
96
|
-
lifted.push(finding.id);
|
|
97
|
-
}
|
|
98
|
-
return lifted;
|
|
99
|
-
}
|
|
100
|
-
export function buildDefaultSuppressionExpirySweepDeps() {
|
|
101
|
-
return {
|
|
102
|
-
listExpiredSuppressions: async () => {
|
|
103
|
-
const res = await callApi('daemon_list_expired_security_suppressions', {});
|
|
104
|
-
return res.items ?? [];
|
|
105
|
-
},
|
|
106
|
-
unsuppressFinding: async (findingId) => {
|
|
107
|
-
await callApi('daemon_unsuppress_security_finding', { findingId });
|
|
108
|
-
},
|
|
109
|
-
writeAudit: async (input) => {
|
|
110
|
-
await callApi('daemon_write_security_finding_audit', input);
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
77
|
//# sourceMappingURL=security-rescan-resolution.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-rescan-resolution.js","sourceRoot":"","sources":["../src/security-rescan-resolution.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"security-rescan-resolution.js","sourceRoot":"","sources":["../src/security-rescan-resolution.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,24 +2,27 @@
|
|
|
2
2
|
* Security scan engine.
|
|
3
3
|
*
|
|
4
4
|
* Polls security_scan_configs for due scans (cron-due or manual_run_requested_at
|
|
5
|
-
* set), dispatches pluggable Scanner implementations per IOC class,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* set), dispatches pluggable Scanner implementations per IOC class, upserts
|
|
6
|
+
* security_findings rows (one per product/ioc_class/identifier), and hands
|
|
7
|
+
* any newly-observed or unremediated-open finding to security-auto-inject
|
|
8
|
+
* for remediation materialization.
|
|
9
|
+
*
|
|
10
|
+
* No longer writes scan-run rows -- the current-state model only persists
|
|
11
|
+
* findings. `last_run_at` on the config is updated directly at the end of
|
|
12
|
+
* runScanForConfig.
|
|
9
13
|
*
|
|
10
14
|
* Activation: gated by shouldRunLoop('TELORA_SECURITY_SCAN_LOOP') in
|
|
11
15
|
* unified-shell.ts. Opt-out semantics match the other daemon loop ticks
|
|
12
16
|
* (unset/anything-but-'0' = enabled, '0' = disabled). See
|
|
13
17
|
* docs/runbook-loop-activation.md.
|
|
14
18
|
*
|
|
15
|
-
* Pattern reference: verification-engine.ts (pluggable strategies + Deps).
|
|
16
|
-
*
|
|
17
19
|
* @module security-scan-engine
|
|
18
20
|
*/
|
|
19
21
|
import type { DaemonConfig } from './types.js';
|
|
20
22
|
import { type AutoInjectDeps } from './security-auto-inject.js';
|
|
21
23
|
import { type ResolutionDeps } from './security-rescan-resolution.js';
|
|
22
24
|
export type Severity = 'low' | 'medium' | 'high' | 'critical';
|
|
25
|
+
export type FindingStatus = 'open' | 'remediating' | 'resolved' | 'escalated';
|
|
23
26
|
/** Configuration row driving an individual product's scan cadence. */
|
|
24
27
|
export interface ScanConfig {
|
|
25
28
|
id: string;
|
|
@@ -27,7 +30,6 @@ export interface ScanConfig {
|
|
|
27
30
|
productId: string;
|
|
28
31
|
scheduleCron: string;
|
|
29
32
|
enabledIocClasses: string[];
|
|
30
|
-
autoInjectSeverityThreshold: Severity;
|
|
31
33
|
enabled: boolean;
|
|
32
34
|
manualRunRequestedAt: string | null;
|
|
33
35
|
lastRunAt: string | null;
|
|
@@ -58,22 +60,32 @@ export interface Scanner {
|
|
|
58
60
|
}
|
|
59
61
|
export declare function registerScanner(scanner: Scanner): void;
|
|
60
62
|
export declare function getRegisteredScanners(): Scanner[];
|
|
63
|
+
export interface UpsertFindingResult {
|
|
64
|
+
findingId: string;
|
|
65
|
+
/** True when this row was just inserted (vs an existing row was updated). */
|
|
66
|
+
wasNew: boolean;
|
|
67
|
+
status: FindingStatus;
|
|
68
|
+
linkedInjectionId: string | null;
|
|
69
|
+
}
|
|
70
|
+
/** Options that further narrow which due configs the tick should pick up. */
|
|
71
|
+
export interface DueScanConfigsOptions {
|
|
72
|
+
/**
|
|
73
|
+
* When true, return only configs with a pending manual-run request and
|
|
74
|
+
* skip cron evaluation entirely. Used by the fast (30s) request-drain
|
|
75
|
+
* loop so the click-to-run latency matches other queued-work pickups.
|
|
76
|
+
*/
|
|
77
|
+
manualOnly?: boolean;
|
|
78
|
+
}
|
|
61
79
|
export interface SecurityScanDeps {
|
|
62
|
-
getDueScanConfigs: () => Promise<ScanConfig[]>;
|
|
63
|
-
startRun: (configId: string, trigger: 'schedule' | 'manual') => Promise<string>;
|
|
64
|
-
finishRun: (runId: string, update: {
|
|
65
|
-
status: 'succeeded' | 'failed' | 'partial';
|
|
66
|
-
coverageSummary: Record<string, unknown>;
|
|
67
|
-
findingsCountBySeverity: Record<Severity, number>;
|
|
68
|
-
durationMs: number;
|
|
69
|
-
}) => Promise<void>;
|
|
80
|
+
getDueScanConfigs: (opts?: DueScanConfigsOptions) => Promise<ScanConfig[]>;
|
|
70
81
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
82
|
+
* Insert or update one row of security_findings. Conflict on
|
|
83
|
+
* (product_id, ioc_class, identifier) updates last_seen_at, severity,
|
|
84
|
+
* and payload. first_seen_at is set on insert only.
|
|
73
85
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
upsertFinding: (productId: string, organizationId: string, finding: FindingDraft) => Promise<UpsertFindingResult>;
|
|
87
|
+
/** Stamp last_run_at on the config after a scan completes. */
|
|
88
|
+
updateConfigLastRun: (configId: string) => Promise<void>;
|
|
77
89
|
clearManualRunRequest: (configId: string) => Promise<void>;
|
|
78
90
|
resolveCwd: (productId: string) => string;
|
|
79
91
|
/**
|
|
@@ -84,9 +96,10 @@ export interface SecurityScanDeps {
|
|
|
84
96
|
clearFeedCaches: () => void;
|
|
85
97
|
scanners: Scanner[];
|
|
86
98
|
/**
|
|
87
|
-
* Optional
|
|
88
|
-
*
|
|
89
|
-
*
|
|
99
|
+
* Optional auto-injection hook. When set, each new finding (or
|
|
100
|
+
* existing open finding with no remediation) is passed to
|
|
101
|
+
* processNewFinding so the daemon can materialize a remediation
|
|
102
|
+
* injection + delivery for it.
|
|
90
103
|
*/
|
|
91
104
|
autoInjectDeps?: AutoInjectDeps;
|
|
92
105
|
/**
|
|
@@ -97,6 +110,6 @@ export interface SecurityScanDeps {
|
|
|
97
110
|
resolutionDeps?: ResolutionDeps;
|
|
98
111
|
}
|
|
99
112
|
export declare function runScanForConfig(config: ScanConfig, trigger: 'schedule' | 'manual', deps: SecurityScanDeps): Promise<void>;
|
|
100
|
-
export declare function runSecurityScanTick(deps: SecurityScanDeps): Promise<void>;
|
|
113
|
+
export declare function runSecurityScanTick(deps: SecurityScanDeps, opts?: DueScanConfigsOptions): Promise<void>;
|
|
101
114
|
export declare function buildDefaultSecurityScanDeps(config: DaemonConfig): SecurityScanDeps;
|
|
102
115
|
//# sourceMappingURL=security-scan-engine.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-scan-engine.d.ts","sourceRoot":"","sources":["../src/security-scan-engine.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"security-scan-engine.d.ts","sourceRoot":"","sources":["../src/security-scan-engine.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,18 +2,20 @@
|
|
|
2
2
|
* Security scan engine.
|
|
3
3
|
*
|
|
4
4
|
* Polls security_scan_configs for due scans (cron-due or manual_run_requested_at
|
|
5
|
-
* set), dispatches pluggable Scanner implementations per IOC class,
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* set), dispatches pluggable Scanner implementations per IOC class, upserts
|
|
6
|
+
* security_findings rows (one per product/ioc_class/identifier), and hands
|
|
7
|
+
* any newly-observed or unremediated-open finding to security-auto-inject
|
|
8
|
+
* for remediation materialization.
|
|
9
|
+
*
|
|
10
|
+
* No longer writes scan-run rows -- the current-state model only persists
|
|
11
|
+
* findings. `last_run_at` on the config is updated directly at the end of
|
|
12
|
+
* runScanForConfig.
|
|
9
13
|
*
|
|
10
14
|
* Activation: gated by shouldRunLoop('TELORA_SECURITY_SCAN_LOOP') in
|
|
11
15
|
* unified-shell.ts. Opt-out semantics match the other daemon loop ticks
|
|
12
16
|
* (unset/anything-but-'0' = enabled, '0' = disabled). See
|
|
13
17
|
* docs/runbook-loop-activation.md.
|
|
14
18
|
*
|
|
15
|
-
* Pattern reference: verification-engine.ts (pluggable strategies + Deps).
|
|
16
|
-
*
|
|
17
19
|
* @module security-scan-engine
|
|
18
20
|
*/
|
|
19
21
|
import { callApi } from './queries/shared.js';
|
|
@@ -33,13 +35,6 @@ export function getRegisteredScanners() {
|
|
|
33
35
|
return [...DEFAULT_REGISTRY.values()];
|
|
34
36
|
}
|
|
35
37
|
export async function runScanForConfig(config, trigger, deps) {
|
|
36
|
-
const runId = await deps.startRun(config.id, trigger);
|
|
37
|
-
const startedAt = Date.now();
|
|
38
|
-
const coverage = {};
|
|
39
|
-
const warnings = [];
|
|
40
|
-
const counts = { low: 0, medium: 0, high: 0, critical: 0 };
|
|
41
|
-
let anyFailure = false;
|
|
42
|
-
let anySuccess = false;
|
|
43
38
|
// Manual triggers carry the "Refresh feeds" intent: flush the OSV/GHSA
|
|
44
39
|
// TTL caches so this scan pulls fresh advisory data instead of replaying
|
|
45
40
|
// the 15-minute window. Scheduled triggers honour the cache to keep
|
|
@@ -58,77 +53,60 @@ export async function runScanForConfig(config, trigger, deps) {
|
|
|
58
53
|
config,
|
|
59
54
|
repoPath: deps.resolveCwd(config.productId),
|
|
60
55
|
});
|
|
61
|
-
coverage[scanner.iocClass] = result.coverage;
|
|
62
|
-
anySuccess = true;
|
|
63
56
|
const observed = new Set();
|
|
64
57
|
for (const finding of result.findings) {
|
|
65
|
-
const
|
|
66
|
-
counts[finding.severity] = (counts[finding.severity] ?? 0) + 1;
|
|
58
|
+
const upsert = await deps.upsertFinding(config.productId, config.organizationId, finding);
|
|
67
59
|
observed.add(finding.identifier);
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
60
|
+
// Trigger auto-inject when:
|
|
61
|
+
// * the finding is brand new, OR
|
|
62
|
+
// * the existing finding is open with no remediation in flight
|
|
63
|
+
// (e.g. previous delivery was cancelled).
|
|
64
|
+
const shouldRemediate = upsert.wasNew || (upsert.status === 'open' && upsert.linkedInjectionId === null);
|
|
65
|
+
if (deps.autoInjectDeps && shouldRemediate) {
|
|
71
66
|
try {
|
|
72
67
|
const forInjection = {
|
|
73
|
-
id: findingId,
|
|
68
|
+
id: upsert.findingId,
|
|
74
69
|
organizationId: config.organizationId,
|
|
75
70
|
productId: config.productId,
|
|
76
71
|
iocClass: finding.iocClass,
|
|
77
72
|
severity: finding.severity,
|
|
78
73
|
identifier: finding.identifier,
|
|
79
74
|
payload: finding.payload,
|
|
80
|
-
status:
|
|
81
|
-
|
|
82
|
-
linkedInjectionId: null,
|
|
83
|
-
};
|
|
84
|
-
const options = {
|
|
85
|
-
autoInjectThreshold: config.autoInjectSeverityThreshold,
|
|
75
|
+
status: upsert.status,
|
|
76
|
+
linkedInjectionId: upsert.linkedInjectionId,
|
|
86
77
|
};
|
|
87
|
-
await processNewFinding(forInjection,
|
|
78
|
+
await processNewFinding(forInjection, {}, deps.autoInjectDeps);
|
|
88
79
|
}
|
|
89
80
|
catch (err) {
|
|
90
|
-
|
|
81
|
+
console.warn(`[security-scan-engine] auto-inject ${finding.identifier}:`, err.message);
|
|
91
82
|
}
|
|
92
83
|
}
|
|
93
84
|
}
|
|
94
85
|
observedByClass.set(scanner.iocClass, observed);
|
|
95
86
|
}
|
|
96
87
|
catch (err) {
|
|
97
|
-
|
|
98
|
-
warnings.push(`${scanner.iocClass}: ${err.message}`);
|
|
99
|
-
coverage[scanner.iocClass] = { error: err.message };
|
|
88
|
+
console.warn(`[security-scan-engine] scanner ${scanner.iocClass} failed:`, err.message);
|
|
100
89
|
}
|
|
101
90
|
}
|
|
102
91
|
// Re-scan resolution: previously-open findings whose class was
|
|
103
92
|
// covered by this run but whose identifier did not re-appear are
|
|
104
|
-
// flipped to 'resolved'. Failures here are non-fatal
|
|
105
|
-
// already succeeded for its primary purpose (finding fresh issues).
|
|
93
|
+
// flipped to 'resolved'. Failures here are non-fatal.
|
|
106
94
|
if (deps.resolutionDeps && observedByClass.size > 0) {
|
|
107
95
|
try {
|
|
108
96
|
const observedSets = Array.from(observedByClass.entries()).map(([iocClass, identifiers]) => ({ iocClass, identifiers }));
|
|
109
|
-
|
|
110
|
-
if (resolved.length > 0) {
|
|
111
|
-
coverage.resolved_findings = resolved.length;
|
|
112
|
-
}
|
|
97
|
+
await resolveStaleFindings(config.productId, observedSets, deps.resolutionDeps);
|
|
113
98
|
}
|
|
114
99
|
catch (err) {
|
|
115
|
-
|
|
100
|
+
console.warn('[security-scan-engine] resolution sweep failed:', err.message);
|
|
116
101
|
}
|
|
117
102
|
}
|
|
118
|
-
|
|
119
|
-
|
|
103
|
+
// Stamp config.last_run_at so cadence recomputes.
|
|
104
|
+
try {
|
|
105
|
+
await deps.updateConfigLastRun(config.id);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
console.warn('[security-scan-engine] updateConfigLastRun failed:', err.message);
|
|
120
109
|
}
|
|
121
|
-
const status = anyFailure
|
|
122
|
-
? anySuccess
|
|
123
|
-
? 'partial'
|
|
124
|
-
: 'failed'
|
|
125
|
-
: 'succeeded';
|
|
126
|
-
await deps.finishRun(runId, {
|
|
127
|
-
status,
|
|
128
|
-
coverageSummary: coverage,
|
|
129
|
-
findingsCountBySeverity: counts,
|
|
130
|
-
durationMs: Date.now() - startedAt,
|
|
131
|
-
});
|
|
132
110
|
if (trigger === 'manual') {
|
|
133
111
|
await deps.clearManualRunRequest(config.id);
|
|
134
112
|
}
|
|
@@ -136,8 +114,8 @@ export async function runScanForConfig(config, trigger, deps) {
|
|
|
136
114
|
// ---------------------------------------------------------------------------
|
|
137
115
|
// Loop tick -- invoked by unified-shell on a fixed cadence
|
|
138
116
|
// ---------------------------------------------------------------------------
|
|
139
|
-
export async function runSecurityScanTick(deps) {
|
|
140
|
-
const configs = await deps.getDueScanConfigs();
|
|
117
|
+
export async function runSecurityScanTick(deps, opts = {}) {
|
|
118
|
+
const configs = await deps.getDueScanConfigs(opts);
|
|
141
119
|
for (const config of configs) {
|
|
142
120
|
if (!config.enabled)
|
|
143
121
|
continue;
|
|
@@ -147,8 +125,7 @@ export async function runSecurityScanTick(deps) {
|
|
|
147
125
|
}
|
|
148
126
|
catch {
|
|
149
127
|
// Per-config failures are swallowed so a single broken product
|
|
150
|
-
// does not stop the engine from servicing others.
|
|
151
|
-
// already records the failure status.
|
|
128
|
+
// does not stop the engine from servicing others.
|
|
152
129
|
}
|
|
153
130
|
}
|
|
154
131
|
}
|
|
@@ -163,28 +140,20 @@ export function buildDefaultSecurityScanDeps(config) {
|
|
|
163
140
|
return configForProduct(config, product).repoPath;
|
|
164
141
|
};
|
|
165
142
|
return {
|
|
166
|
-
getDueScanConfigs: async () => {
|
|
167
|
-
const res = await callApi('daemon_get_due_security_scan_configs', {});
|
|
143
|
+
getDueScanConfigs: async (opts) => {
|
|
144
|
+
const res = await callApi('daemon_get_due_security_scan_configs', opts?.manualOnly ? { manualOnly: true } : {});
|
|
168
145
|
return res.items ?? [];
|
|
169
146
|
},
|
|
170
|
-
|
|
171
|
-
const res = await callApi('
|
|
172
|
-
configId,
|
|
173
|
-
trigger,
|
|
174
|
-
});
|
|
175
|
-
return res.runId;
|
|
176
|
-
},
|
|
177
|
-
finishRun: async (runId, update) => {
|
|
178
|
-
await callApi('daemon_finish_security_scan_run', { runId, ...update });
|
|
179
|
-
},
|
|
180
|
-
writeFinding: async (runId, productId, organizationId, finding) => {
|
|
181
|
-
const res = await callApi('daemon_write_security_finding', {
|
|
182
|
-
runId,
|
|
147
|
+
upsertFinding: async (productId, organizationId, finding) => {
|
|
148
|
+
const res = await callApi('daemon_upsert_security_finding', {
|
|
183
149
|
productId,
|
|
184
150
|
organizationId,
|
|
185
151
|
...finding,
|
|
186
152
|
});
|
|
187
|
-
return
|
|
153
|
+
return res;
|
|
154
|
+
},
|
|
155
|
+
updateConfigLastRun: async (configId) => {
|
|
156
|
+
await callApi('daemon_update_scan_config_last_run', { configId });
|
|
188
157
|
},
|
|
189
158
|
clearManualRunRequest: async (configId) => {
|
|
190
159
|
await callApi('daemon_clear_manual_scan_request', { configId });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-scan-engine.js","sourceRoot":"","sources":["../src/security-scan-engine.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"security-scan-engine.js","sourceRoot":"","sources":["../src/security-scan-engine.ts"],"names":[],"mappings":"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"}
|