shroud-privacy 2.2.10 → 2.2.12
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 +6 -5
- package/dist/detectors/regex.js +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/compliance.d.ts +0 -44
- package/dist/compliance.js +0 -76
- package/dist/exposure.d.ts +0 -29
- package/dist/exposure.js +0 -72
- package/dist/policy.d.ts +0 -49
- package/dist/policy.js +0 -105
- package/dist/siem.d.ts +0 -35
- package/dist/siem.js +0 -91
- package/dist/store-file.d.ts +0 -26
- package/dist/store-file.js +0 -79
package/README.md
CHANGED
|
@@ -88,9 +88,10 @@ Shroud does not guarantee compliance — regex-based detection has limitations (
|
|
|
88
88
|
```bash
|
|
89
89
|
openclaw --version # ensure 2026.3.22+
|
|
90
90
|
openclaw plugins install shroud-privacy
|
|
91
|
+
openclaw gateway restart
|
|
91
92
|
```
|
|
92
93
|
|
|
93
|
-
|
|
94
|
+
That's it — Shroud is automatically enabled on install. All 100+ detectors are active with safe defaults, no configuration required. To customise, see [Configure](#configure).
|
|
94
95
|
|
|
95
96
|
### Any agent (via APP)
|
|
96
97
|
|
|
@@ -166,13 +167,13 @@ openclaw gateway restart
|
|
|
166
167
|
|
|
167
168
|
## Configure
|
|
168
169
|
|
|
169
|
-
|
|
170
|
+
Shroud works out of the box with zero configuration. `openclaw plugins install` sets `enabled: true` automatically. To customise, edit `~/.openclaw/openclaw.json` under `plugins.entries."shroud-privacy".config`:
|
|
170
171
|
|
|
171
172
|
```jsonc
|
|
172
173
|
"shroud-privacy": {
|
|
173
|
-
"enabled": true,
|
|
174
|
+
"enabled": true, // set automatically by plugins install
|
|
174
175
|
"config": {
|
|
175
|
-
"auditEnabled": true
|
|
176
|
+
"auditEnabled": true // audit log on — see what Shroud is doing
|
|
176
177
|
// "minConfidence": 0.0 // catch everything (default)
|
|
177
178
|
// "secretKey": "" // auto-generated if empty
|
|
178
179
|
// "persistentSalt": "" // set for cross-session consistency
|
|
@@ -288,7 +289,7 @@ Shroud includes a `ContextDetector` that wraps the regex engine with post-detect
|
|
|
288
289
|
|
|
289
290
|
- **Context-aware boosting**: Text blocks containing config keywords (`interface`, `router ospf`, `hostname`) get +10% confidence for detected entities.
|
|
290
291
|
- **Proximity clustering**: When a name, email, and phone appear within 200 characters, each gets a confidence boost.
|
|
291
|
-
- **Hostname propagation**: `hostname
|
|
292
|
+
- **Hostname propagation**: `hostname CORE-RTR-01` in one place → bare `CORE-RTR-01` detected everywhere in the text.
|
|
292
293
|
- **Learned entities**: Hostnames and infra identifiers seen in previous messages are remembered and detected in future messages without requiring config-line context.
|
|
293
294
|
- **Documentation filtering**: RFC 3849 IPv6 doc prefix (`2001:db8::/32`), IPv6 loopback (`::1`), `example.com` emails, and well-known placeholders are automatically skipped.
|
|
294
295
|
- **DNS-based URL classification**: External URLs pass through to the LLM; internal URLs are obfuscated. See [URL handling](#url-handling).
|
package/dist/detectors/regex.js
CHANGED
|
@@ -559,9 +559,9 @@ export const BUILTIN_PATTERNS = [
|
|
|
559
559
|
confidence: 0.90,
|
|
560
560
|
},
|
|
561
561
|
{
|
|
562
|
-
// Short device codes:
|
|
562
|
+
// Short device codes: LABRTR01, DCRTR02 — uppercase prefix + role abbreviation + digit(s)
|
|
563
563
|
name: "device_name_short",
|
|
564
|
-
pattern: /\b([A-Z]{
|
|
564
|
+
pattern: /\b([A-Z]{2,6}(?:RTR|SWT|FWL|APX|DST|AGG|CPE|WLC)[A-Z]?\d{1,2})\b/g,
|
|
565
565
|
category: Category.HOSTNAME,
|
|
566
566
|
confidence: 0.85,
|
|
567
567
|
},
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "shroud-privacy",
|
|
3
3
|
"name": "Shroud",
|
|
4
|
-
"version": "2.2.
|
|
4
|
+
"version": "2.2.12",
|
|
5
5
|
"description": "Privacy obfuscation with deterministic fake values and deobfuscation — PII never reaches the LLM, tool calls still work",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shroud-privacy",
|
|
3
|
-
"version": "2.2.
|
|
3
|
+
"version": "2.2.12",
|
|
4
4
|
"description": "Privacy and infrastructure protection for AI agents — detects sensitive data (PII, network topology, credentials, OT/SCADA) and replaces with deterministic fakes before anything reaches the LLM.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/dist/compliance.d.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compliance reporter — generates category coverage and detection gap reports.
|
|
3
|
-
*/
|
|
4
|
-
export interface ComplianceConfig {
|
|
5
|
-
/** Path to write report. Empty = disabled. */
|
|
6
|
-
reportPath: string;
|
|
7
|
-
/** Report interval: "hourly" | "daily". */
|
|
8
|
-
reportInterval: "hourly" | "daily";
|
|
9
|
-
/** Required categories that MUST have detections. */
|
|
10
|
-
requiredCategories: string[];
|
|
11
|
-
}
|
|
12
|
-
export interface ComplianceReport {
|
|
13
|
-
generatedAt: string;
|
|
14
|
-
periodStart: string;
|
|
15
|
-
periodEnd: string;
|
|
16
|
-
/** Categories that had detections. */
|
|
17
|
-
activeCategoryCoverage: Record<string, number>;
|
|
18
|
-
/** Required categories with zero detections (gaps). */
|
|
19
|
-
detectionGaps: string[];
|
|
20
|
-
/** Total entities detected in period. */
|
|
21
|
-
totalEntities: number;
|
|
22
|
-
/** Store utilization. */
|
|
23
|
-
storeMappings: number;
|
|
24
|
-
/** Allowlist usage (how many entities were skipped). */
|
|
25
|
-
allowlistSkips: number;
|
|
26
|
-
/** Compliance score: % of required categories with detections. */
|
|
27
|
-
complianceScore: number;
|
|
28
|
-
}
|
|
29
|
-
export declare class ComplianceReporter {
|
|
30
|
-
private _config;
|
|
31
|
-
private _periodStart;
|
|
32
|
-
private _categoryCounts;
|
|
33
|
-
private _totalEntities;
|
|
34
|
-
private _allowlistSkips;
|
|
35
|
-
constructor(config?: Partial<ComplianceConfig>);
|
|
36
|
-
get enabled(): boolean;
|
|
37
|
-
/** Record detection event. */
|
|
38
|
-
recordDetections(categoryCounts: Record<string, number>, allowlistSkips?: number): void;
|
|
39
|
-
/** Generate and optionally write a compliance report. */
|
|
40
|
-
generateReport(storeMappings?: number): ComplianceReport;
|
|
41
|
-
/** Reset for new period. */
|
|
42
|
-
resetPeriod(): void;
|
|
43
|
-
getStats(): object;
|
|
44
|
-
}
|
package/dist/compliance.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compliance reporter — generates category coverage and detection gap reports.
|
|
3
|
-
*/
|
|
4
|
-
import { writeFileSync } from "node:fs";
|
|
5
|
-
export class ComplianceReporter {
|
|
6
|
-
_config;
|
|
7
|
-
_periodStart;
|
|
8
|
-
_categoryCounts = {};
|
|
9
|
-
_totalEntities = 0;
|
|
10
|
-
_allowlistSkips = 0;
|
|
11
|
-
constructor(config = {}) {
|
|
12
|
-
this._config = {
|
|
13
|
-
reportPath: config.reportPath ?? "",
|
|
14
|
-
reportInterval: config.reportInterval ?? "daily",
|
|
15
|
-
requiredCategories: config.requiredCategories ?? [],
|
|
16
|
-
};
|
|
17
|
-
this._periodStart = new Date().toISOString();
|
|
18
|
-
}
|
|
19
|
-
get enabled() {
|
|
20
|
-
return !!this._config.reportPath;
|
|
21
|
-
}
|
|
22
|
-
/** Record detection event. */
|
|
23
|
-
recordDetections(categoryCounts, allowlistSkips = 0) {
|
|
24
|
-
for (const [cat, count] of Object.entries(categoryCounts)) {
|
|
25
|
-
this._categoryCounts[cat] = (this._categoryCounts[cat] ?? 0) + count;
|
|
26
|
-
}
|
|
27
|
-
this._totalEntities += Object.values(categoryCounts).reduce((a, b) => a + b, 0);
|
|
28
|
-
this._allowlistSkips += allowlistSkips;
|
|
29
|
-
}
|
|
30
|
-
/** Generate and optionally write a compliance report. */
|
|
31
|
-
generateReport(storeMappings = 0) {
|
|
32
|
-
const now = new Date().toISOString();
|
|
33
|
-
const gaps = this._config.requiredCategories.filter((cat) => (this._categoryCounts[cat] ?? 0) === 0);
|
|
34
|
-
const coveredRequired = this._config.requiredCategories.filter((cat) => (this._categoryCounts[cat] ?? 0) > 0);
|
|
35
|
-
const score = this._config.requiredCategories.length > 0
|
|
36
|
-
? Math.round((coveredRequired.length / this._config.requiredCategories.length) *
|
|
37
|
-
100)
|
|
38
|
-
: 100;
|
|
39
|
-
const report = {
|
|
40
|
-
generatedAt: now,
|
|
41
|
-
periodStart: this._periodStart,
|
|
42
|
-
periodEnd: now,
|
|
43
|
-
activeCategoryCoverage: { ...this._categoryCounts },
|
|
44
|
-
detectionGaps: gaps,
|
|
45
|
-
totalEntities: this._totalEntities,
|
|
46
|
-
storeMappings,
|
|
47
|
-
allowlistSkips: this._allowlistSkips,
|
|
48
|
-
complianceScore: score,
|
|
49
|
-
};
|
|
50
|
-
if (this._config.reportPath) {
|
|
51
|
-
try {
|
|
52
|
-
writeFileSync(this._config.reportPath, JSON.stringify(report, null, 2) + "\n");
|
|
53
|
-
}
|
|
54
|
-
catch {
|
|
55
|
-
// best-effort
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return report;
|
|
59
|
-
}
|
|
60
|
-
/** Reset for new period. */
|
|
61
|
-
resetPeriod() {
|
|
62
|
-
this._categoryCounts = {};
|
|
63
|
-
this._totalEntities = 0;
|
|
64
|
-
this._allowlistSkips = 0;
|
|
65
|
-
this._periodStart = new Date().toISOString();
|
|
66
|
-
}
|
|
67
|
-
getStats() {
|
|
68
|
-
return {
|
|
69
|
-
enabled: this.enabled,
|
|
70
|
-
periodStart: this._periodStart,
|
|
71
|
-
totalEntities: this._totalEntities,
|
|
72
|
-
categoryCoverage: Object.keys(this._categoryCounts).length,
|
|
73
|
-
requiredCategories: this._config.requiredCategories.length,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
}
|
package/dist/exposure.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rate-of-exposure tracker — monitors entity detection velocity.
|
|
3
|
-
* Alerts when entities/minute exceeds threshold, indicating potential
|
|
4
|
-
* bulk data exfiltration or unusual activity.
|
|
5
|
-
*/
|
|
6
|
-
export interface ExposureConfig {
|
|
7
|
-
/** Entities per minute threshold. 0 = disabled. */
|
|
8
|
-
rateThreshold: number;
|
|
9
|
-
/** Window size in seconds for rate calculation. Default: 60. */
|
|
10
|
-
windowSeconds: number;
|
|
11
|
-
/** Callback when threshold exceeded. */
|
|
12
|
-
onAlert?: (rate: number, threshold: number, window: number) => void;
|
|
13
|
-
}
|
|
14
|
-
export declare class ExposureTracker {
|
|
15
|
-
private _config;
|
|
16
|
-
private _timestamps;
|
|
17
|
-
private _alertCount;
|
|
18
|
-
private _lastAlertTime;
|
|
19
|
-
/** Minimum interval between alerts (ms) to prevent alert storms. */
|
|
20
|
-
private _alertCooldownMs;
|
|
21
|
-
constructor(config?: Partial<ExposureConfig>);
|
|
22
|
-
get enabled(): boolean;
|
|
23
|
-
/** Record entity detections. */
|
|
24
|
-
record(entityCount: number): void;
|
|
25
|
-
/** Current rate (entities/minute). */
|
|
26
|
-
currentRate(): number;
|
|
27
|
-
getStats(): object;
|
|
28
|
-
reset(): void;
|
|
29
|
-
}
|
package/dist/exposure.js
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Rate-of-exposure tracker — monitors entity detection velocity.
|
|
3
|
-
* Alerts when entities/minute exceeds threshold, indicating potential
|
|
4
|
-
* bulk data exfiltration or unusual activity.
|
|
5
|
-
*/
|
|
6
|
-
export class ExposureTracker {
|
|
7
|
-
_config;
|
|
8
|
-
_timestamps = [];
|
|
9
|
-
_alertCount = 0;
|
|
10
|
-
_lastAlertTime = 0;
|
|
11
|
-
/** Minimum interval between alerts (ms) to prevent alert storms. */
|
|
12
|
-
_alertCooldownMs = 60000;
|
|
13
|
-
constructor(config = {}) {
|
|
14
|
-
this._config = {
|
|
15
|
-
rateThreshold: config.rateThreshold ?? 0,
|
|
16
|
-
windowSeconds: config.windowSeconds ?? 60,
|
|
17
|
-
onAlert: config.onAlert,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
get enabled() {
|
|
21
|
-
return this._config.rateThreshold > 0;
|
|
22
|
-
}
|
|
23
|
-
/** Record entity detections. */
|
|
24
|
-
record(entityCount) {
|
|
25
|
-
if (!this.enabled || entityCount === 0)
|
|
26
|
-
return;
|
|
27
|
-
const now = Date.now();
|
|
28
|
-
for (let i = 0; i < entityCount; i++) {
|
|
29
|
-
this._timestamps.push(now);
|
|
30
|
-
}
|
|
31
|
-
// Trim old timestamps outside the window
|
|
32
|
-
const cutoff = now - this._config.windowSeconds * 1000;
|
|
33
|
-
while (this._timestamps.length > 0 && this._timestamps[0] < cutoff) {
|
|
34
|
-
this._timestamps.shift();
|
|
35
|
-
}
|
|
36
|
-
// Check rate
|
|
37
|
-
const windowMinutes = this._config.windowSeconds / 60;
|
|
38
|
-
const rate = this._timestamps.length / windowMinutes;
|
|
39
|
-
if (rate > this._config.rateThreshold) {
|
|
40
|
-
if (now - this._lastAlertTime > this._alertCooldownMs) {
|
|
41
|
-
this._alertCount++;
|
|
42
|
-
this._lastAlertTime = now;
|
|
43
|
-
this._config.onAlert?.(Math.round(rate), this._config.rateThreshold, this._config.windowSeconds);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
/** Current rate (entities/minute). */
|
|
48
|
-
currentRate() {
|
|
49
|
-
const now = Date.now();
|
|
50
|
-
const cutoff = now - this._config.windowSeconds * 1000;
|
|
51
|
-
while (this._timestamps.length > 0 && this._timestamps[0] < cutoff) {
|
|
52
|
-
this._timestamps.shift();
|
|
53
|
-
}
|
|
54
|
-
const windowMinutes = this._config.windowSeconds / 60;
|
|
55
|
-
return Math.round(this._timestamps.length / windowMinutes);
|
|
56
|
-
}
|
|
57
|
-
getStats() {
|
|
58
|
-
return {
|
|
59
|
-
enabled: this.enabled,
|
|
60
|
-
currentRate: this.currentRate(),
|
|
61
|
-
threshold: this._config.rateThreshold,
|
|
62
|
-
windowSeconds: this._config.windowSeconds,
|
|
63
|
-
alertCount: this._alertCount,
|
|
64
|
-
entitiesInWindow: this._timestamps.length,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
reset() {
|
|
68
|
-
this._timestamps = [];
|
|
69
|
-
this._alertCount = 0;
|
|
70
|
-
this._lastAlertTime = 0;
|
|
71
|
-
}
|
|
72
|
-
}
|
package/dist/policy.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Policy-as-code enforcement — validate Shroud config against policy constraints.
|
|
3
|
-
* Policy files define required categories, minimum confidence levels, and mandatory denylist entries.
|
|
4
|
-
*/
|
|
5
|
-
import { ShroudConfig } from "./types.js";
|
|
6
|
-
export interface PolicyDef {
|
|
7
|
-
/** Policy version. */
|
|
8
|
-
version: string;
|
|
9
|
-
/** Human-readable policy name. */
|
|
10
|
-
name: string;
|
|
11
|
-
/** Categories that MUST be enabled (not overridden to disabled). */
|
|
12
|
-
requiredCategories?: string[];
|
|
13
|
-
/** Minimum global confidence threshold. */
|
|
14
|
-
minConfidence?: number;
|
|
15
|
-
/** Maximum global confidence threshold (prevent over-filtering). */
|
|
16
|
-
maxConfidence?: number;
|
|
17
|
-
/** Values that MUST be in the denylist. */
|
|
18
|
-
mandatoryDenylist?: string[];
|
|
19
|
-
/** Values that MUST NOT be in the allowlist. */
|
|
20
|
-
prohibitedAllowlist?: string[];
|
|
21
|
-
/** Audit must be enabled. */
|
|
22
|
-
requireAudit?: boolean;
|
|
23
|
-
/** Canary must be enabled. */
|
|
24
|
-
requireCanary?: boolean;
|
|
25
|
-
/** Redaction level must be one of these. */
|
|
26
|
-
allowedRedactionLevels?: string[];
|
|
27
|
-
/** Custom rules. */
|
|
28
|
-
customRules?: Array<{
|
|
29
|
-
name: string;
|
|
30
|
-
check: string;
|
|
31
|
-
message: string;
|
|
32
|
-
}>;
|
|
33
|
-
}
|
|
34
|
-
export interface PolicyViolation {
|
|
35
|
-
rule: string;
|
|
36
|
-
severity: "error" | "warning";
|
|
37
|
-
message: string;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Load a policy from a JSON file.
|
|
41
|
-
*/
|
|
42
|
-
export declare function loadPolicy(filePath: string): PolicyDef | null;
|
|
43
|
-
/**
|
|
44
|
-
* Validate a ShroudConfig against a policy.
|
|
45
|
-
* Returns an array of violations (empty = compliant).
|
|
46
|
-
*/
|
|
47
|
-
export declare function validatePolicy(config: ShroudConfig, policy: PolicyDef, detectorOverrides?: Record<string, {
|
|
48
|
-
enabled?: boolean;
|
|
49
|
-
}>): PolicyViolation[];
|
package/dist/policy.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Policy-as-code enforcement — validate Shroud config against policy constraints.
|
|
3
|
-
* Policy files define required categories, minimum confidence levels, and mandatory denylist entries.
|
|
4
|
-
*/
|
|
5
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
6
|
-
/**
|
|
7
|
-
* Load a policy from a JSON file.
|
|
8
|
-
*/
|
|
9
|
-
export function loadPolicy(filePath) {
|
|
10
|
-
if (!filePath || !existsSync(filePath))
|
|
11
|
-
return null;
|
|
12
|
-
try {
|
|
13
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
14
|
-
return JSON.parse(raw);
|
|
15
|
-
}
|
|
16
|
-
catch (e) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Validate a ShroudConfig against a policy.
|
|
22
|
-
* Returns an array of violations (empty = compliant).
|
|
23
|
-
*/
|
|
24
|
-
export function validatePolicy(config, policy, detectorOverrides) {
|
|
25
|
-
const violations = [];
|
|
26
|
-
// Required categories
|
|
27
|
-
if (policy.requiredCategories) {
|
|
28
|
-
const overrides = detectorOverrides ?? config.detectorOverrides ?? {};
|
|
29
|
-
for (const cat of policy.requiredCategories) {
|
|
30
|
-
// Check if any rule for this category is explicitly disabled
|
|
31
|
-
const disabledRules = Object.entries(overrides)
|
|
32
|
-
.filter(([, v]) => v.enabled === false)
|
|
33
|
-
.map(([k]) => k);
|
|
34
|
-
// This is a simplified check — in practice you'd need to map rules to categories
|
|
35
|
-
if (disabledRules.length > 0) {
|
|
36
|
-
// Log a warning, not an error, since we can't easily map rule→category here
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
// Minimum confidence
|
|
41
|
-
if (policy.minConfidence !== undefined && config.minConfidence < policy.minConfidence) {
|
|
42
|
-
violations.push({
|
|
43
|
-
rule: "minConfidence",
|
|
44
|
-
severity: "error",
|
|
45
|
-
message: `Global minConfidence (${config.minConfidence}) is below policy minimum (${policy.minConfidence})`,
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
// Maximum confidence
|
|
49
|
-
if (policy.maxConfidence !== undefined && config.minConfidence > policy.maxConfidence) {
|
|
50
|
-
violations.push({
|
|
51
|
-
rule: "maxConfidence",
|
|
52
|
-
severity: "warning",
|
|
53
|
-
message: `Global minConfidence (${config.minConfidence}) exceeds policy maximum (${policy.maxConfidence}) — may over-filter`,
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
// Mandatory denylist
|
|
57
|
-
if (policy.mandatoryDenylist) {
|
|
58
|
-
for (const entry of policy.mandatoryDenylist) {
|
|
59
|
-
if (!config.denylist.includes(entry)) {
|
|
60
|
-
violations.push({
|
|
61
|
-
rule: "mandatoryDenylist",
|
|
62
|
-
severity: "error",
|
|
63
|
-
message: `Required denylist entry missing: "${entry}"`,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Prohibited allowlist
|
|
69
|
-
if (policy.prohibitedAllowlist) {
|
|
70
|
-
for (const entry of policy.prohibitedAllowlist) {
|
|
71
|
-
if (config.allowlist.includes(entry)) {
|
|
72
|
-
violations.push({
|
|
73
|
-
rule: "prohibitedAllowlist",
|
|
74
|
-
severity: "error",
|
|
75
|
-
message: `Prohibited allowlist entry found: "${entry}"`,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
// Audit required
|
|
81
|
-
if (policy.requireAudit && !config.auditEnabled) {
|
|
82
|
-
violations.push({
|
|
83
|
-
rule: "requireAudit",
|
|
84
|
-
severity: "error",
|
|
85
|
-
message: "Policy requires audit logging to be enabled",
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
// Canary required
|
|
89
|
-
if (policy.requireCanary && !config.canaryEnabled) {
|
|
90
|
-
violations.push({
|
|
91
|
-
rule: "requireCanary",
|
|
92
|
-
severity: "warning",
|
|
93
|
-
message: "Policy recommends canary token injection",
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
// Allowed redaction levels
|
|
97
|
-
if (policy.allowedRedactionLevels && !policy.allowedRedactionLevels.includes(config.redactionLevel)) {
|
|
98
|
-
violations.push({
|
|
99
|
-
rule: "redactionLevel",
|
|
100
|
-
severity: "error",
|
|
101
|
-
message: `Redaction level "${config.redactionLevel}" is not allowed by policy. Allowed: ${policy.allowedRedactionLevels.join(", ")}`,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
return violations;
|
|
105
|
-
}
|
package/dist/siem.d.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SIEM webhook forwarder — batch and async delivery of audit events.
|
|
3
|
-
* Does not block obfuscation pipeline.
|
|
4
|
-
*/
|
|
5
|
-
export interface SiemConfig {
|
|
6
|
-
/** Webhook URL to send audit events to. Empty = disabled. */
|
|
7
|
-
webhookUrl: string;
|
|
8
|
-
/** Custom headers (e.g., Authorization). */
|
|
9
|
-
headers: Record<string, string>;
|
|
10
|
-
/** Max events to batch before flushing. Default: 10. */
|
|
11
|
-
batchSize: number;
|
|
12
|
-
/** Max time (ms) to hold events before flushing. Default: 5000. */
|
|
13
|
-
flushIntervalMs: number;
|
|
14
|
-
/** Format: "json" (array of events) or "ndjson" (newline-delimited). */
|
|
15
|
-
format: "json" | "ndjson";
|
|
16
|
-
}
|
|
17
|
-
export declare const DEFAULT_SIEM_CONFIG: SiemConfig;
|
|
18
|
-
export declare class SiemForwarder {
|
|
19
|
-
private _config;
|
|
20
|
-
private _buffer;
|
|
21
|
-
private _timer;
|
|
22
|
-
private _sendCount;
|
|
23
|
-
private _errorCount;
|
|
24
|
-
private _lastError;
|
|
25
|
-
constructor(config?: Partial<SiemConfig>);
|
|
26
|
-
get enabled(): boolean;
|
|
27
|
-
/** Queue an audit event for delivery. */
|
|
28
|
-
push(event: Record<string, unknown>): void;
|
|
29
|
-
/** Flush buffered events to the webhook. */
|
|
30
|
-
flush(): void;
|
|
31
|
-
/** Stop the forwarder and flush remaining events. */
|
|
32
|
-
stop(): void;
|
|
33
|
-
getStats(): object;
|
|
34
|
-
private _sendHttp;
|
|
35
|
-
}
|
package/dist/siem.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SIEM webhook forwarder — batch and async delivery of audit events.
|
|
3
|
-
* Does not block obfuscation pipeline.
|
|
4
|
-
*/
|
|
5
|
-
export const DEFAULT_SIEM_CONFIG = {
|
|
6
|
-
webhookUrl: "",
|
|
7
|
-
headers: {},
|
|
8
|
-
batchSize: 10,
|
|
9
|
-
flushIntervalMs: 5000,
|
|
10
|
-
format: "json",
|
|
11
|
-
};
|
|
12
|
-
export class SiemForwarder {
|
|
13
|
-
_config;
|
|
14
|
-
_buffer = [];
|
|
15
|
-
_timer = null;
|
|
16
|
-
_sendCount = 0;
|
|
17
|
-
_errorCount = 0;
|
|
18
|
-
_lastError = null;
|
|
19
|
-
constructor(config = {}) {
|
|
20
|
-
this._config = { ...DEFAULT_SIEM_CONFIG, ...config };
|
|
21
|
-
if (this._config.webhookUrl) {
|
|
22
|
-
this._timer = setInterval(() => this.flush(), this._config.flushIntervalMs);
|
|
23
|
-
if (this._timer.unref)
|
|
24
|
-
this._timer.unref();
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
get enabled() {
|
|
28
|
-
return !!this._config.webhookUrl;
|
|
29
|
-
}
|
|
30
|
-
/** Queue an audit event for delivery. */
|
|
31
|
-
push(event) {
|
|
32
|
-
if (!this.enabled)
|
|
33
|
-
return;
|
|
34
|
-
this._buffer.push({
|
|
35
|
-
...event,
|
|
36
|
-
_ts: new Date().toISOString(),
|
|
37
|
-
_source: "shroud",
|
|
38
|
-
});
|
|
39
|
-
if (this._buffer.length >= this._config.batchSize) {
|
|
40
|
-
this.flush();
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
/** Flush buffered events to the webhook. */
|
|
44
|
-
flush() {
|
|
45
|
-
if (this._buffer.length === 0 || !this.enabled)
|
|
46
|
-
return;
|
|
47
|
-
const events = this._buffer.splice(0);
|
|
48
|
-
const body = this._config.format === "ndjson"
|
|
49
|
-
? events.map((e) => JSON.stringify(e)).join("\n") + "\n"
|
|
50
|
-
: JSON.stringify(events);
|
|
51
|
-
this._sendHttp(body).catch((err) => {
|
|
52
|
-
this._errorCount++;
|
|
53
|
-
this._lastError = String(err);
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
/** Stop the forwarder and flush remaining events. */
|
|
57
|
-
stop() {
|
|
58
|
-
if (this._timer) {
|
|
59
|
-
clearInterval(this._timer);
|
|
60
|
-
this._timer = null;
|
|
61
|
-
}
|
|
62
|
-
this.flush();
|
|
63
|
-
}
|
|
64
|
-
getStats() {
|
|
65
|
-
return {
|
|
66
|
-
enabled: this.enabled,
|
|
67
|
-
buffered: this._buffer.length,
|
|
68
|
-
sent: this._sendCount,
|
|
69
|
-
errors: this._errorCount,
|
|
70
|
-
lastError: this._lastError,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
async _sendHttp(body) {
|
|
74
|
-
const contentType = this._config.format === "ndjson"
|
|
75
|
-
? "application/x-ndjson"
|
|
76
|
-
: "application/json";
|
|
77
|
-
const resp = await fetch(this._config.webhookUrl, {
|
|
78
|
-
method: "POST",
|
|
79
|
-
headers: {
|
|
80
|
-
"Content-Type": contentType,
|
|
81
|
-
"User-Agent": "shroud-siem/1.0",
|
|
82
|
-
...this._config.headers,
|
|
83
|
-
},
|
|
84
|
-
body,
|
|
85
|
-
});
|
|
86
|
-
if (!resp.ok) {
|
|
87
|
-
throw new Error(`SIEM webhook returned ${resp.status}`);
|
|
88
|
-
}
|
|
89
|
-
this._sendCount += 1;
|
|
90
|
-
}
|
|
91
|
-
}
|
package/dist/store-file.d.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File-backed mapping store — persists to JSON on disk.
|
|
3
|
-
* Wraps MemoryStore with periodic flush and load-on-startup.
|
|
4
|
-
*/
|
|
5
|
-
import { Category } from "./types.js";
|
|
6
|
-
import { MappingStore } from "./store.js";
|
|
7
|
-
export declare class FileBackedStore implements MappingStore {
|
|
8
|
-
private _inner;
|
|
9
|
-
private _filePath;
|
|
10
|
-
private _dirty;
|
|
11
|
-
private _flushTimer;
|
|
12
|
-
private _flushIntervalMs;
|
|
13
|
-
constructor(filePath: string, maxSize?: number, flushIntervalMs?: number);
|
|
14
|
-
put(real: string, fake: string, category: Category): void;
|
|
15
|
-
getFake(real: string): string | undefined;
|
|
16
|
-
getReal(fake: string): string | undefined;
|
|
17
|
-
getCategory(real: string): Category | undefined;
|
|
18
|
-
allMappings(): Map<string, string>;
|
|
19
|
-
size(): number;
|
|
20
|
-
clear(): void;
|
|
21
|
-
/** Flush dirty mappings to disk. */
|
|
22
|
-
flush(): void;
|
|
23
|
-
/** Stop the periodic flush timer. */
|
|
24
|
-
stop(): void;
|
|
25
|
-
private _loadFromDisk;
|
|
26
|
-
}
|
package/dist/store-file.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* File-backed mapping store — persists to JSON on disk.
|
|
3
|
-
* Wraps MemoryStore with periodic flush and load-on-startup.
|
|
4
|
-
*/
|
|
5
|
-
import { writeFileSync, readFileSync, existsSync, mkdirSync } from "node:fs";
|
|
6
|
-
import { dirname } from "node:path";
|
|
7
|
-
import { MemoryStore } from "./store.js";
|
|
8
|
-
export class FileBackedStore {
|
|
9
|
-
_inner;
|
|
10
|
-
_filePath;
|
|
11
|
-
_dirty = false;
|
|
12
|
-
_flushTimer = null;
|
|
13
|
-
_flushIntervalMs;
|
|
14
|
-
constructor(filePath, maxSize = 0, flushIntervalMs = 5000) {
|
|
15
|
-
this._inner = new MemoryStore(maxSize);
|
|
16
|
-
this._filePath = filePath;
|
|
17
|
-
this._flushIntervalMs = flushIntervalMs;
|
|
18
|
-
// Ensure directory exists
|
|
19
|
-
const dir = dirname(filePath);
|
|
20
|
-
if (!existsSync(dir)) {
|
|
21
|
-
mkdirSync(dir, { recursive: true });
|
|
22
|
-
}
|
|
23
|
-
// Load existing data
|
|
24
|
-
this._loadFromDisk();
|
|
25
|
-
// Periodic flush
|
|
26
|
-
this._flushTimer = setInterval(() => this.flush(), this._flushIntervalMs);
|
|
27
|
-
if (this._flushTimer.unref)
|
|
28
|
-
this._flushTimer.unref();
|
|
29
|
-
}
|
|
30
|
-
put(real, fake, category) {
|
|
31
|
-
this._inner.put(real, fake, category);
|
|
32
|
-
this._dirty = true;
|
|
33
|
-
}
|
|
34
|
-
getFake(real) { return this._inner.getFake(real); }
|
|
35
|
-
getReal(fake) { return this._inner.getReal(fake); }
|
|
36
|
-
getCategory(real) { return this._inner.getCategory(real); }
|
|
37
|
-
allMappings() { return this._inner.allMappings(); }
|
|
38
|
-
size() { return this._inner.size(); }
|
|
39
|
-
clear() {
|
|
40
|
-
this._inner.clear();
|
|
41
|
-
this._dirty = true;
|
|
42
|
-
this.flush();
|
|
43
|
-
}
|
|
44
|
-
/** Flush dirty mappings to disk. */
|
|
45
|
-
flush() {
|
|
46
|
-
if (!this._dirty)
|
|
47
|
-
return;
|
|
48
|
-
try {
|
|
49
|
-
const data = this._inner.export("", undefined);
|
|
50
|
-
writeFileSync(this._filePath, JSON.stringify(data, null, 2) + "\n");
|
|
51
|
-
this._dirty = false;
|
|
52
|
-
}
|
|
53
|
-
catch (e) {
|
|
54
|
-
// best-effort — log but don't crash
|
|
55
|
-
process.stderr.write(`[shroud-store] Flush failed: ${e.message}\n`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
/** Stop the periodic flush timer. */
|
|
59
|
-
stop() {
|
|
60
|
-
if (this._flushTimer) {
|
|
61
|
-
clearInterval(this._flushTimer);
|
|
62
|
-
this._flushTimer = null;
|
|
63
|
-
}
|
|
64
|
-
this.flush(); // final flush
|
|
65
|
-
}
|
|
66
|
-
_loadFromDisk() {
|
|
67
|
-
if (!existsSync(this._filePath))
|
|
68
|
-
return;
|
|
69
|
-
try {
|
|
70
|
-
const raw = readFileSync(this._filePath, "utf-8");
|
|
71
|
-
const data = JSON.parse(raw);
|
|
72
|
-
this._inner.import(data);
|
|
73
|
-
process.stderr.write(`[shroud-store] Loaded ${data.mappings.length} mappings from ${this._filePath}\n`);
|
|
74
|
-
}
|
|
75
|
-
catch (e) {
|
|
76
|
-
process.stderr.write(`[shroud-store] Load failed: ${e.message}\n`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|