@tracehound/core 1.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/README.md +125 -0
- package/dist/core/agent.d.ts +89 -0
- package/dist/core/agent.d.ts.map +1 -0
- package/dist/core/agent.js +141 -0
- package/dist/core/agent.js.map +1 -0
- package/dist/core/audit-chain.d.ts +39 -0
- package/dist/core/audit-chain.d.ts.map +1 -0
- package/dist/core/audit-chain.js +87 -0
- package/dist/core/audit-chain.js.map +1 -0
- package/dist/core/cold-storage.d.ts +87 -0
- package/dist/core/cold-storage.d.ts.map +1 -0
- package/dist/core/cold-storage.js +53 -0
- package/dist/core/cold-storage.js.map +1 -0
- package/dist/core/evidence-factory.d.ts +85 -0
- package/dist/core/evidence-factory.d.ts.map +1 -0
- package/dist/core/evidence-factory.js +96 -0
- package/dist/core/evidence-factory.js.map +1 -0
- package/dist/core/evidence.d.ts +48 -0
- package/dist/core/evidence.d.ts.map +1 -0
- package/dist/core/evidence.js +135 -0
- package/dist/core/evidence.js.map +1 -0
- package/dist/core/fail-safe.d.ts +149 -0
- package/dist/core/fail-safe.d.ts.map +1 -0
- package/dist/core/fail-safe.js +217 -0
- package/dist/core/fail-safe.js.map +1 -0
- package/dist/core/hound-ipc.d.ts +91 -0
- package/dist/core/hound-ipc.d.ts.map +1 -0
- package/dist/core/hound-ipc.js +196 -0
- package/dist/core/hound-ipc.js.map +1 -0
- package/dist/core/hound-pool.d.ts +157 -0
- package/dist/core/hound-pool.d.ts.map +1 -0
- package/dist/core/hound-pool.js +337 -0
- package/dist/core/hound-pool.js.map +1 -0
- package/dist/core/hound-process.d.ts +14 -0
- package/dist/core/hound-process.d.ts.map +1 -0
- package/dist/core/hound-process.js +112 -0
- package/dist/core/hound-process.js.map +1 -0
- package/dist/core/hound-worker.d.ts +14 -0
- package/dist/core/hound-worker.d.ts.map +1 -0
- package/dist/core/hound-worker.js +112 -0
- package/dist/core/hound-worker.js.map +1 -0
- package/dist/core/lane-queue.d.ts +121 -0
- package/dist/core/lane-queue.d.ts.map +1 -0
- package/dist/core/lane-queue.js +181 -0
- package/dist/core/lane-queue.js.map +1 -0
- package/dist/core/license-manager.d.ts +128 -0
- package/dist/core/license-manager.d.ts.map +1 -0
- package/dist/core/license-manager.js +219 -0
- package/dist/core/license-manager.js.map +1 -0
- package/dist/core/notification-emitter.d.ts +140 -0
- package/dist/core/notification-emitter.d.ts.map +1 -0
- package/dist/core/notification-emitter.js +197 -0
- package/dist/core/notification-emitter.js.map +1 -0
- package/dist/core/process-adapter.d.ts +146 -0
- package/dist/core/process-adapter.d.ts.map +1 -0
- package/dist/core/process-adapter.js +174 -0
- package/dist/core/process-adapter.js.map +1 -0
- package/dist/core/quarantine.d.ts +95 -0
- package/dist/core/quarantine.d.ts.map +1 -0
- package/dist/core/quarantine.js +221 -0
- package/dist/core/quarantine.js.map +1 -0
- package/dist/core/rate-limiter.d.ts +94 -0
- package/dist/core/rate-limiter.d.ts.map +1 -0
- package/dist/core/rate-limiter.js +156 -0
- package/dist/core/rate-limiter.js.map +1 -0
- package/dist/core/s3-cold-storage.d.ts +116 -0
- package/dist/core/s3-cold-storage.d.ts.map +1 -0
- package/dist/core/s3-cold-storage.js +198 -0
- package/dist/core/s3-cold-storage.js.map +1 -0
- package/dist/core/scheduler.d.ts +126 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +138 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/security-state.d.ts +170 -0
- package/dist/core/security-state.d.ts.map +1 -0
- package/dist/core/security-state.js +156 -0
- package/dist/core/security-state.js.map +1 -0
- package/dist/core/tier-capacity.d.ts +58 -0
- package/dist/core/tier-capacity.d.ts.map +1 -0
- package/dist/core/tier-capacity.js +89 -0
- package/dist/core/tier-capacity.js.map +1 -0
- package/dist/core/tracehound.d.ts +85 -0
- package/dist/core/tracehound.d.ts.map +1 -0
- package/dist/core/tracehound.js +90 -0
- package/dist/core/tracehound.js.map +1 -0
- package/dist/core/trust-boundary.d.ts +85 -0
- package/dist/core/trust-boundary.d.ts.map +1 -0
- package/dist/core/trust-boundary.js +71 -0
- package/dist/core/trust-boundary.js.map +1 -0
- package/dist/core/watcher.d.ts +153 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +141 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +112 -0
- package/dist/index.js.map +1 -0
- package/dist/types/audit.d.ts +45 -0
- package/dist/types/audit.d.ts.map +1 -0
- package/dist/types/audit.js +5 -0
- package/dist/types/audit.js.map +1 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/common.js +5 -0
- package/dist/types/common.js.map +1 -0
- package/dist/types/config.d.ts +98 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +58 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/errors.d.ts +118 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +266 -0
- package/dist/types/errors.js.map +1 -0
- package/dist/types/evidence.d.ts +102 -0
- package/dist/types/evidence.d.ts.map +1 -0
- package/dist/types/evidence.js +5 -0
- package/dist/types/evidence.js.map +1 -0
- package/dist/types/index.d.ts +18 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +9 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/result.d.ts +62 -0
- package/dist/types/result.d.ts.map +1 -0
- package/dist/types/result.js +34 -0
- package/dist/types/result.js.map +1 -0
- package/dist/types/scent.d.ts +55 -0
- package/dist/types/scent.d.ts.map +1 -0
- package/dist/types/scent.js +5 -0
- package/dist/types/scent.js.map +1 -0
- package/dist/types/signature.d.ts +47 -0
- package/dist/types/signature.d.ts.map +1 -0
- package/dist/types/signature.js +68 -0
- package/dist/types/signature.js.map +1 -0
- package/dist/types/threat.d.ts +38 -0
- package/dist/types/threat.d.ts.map +1 -0
- package/dist/types/threat.js +18 -0
- package/dist/types/threat.js.map +1 -0
- package/dist/utils/binary-codec.d.ts +225 -0
- package/dist/utils/binary-codec.d.ts.map +1 -0
- package/dist/utils/binary-codec.js +266 -0
- package/dist/utils/binary-codec.js.map +1 -0
- package/dist/utils/compare.d.ts +26 -0
- package/dist/utils/compare.d.ts.map +1 -0
- package/dist/utils/compare.js +44 -0
- package/dist/utils/compare.js.map +1 -0
- package/dist/utils/encode.d.ts +39 -0
- package/dist/utils/encode.d.ts.map +1 -0
- package/dist/utils/encode.js +124 -0
- package/dist/utils/encode.js.map +1 -0
- package/dist/utils/hash.d.ts +19 -0
- package/dist/utils/hash.d.ts.map +1 -0
- package/dist/utils/hash.js +25 -0
- package/dist/utils/hash.js.map +1 -0
- package/dist/utils/id.d.ts +20 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +47 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/runtime.d.ts +24 -0
- package/dist/utils/runtime.d.ts.map +1 -0
- package/dist/utils/runtime.js +68 -0
- package/dist/utils/runtime.js.map +1 -0
- package/dist/utils/serialize.d.ts +14 -0
- package/dist/utils/serialize.d.ts.map +1 -0
- package/dist/utils/serialize.js +27 -0
- package/dist/utils/serialize.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-adapter.js","sourceRoot":"","sources":["../../src/core/process-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quarantine - priority-based evidence storage with eviction.
|
|
3
|
+
*/
|
|
4
|
+
import type { Severity } from '../types/common.js';
|
|
5
|
+
import type { QuarantineConfig } from '../types/config.js';
|
|
6
|
+
import type { EvidenceHandle, NeutralizationRecord, PurgeRecord } from '../types/evidence.js';
|
|
7
|
+
import type { AuditChain } from './audit-chain.js';
|
|
8
|
+
/** Result of insert operation */
|
|
9
|
+
export interface InsertResult {
|
|
10
|
+
status: 'inserted' | 'duplicate';
|
|
11
|
+
existing?: EvidenceHandle;
|
|
12
|
+
}
|
|
13
|
+
/** Quarantine statistics */
|
|
14
|
+
export interface QuarantineStats {
|
|
15
|
+
count: number;
|
|
16
|
+
bytes: number;
|
|
17
|
+
bySeverity: Record<Severity, number>;
|
|
18
|
+
}
|
|
19
|
+
/** Result of replace operation */
|
|
20
|
+
export interface ReplaceResult {
|
|
21
|
+
status: 'replaced' | 'inserted_only';
|
|
22
|
+
neutralized?: NeutralizationRecord;
|
|
23
|
+
inserted: boolean;
|
|
24
|
+
duplicate?: EvidenceHandle;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Quarantine storage with priority-based eviction.
|
|
28
|
+
* Stores evidence by signature and evicts lowest priority when limits exceeded.
|
|
29
|
+
*/
|
|
30
|
+
export declare class Quarantine {
|
|
31
|
+
private config;
|
|
32
|
+
private auditChain;
|
|
33
|
+
private store;
|
|
34
|
+
private totalBytes;
|
|
35
|
+
constructor(config: QuarantineConfig, auditChain: AuditChain);
|
|
36
|
+
/**
|
|
37
|
+
* Insert evidence into quarantine.
|
|
38
|
+
* Triggers eviction if limits exceeded.
|
|
39
|
+
*/
|
|
40
|
+
insert(evidence: EvidenceHandle): InsertResult;
|
|
41
|
+
/**
|
|
42
|
+
* Get evidence by signature.
|
|
43
|
+
*/
|
|
44
|
+
get(signature: string): EvidenceHandle | null;
|
|
45
|
+
/**
|
|
46
|
+
* Check if signature exists in quarantine.
|
|
47
|
+
*/
|
|
48
|
+
has(signature: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Neutralize evidence by signature.
|
|
51
|
+
* Removes from quarantine and appends to audit chain.
|
|
52
|
+
*/
|
|
53
|
+
neutralize(signature: string): NeutralizationRecord | null;
|
|
54
|
+
/**
|
|
55
|
+
* Flush all evidence from quarantine.
|
|
56
|
+
* Returns all neutralization records.
|
|
57
|
+
*/
|
|
58
|
+
flush(): NeutralizationRecord[];
|
|
59
|
+
/**
|
|
60
|
+
* Purge evidence by signature with explicit reason.
|
|
61
|
+
* Unlike neutralize, purge creates a PurgeRecord with reason metadata.
|
|
62
|
+
*
|
|
63
|
+
* @param signature - Evidence signature to purge
|
|
64
|
+
* @param reason - Reason for purge
|
|
65
|
+
* @returns PurgeRecord if found, null if not found
|
|
66
|
+
*/
|
|
67
|
+
purge(signature: string, reason: 'timeout' | 'error' | 'abort' | 'panic'): PurgeRecord | null;
|
|
68
|
+
/**
|
|
69
|
+
* Replace evidence with new evidence atomically.
|
|
70
|
+
* Old evidence is neutralized and new evidence is inserted.
|
|
71
|
+
*
|
|
72
|
+
* @param oldSignature - Signature of evidence to replace
|
|
73
|
+
* @param newEvidence - New evidence to insert
|
|
74
|
+
* @returns Result with old neutralization record and new insert status
|
|
75
|
+
*/
|
|
76
|
+
replace(oldSignature: string, newEvidence: EvidenceHandle): ReplaceResult;
|
|
77
|
+
/**
|
|
78
|
+
* Get current quarantine statistics.
|
|
79
|
+
*/
|
|
80
|
+
get stats(): QuarantineStats;
|
|
81
|
+
/**
|
|
82
|
+
* Evict lowest priority evidence.
|
|
83
|
+
*/
|
|
84
|
+
private evict;
|
|
85
|
+
/**
|
|
86
|
+
* Select evidence for eviction based on priority.
|
|
87
|
+
* Lowest severity first, then oldest.
|
|
88
|
+
*/
|
|
89
|
+
private selectForEviction;
|
|
90
|
+
/**
|
|
91
|
+
* Check if quarantine exceeds configured limits.
|
|
92
|
+
*/
|
|
93
|
+
private exceedsLimits;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=quarantine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quarantine.d.ts","sourceRoot":"","sources":["../../src/core/quarantine.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAClD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AAC7F,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAElD,iCAAiC;AACjC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,UAAU,GAAG,WAAW,CAAA;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAA;CAC1B;AAED,4BAA4B;AAC5B,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;CACrC;AAED,kCAAkC;AAClC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,UAAU,GAAG,eAAe,CAAA;IACpC,WAAW,CAAC,EAAE,oBAAoB,CAAA;IAClC,QAAQ,EAAE,OAAO,CAAA;IACjB,SAAS,CAAC,EAAE,cAAc,CAAA;CAC3B;AAUD;;;GAGG;AACH,qBAAa,UAAU;IAKnB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,UAAU;IALpB,OAAO,CAAC,KAAK,CAAoC;IACjD,OAAO,CAAC,UAAU,CAAI;gBAGZ,MAAM,EAAE,gBAAgB,EACxB,UAAU,EAAE,UAAU;IAGhC;;;OAGG;IACH,MAAM,CAAC,QAAQ,EAAE,cAAc,GAAG,YAAY;IAqB9C;;OAEG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAI7C;;OAEG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/B;;;OAGG;IACH,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI;IAoB1D;;;OAGG;IACH,KAAK,IAAI,oBAAoB,EAAE;IAgB/B;;;;;;;OAOG;IACH,KAAK,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,IAAI;IAqC7F;;;;;;;OAOG;IACH,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,GAAG,aAAa;IAwBzE;;OAEG;IACH,IAAI,KAAK,IAAI,eAAe,CAiB3B;IAED;;OAEG;IACH,OAAO,CAAC,KAAK;IAoBb;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAgBzB;;OAEG;IACH,OAAO,CAAC,aAAa;CAGtB"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quarantine - priority-based evidence storage with eviction.
|
|
3
|
+
*/
|
|
4
|
+
/** Severity ranking for eviction priority */
|
|
5
|
+
const SEVERITY_RANK = {
|
|
6
|
+
low: 0,
|
|
7
|
+
medium: 1,
|
|
8
|
+
high: 2,
|
|
9
|
+
critical: 3,
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Quarantine storage with priority-based eviction.
|
|
13
|
+
* Stores evidence by signature and evicts lowest priority when limits exceeded.
|
|
14
|
+
*/
|
|
15
|
+
export class Quarantine {
|
|
16
|
+
config;
|
|
17
|
+
auditChain;
|
|
18
|
+
store = new Map();
|
|
19
|
+
totalBytes = 0;
|
|
20
|
+
constructor(config, auditChain) {
|
|
21
|
+
this.config = config;
|
|
22
|
+
this.auditChain = auditChain;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Insert evidence into quarantine.
|
|
26
|
+
* Triggers eviction if limits exceeded.
|
|
27
|
+
*/
|
|
28
|
+
insert(evidence) {
|
|
29
|
+
// Check duplicate
|
|
30
|
+
if (this.store.has(evidence.signature)) {
|
|
31
|
+
return {
|
|
32
|
+
status: 'duplicate',
|
|
33
|
+
existing: this.store.get(evidence.signature),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Insert new evidence
|
|
37
|
+
this.store.set(evidence.signature, evidence);
|
|
38
|
+
this.totalBytes += evidence.size;
|
|
39
|
+
// Evict if limits exceeded
|
|
40
|
+
while (this.exceedsLimits()) {
|
|
41
|
+
this.evict(1);
|
|
42
|
+
}
|
|
43
|
+
return { status: 'inserted' };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get evidence by signature.
|
|
47
|
+
*/
|
|
48
|
+
get(signature) {
|
|
49
|
+
return this.store.get(signature) ?? null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Check if signature exists in quarantine.
|
|
53
|
+
*/
|
|
54
|
+
has(signature) {
|
|
55
|
+
return this.store.has(signature);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Neutralize evidence by signature.
|
|
59
|
+
* Removes from quarantine and appends to audit chain.
|
|
60
|
+
*/
|
|
61
|
+
neutralize(signature) {
|
|
62
|
+
const evidence = this.store.get(signature);
|
|
63
|
+
if (!evidence) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// Get size before neutralize (evidence will be disposed)
|
|
67
|
+
const size = evidence.size;
|
|
68
|
+
// Neutralize with audit chain
|
|
69
|
+
const record = evidence.neutralize(this.auditChain.lastHash);
|
|
70
|
+
this.auditChain.append(record);
|
|
71
|
+
// Remove from store
|
|
72
|
+
this.store.delete(signature);
|
|
73
|
+
this.totalBytes -= size;
|
|
74
|
+
return record;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Flush all evidence from quarantine.
|
|
78
|
+
* Returns all neutralization records.
|
|
79
|
+
*/
|
|
80
|
+
flush() {
|
|
81
|
+
const records = [];
|
|
82
|
+
for (const [_signature, evidence] of this.store) {
|
|
83
|
+
const record = evidence.neutralize(this.auditChain.lastHash);
|
|
84
|
+
this.auditChain.append(record);
|
|
85
|
+
records.push(record);
|
|
86
|
+
}
|
|
87
|
+
// Clear store
|
|
88
|
+
this.store.clear();
|
|
89
|
+
this.totalBytes = 0;
|
|
90
|
+
return records;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Purge evidence by signature with explicit reason.
|
|
94
|
+
* Unlike neutralize, purge creates a PurgeRecord with reason metadata.
|
|
95
|
+
*
|
|
96
|
+
* @param signature - Evidence signature to purge
|
|
97
|
+
* @param reason - Reason for purge
|
|
98
|
+
* @returns PurgeRecord if found, null if not found
|
|
99
|
+
*/
|
|
100
|
+
purge(signature, reason) {
|
|
101
|
+
const evidence = this.store.get(signature);
|
|
102
|
+
if (!evidence) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
const size = evidence.size;
|
|
106
|
+
const hash = evidence.hash;
|
|
107
|
+
// Create purge record before disposing
|
|
108
|
+
const record = {
|
|
109
|
+
id: `prg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
110
|
+
reason,
|
|
111
|
+
scent: {
|
|
112
|
+
id: evidence.signature, // Using signature as proxy for scent ID
|
|
113
|
+
source: 'unknown', // Not available from evidence handle
|
|
114
|
+
timestamp: evidence.captured,
|
|
115
|
+
payloadHash: hash,
|
|
116
|
+
payloadSize: size,
|
|
117
|
+
},
|
|
118
|
+
purgeTimestamp: Date.now(),
|
|
119
|
+
};
|
|
120
|
+
// Dispose evidence (force cleanup without audit chain)
|
|
121
|
+
try {
|
|
122
|
+
evidence.transfer(); // Transfer ownership to force disposal
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Already disposed, ignore
|
|
126
|
+
}
|
|
127
|
+
// Remove from store
|
|
128
|
+
this.store.delete(signature);
|
|
129
|
+
this.totalBytes -= size;
|
|
130
|
+
return record;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Replace evidence with new evidence atomically.
|
|
134
|
+
* Old evidence is neutralized and new evidence is inserted.
|
|
135
|
+
*
|
|
136
|
+
* @param oldSignature - Signature of evidence to replace
|
|
137
|
+
* @param newEvidence - New evidence to insert
|
|
138
|
+
* @returns Result with old neutralization record and new insert status
|
|
139
|
+
*/
|
|
140
|
+
replace(oldSignature, newEvidence) {
|
|
141
|
+
// First, neutralize old evidence
|
|
142
|
+
const neutralized = this.neutralize(oldSignature);
|
|
143
|
+
if (!neutralized) {
|
|
144
|
+
// Old evidence not found, just insert new
|
|
145
|
+
const insertResult = this.insert(newEvidence);
|
|
146
|
+
return {
|
|
147
|
+
status: 'inserted_only',
|
|
148
|
+
inserted: insertResult.status === 'inserted',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// Insert new evidence
|
|
152
|
+
const insertResult = this.insert(newEvidence);
|
|
153
|
+
return {
|
|
154
|
+
status: 'replaced',
|
|
155
|
+
neutralized,
|
|
156
|
+
inserted: insertResult.status === 'inserted',
|
|
157
|
+
...(insertResult.status === 'duplicate' && { duplicate: insertResult.existing }),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Get current quarantine statistics.
|
|
162
|
+
*/
|
|
163
|
+
get stats() {
|
|
164
|
+
const bySeverity = {
|
|
165
|
+
low: 0,
|
|
166
|
+
medium: 0,
|
|
167
|
+
high: 0,
|
|
168
|
+
critical: 0,
|
|
169
|
+
};
|
|
170
|
+
for (const evidence of this.store.values()) {
|
|
171
|
+
bySeverity[evidence.severity]++;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
count: this.store.size,
|
|
175
|
+
bytes: this.totalBytes,
|
|
176
|
+
bySeverity,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Evict lowest priority evidence.
|
|
181
|
+
*/
|
|
182
|
+
evict(count) {
|
|
183
|
+
const victims = this.selectForEviction(count);
|
|
184
|
+
for (const evidence of victims) {
|
|
185
|
+
const size = evidence.size;
|
|
186
|
+
const signature = evidence.signature;
|
|
187
|
+
// Neutralize and append to audit chain
|
|
188
|
+
const record = evidence.neutralize(this.auditChain.lastHash);
|
|
189
|
+
this.auditChain.append(record);
|
|
190
|
+
// Remove from store
|
|
191
|
+
this.store.delete(signature);
|
|
192
|
+
this.totalBytes -= size;
|
|
193
|
+
// NOTE: Eviction is tracked via audit chain neutralization record.
|
|
194
|
+
// High/critical severity alerts are handled by Watcher observing quarantine stats.
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Select evidence for eviction based on priority.
|
|
199
|
+
* Lowest severity first, then oldest.
|
|
200
|
+
*/
|
|
201
|
+
selectForEviction(count) {
|
|
202
|
+
const all = Array.from(this.store.values());
|
|
203
|
+
// Sort: lowest severity first, then oldest
|
|
204
|
+
all.sort((a, b) => {
|
|
205
|
+
const severityDiff = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];
|
|
206
|
+
if (severityDiff !== 0) {
|
|
207
|
+
return severityDiff;
|
|
208
|
+
}
|
|
209
|
+
// Same severity: oldest first
|
|
210
|
+
return a.captured - b.captured;
|
|
211
|
+
});
|
|
212
|
+
return all.slice(0, count);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Check if quarantine exceeds configured limits.
|
|
216
|
+
*/
|
|
217
|
+
exceedsLimits() {
|
|
218
|
+
return this.store.size > this.config.maxCount || this.totalBytes > this.config.maxBytes;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=quarantine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quarantine.js","sourceRoot":"","sources":["../../src/core/quarantine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter - sliding window with per-source tracking and cleanup.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: Prevents pool exhaustion DoS by early rejection.
|
|
5
|
+
* Memory Safety: TTL-based cleanup prevents memory leaks.
|
|
6
|
+
*/
|
|
7
|
+
import type { RateLimitConfig } from '../types/config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Result of a rate limit check.
|
|
10
|
+
*/
|
|
11
|
+
export type RateLimitResult = {
|
|
12
|
+
allowed: true;
|
|
13
|
+
} | {
|
|
14
|
+
allowed: false;
|
|
15
|
+
/** True if source is in block penalty period */
|
|
16
|
+
blocked: boolean;
|
|
17
|
+
/** Milliseconds until source can retry */
|
|
18
|
+
retryAfter: number;
|
|
19
|
+
/** Human-readable reason */
|
|
20
|
+
reason: string;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Rate limiter interface per RFC-0000.
|
|
24
|
+
*/
|
|
25
|
+
export interface IRateLimiter {
|
|
26
|
+
/**
|
|
27
|
+
* Check if source is allowed to proceed.
|
|
28
|
+
* @param source - Source identifier (IP, API key, etc.)
|
|
29
|
+
*/
|
|
30
|
+
check(source: string): RateLimitResult;
|
|
31
|
+
/**
|
|
32
|
+
* Reset rate limit for a specific source.
|
|
33
|
+
* Used for manual unblocking.
|
|
34
|
+
* @param source - Source identifier
|
|
35
|
+
*/
|
|
36
|
+
reset(source: string): void;
|
|
37
|
+
/**
|
|
38
|
+
* Clean up stale entries to prevent memory leaks.
|
|
39
|
+
* Should be called periodically.
|
|
40
|
+
* @returns Number of entries cleaned
|
|
41
|
+
*/
|
|
42
|
+
cleanup(): number;
|
|
43
|
+
/**
|
|
44
|
+
* Get current statistics.
|
|
45
|
+
*/
|
|
46
|
+
readonly stats: RateLimiterStats;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Rate limiter statistics.
|
|
50
|
+
*/
|
|
51
|
+
export interface RateLimiterStats {
|
|
52
|
+
/** Total tracked sources */
|
|
53
|
+
sources: number;
|
|
54
|
+
/** Currently blocked sources */
|
|
55
|
+
blocked: number;
|
|
56
|
+
/** Total checks performed */
|
|
57
|
+
totalChecks: number;
|
|
58
|
+
/** Total rejections */
|
|
59
|
+
totalRejections: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Sliding window rate limiter implementation.
|
|
63
|
+
*
|
|
64
|
+
* Algorithm:
|
|
65
|
+
* 1. Maintain list of request timestamps per source
|
|
66
|
+
* 2. On check, remove timestamps outside window
|
|
67
|
+
* 3. If count >= maxRequests, reject and optionally block
|
|
68
|
+
* 4. Otherwise, record timestamp and allow
|
|
69
|
+
*
|
|
70
|
+
* Block State:
|
|
71
|
+
* - After maxRequests exceeded, source enters blockDurationMs penalty
|
|
72
|
+
* - During block, all requests rejected with blocked: true
|
|
73
|
+
* - After block expires, source can accumulate requests again
|
|
74
|
+
*/
|
|
75
|
+
export declare class RateLimiter implements IRateLimiter {
|
|
76
|
+
private readonly sources;
|
|
77
|
+
private readonly config;
|
|
78
|
+
private totalChecks;
|
|
79
|
+
private totalRejections;
|
|
80
|
+
constructor(config: RateLimitConfig);
|
|
81
|
+
check(source: string): RateLimitResult;
|
|
82
|
+
reset(source: string): void;
|
|
83
|
+
cleanup(): number;
|
|
84
|
+
get stats(): RateLimiterStats;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Create a rate limiter instance.
|
|
88
|
+
* Factory function for end users.
|
|
89
|
+
*
|
|
90
|
+
* @param config - Rate limit configuration
|
|
91
|
+
* @returns Rate limiter instance
|
|
92
|
+
*/
|
|
93
|
+
export declare function createRateLimiter(config: RateLimitConfig): IRateLimiter;
|
|
94
|
+
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/core/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAEzD;;GAEG;AACH,MAAM,MAAM,eAAe,GACvB;IAAE,OAAO,EAAE,IAAI,CAAA;CAAE,GACjB;IACE,OAAO,EAAE,KAAK,CAAA;IACd,gDAAgD;IAChD,OAAO,EAAE,OAAO,CAAA;IAChB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAA;IAClB,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAA;CACf,CAAA;AAcL;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;OAGG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CAAA;IAEtC;;;;OAIG;IACH,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAE3B;;;;OAIG;IACH,OAAO,IAAI,MAAM,CAAA;IAEjB;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,gBAAgB,CAAA;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,OAAO,EAAE,MAAM,CAAA;IACf,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAA;IACf,6BAA6B;IAC7B,WAAW,EAAE,MAAM,CAAA;IACnB,uBAAuB;IACvB,eAAe,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,qBAAa,WAAY,YAAW,YAAY;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAiC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA2B;IAClD,OAAO,CAAC,WAAW,CAAI;IACvB,OAAO,CAAC,eAAe,CAAI;gBAEf,MAAM,EAAE,eAAe;IAmBnC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe;IA4EtC,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAI3B,OAAO,IAAI,MAAM;IAsBjB,IAAI,KAAK,IAAI,gBAAgB,CAgB5B;CACF;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,GAAG,YAAY,CAEvE"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter - sliding window with per-source tracking and cleanup.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY: Prevents pool exhaustion DoS by early rejection.
|
|
5
|
+
* Memory Safety: TTL-based cleanup prevents memory leaks.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Sliding window rate limiter implementation.
|
|
9
|
+
*
|
|
10
|
+
* Algorithm:
|
|
11
|
+
* 1. Maintain list of request timestamps per source
|
|
12
|
+
* 2. On check, remove timestamps outside window
|
|
13
|
+
* 3. If count >= maxRequests, reject and optionally block
|
|
14
|
+
* 4. Otherwise, record timestamp and allow
|
|
15
|
+
*
|
|
16
|
+
* Block State:
|
|
17
|
+
* - After maxRequests exceeded, source enters blockDurationMs penalty
|
|
18
|
+
* - During block, all requests rejected with blocked: true
|
|
19
|
+
* - After block expires, source can accumulate requests again
|
|
20
|
+
*/
|
|
21
|
+
export class RateLimiter {
|
|
22
|
+
sources = new Map();
|
|
23
|
+
config;
|
|
24
|
+
totalChecks = 0;
|
|
25
|
+
totalRejections = 0;
|
|
26
|
+
constructor(config) {
|
|
27
|
+
// Validate config
|
|
28
|
+
if (config.windowMs <= 0) {
|
|
29
|
+
throw new Error('windowMs must be positive');
|
|
30
|
+
}
|
|
31
|
+
if (config.maxRequests <= 0) {
|
|
32
|
+
throw new Error('maxRequests must be positive');
|
|
33
|
+
}
|
|
34
|
+
if (config.blockDurationMs < 0) {
|
|
35
|
+
throw new Error('blockDurationMs cannot be negative');
|
|
36
|
+
}
|
|
37
|
+
this.config = {
|
|
38
|
+
windowMs: config.windowMs,
|
|
39
|
+
maxRequests: config.maxRequests,
|
|
40
|
+
blockDurationMs: config.blockDurationMs,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
check(source) {
|
|
44
|
+
this.totalChecks++;
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
// Get or create source entry
|
|
47
|
+
let entry = this.sources.get(source);
|
|
48
|
+
if (!entry) {
|
|
49
|
+
entry = {
|
|
50
|
+
timestamps: [],
|
|
51
|
+
blockedUntil: null,
|
|
52
|
+
lastActivity: now,
|
|
53
|
+
};
|
|
54
|
+
this.sources.set(source, entry);
|
|
55
|
+
}
|
|
56
|
+
// Update last activity
|
|
57
|
+
entry.lastActivity = now;
|
|
58
|
+
// Check if currently blocked
|
|
59
|
+
if (entry.blockedUntil !== null) {
|
|
60
|
+
if (now < entry.blockedUntil) {
|
|
61
|
+
// Still blocked
|
|
62
|
+
this.totalRejections++;
|
|
63
|
+
return {
|
|
64
|
+
allowed: false,
|
|
65
|
+
blocked: true,
|
|
66
|
+
retryAfter: entry.blockedUntil - now,
|
|
67
|
+
reason: 'Source is blocked due to rate limit violation',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
// Block expired, clear it
|
|
72
|
+
entry.blockedUntil = null;
|
|
73
|
+
entry.timestamps = [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Remove timestamps outside current window
|
|
77
|
+
const windowStart = now - this.config.windowMs;
|
|
78
|
+
entry.timestamps = entry.timestamps.filter((ts) => ts > windowStart);
|
|
79
|
+
// Check if limit exceeded
|
|
80
|
+
if (entry.timestamps.length >= this.config.maxRequests) {
|
|
81
|
+
this.totalRejections++;
|
|
82
|
+
// Apply block if configured
|
|
83
|
+
if (this.config.blockDurationMs > 0) {
|
|
84
|
+
entry.blockedUntil = now + this.config.blockDurationMs;
|
|
85
|
+
return {
|
|
86
|
+
allowed: false,
|
|
87
|
+
blocked: true,
|
|
88
|
+
retryAfter: this.config.blockDurationMs,
|
|
89
|
+
reason: 'Rate limit exceeded, source blocked',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// No block, just sliding window rejection
|
|
93
|
+
const oldestInWindow = entry.timestamps[0];
|
|
94
|
+
// Defensive check (should never happen since length >= maxRequests)
|
|
95
|
+
const retryAfter = oldestInWindow !== undefined
|
|
96
|
+
? oldestInWindow + this.config.windowMs - now
|
|
97
|
+
: this.config.windowMs;
|
|
98
|
+
return {
|
|
99
|
+
allowed: false,
|
|
100
|
+
blocked: false,
|
|
101
|
+
retryAfter: Math.max(0, retryAfter),
|
|
102
|
+
reason: 'Rate limit exceeded within sliding window',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// Allow and record timestamp
|
|
106
|
+
entry.timestamps.push(now);
|
|
107
|
+
return { allowed: true };
|
|
108
|
+
}
|
|
109
|
+
reset(source) {
|
|
110
|
+
this.sources.delete(source);
|
|
111
|
+
}
|
|
112
|
+
cleanup() {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
// Stale threshold: no activity for window + block duration
|
|
115
|
+
const staleThreshold = now - this.config.windowMs - this.config.blockDurationMs;
|
|
116
|
+
let cleaned = 0;
|
|
117
|
+
for (const [source, entry] of this.sources) {
|
|
118
|
+
// Remove if:
|
|
119
|
+
// 1. Not blocked AND last activity older than stale threshold
|
|
120
|
+
// 2. OR blocked but block already expired AND stale
|
|
121
|
+
const isExpiredBlock = entry.blockedUntil !== null && entry.blockedUntil < now;
|
|
122
|
+
const isStale = entry.lastActivity < staleThreshold;
|
|
123
|
+
if (isStale && (entry.blockedUntil === null || isExpiredBlock)) {
|
|
124
|
+
this.sources.delete(source);
|
|
125
|
+
cleaned++;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return cleaned;
|
|
129
|
+
}
|
|
130
|
+
get stats() {
|
|
131
|
+
let blocked = 0;
|
|
132
|
+
const now = Date.now();
|
|
133
|
+
for (const entry of this.sources.values()) {
|
|
134
|
+
if (entry.blockedUntil !== null && entry.blockedUntil > now) {
|
|
135
|
+
blocked++;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
sources: this.sources.size,
|
|
140
|
+
blocked,
|
|
141
|
+
totalChecks: this.totalChecks,
|
|
142
|
+
totalRejections: this.totalRejections,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Create a rate limiter instance.
|
|
148
|
+
* Factory function for end users.
|
|
149
|
+
*
|
|
150
|
+
* @param config - Rate limit configuration
|
|
151
|
+
* @returns Rate limiter instance
|
|
152
|
+
*/
|
|
153
|
+
export function createRateLimiter(config) {
|
|
154
|
+
return new RateLimiter(config);
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=rate-limiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/core/rate-limiter.ts"],"names":[],"mappings":"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"}
|