shroud-privacy 2.0.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.
Files changed (45) hide show
  1. package/LICENSE +190 -0
  2. package/NOTICE +7 -0
  3. package/README.md +369 -0
  4. package/dist/audit.d.ts +46 -0
  5. package/dist/audit.js +127 -0
  6. package/dist/canary.d.ts +31 -0
  7. package/dist/canary.js +73 -0
  8. package/dist/config.d.ts +27 -0
  9. package/dist/config.js +123 -0
  10. package/dist/detectors/base.d.ts +8 -0
  11. package/dist/detectors/base.js +2 -0
  12. package/dist/detectors/code.d.ts +25 -0
  13. package/dist/detectors/code.js +144 -0
  14. package/dist/detectors/context.d.ts +31 -0
  15. package/dist/detectors/context.js +357 -0
  16. package/dist/detectors/patterns.d.ts +15 -0
  17. package/dist/detectors/patterns.js +58 -0
  18. package/dist/detectors/regex.d.ts +28 -0
  19. package/dist/detectors/regex.js +955 -0
  20. package/dist/generators/base.d.ts +6 -0
  21. package/dist/generators/base.js +2 -0
  22. package/dist/generators/codes.d.ts +20 -0
  23. package/dist/generators/codes.js +231 -0
  24. package/dist/generators/names.d.ts +29 -0
  25. package/dist/generators/names.js +194 -0
  26. package/dist/generators/network.d.ts +86 -0
  27. package/dist/generators/network.js +477 -0
  28. package/dist/hooks.d.ts +27 -0
  29. package/dist/hooks.js +457 -0
  30. package/dist/index.d.ts +12 -0
  31. package/dist/index.js +58 -0
  32. package/dist/mapping.d.ts +33 -0
  33. package/dist/mapping.js +72 -0
  34. package/dist/obfuscator.d.ts +78 -0
  35. package/dist/obfuscator.js +603 -0
  36. package/dist/redaction.d.ts +26 -0
  37. package/dist/redaction.js +76 -0
  38. package/dist/store.d.ts +40 -0
  39. package/dist/store.js +79 -0
  40. package/dist/types.d.ts +101 -0
  41. package/dist/types.js +35 -0
  42. package/ncg_adapter.py +530 -0
  43. package/openclaw.plugin.json +72 -0
  44. package/package.json +56 -0
  45. package/shroud_bridge.mjs +225 -0
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Redaction level formatter.
3
+ *
4
+ * Three modes:
5
+ * - full: fake values (current behavior, default)
6
+ * - masked: partial masking (first 2 + *** + last 2)
7
+ * - stats: category placeholders like [HOSTNAME-1]
8
+ */
9
+ import { Category } from "./types.js";
10
+ export type RedactionLevel = "full" | "masked" | "stats";
11
+ export declare class RedactionFormatter {
12
+ private _counters;
13
+ /**
14
+ * Format a replacement value according to the redaction level.
15
+ *
16
+ * @param real The real sensitive value
17
+ * @param fake The generated fake value (used in 'full' mode)
18
+ * @param category The entity category
19
+ * @param level The redaction level
20
+ */
21
+ format(real: string, fake: string, category: Category, level: RedactionLevel): string;
22
+ /** Reset counters (call between requests if needed). */
23
+ resetCounters(): void;
24
+ private _mask;
25
+ private _placeholder;
26
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Redaction level formatter.
3
+ *
4
+ * Three modes:
5
+ * - full: fake values (current behavior, default)
6
+ * - masked: partial masking (first 2 + *** + last 2)
7
+ * - stats: category placeholders like [HOSTNAME-1]
8
+ */
9
+ import { Category } from "./types.js";
10
+ export class RedactionFormatter {
11
+ _counters = new Map();
12
+ /**
13
+ * Format a replacement value according to the redaction level.
14
+ *
15
+ * @param real The real sensitive value
16
+ * @param fake The generated fake value (used in 'full' mode)
17
+ * @param category The entity category
18
+ * @param level The redaction level
19
+ */
20
+ format(real, fake, category, level) {
21
+ switch (level) {
22
+ case "full":
23
+ return fake;
24
+ case "masked":
25
+ return this._mask(real, category);
26
+ case "stats":
27
+ return this._placeholder(category);
28
+ }
29
+ }
30
+ /** Reset counters (call between requests if needed). */
31
+ resetCounters() {
32
+ this._counters.clear();
33
+ }
34
+ _mask(value, category) {
35
+ const len = value.length;
36
+ // Short values get fully masked
37
+ if (len <= 4) {
38
+ return "***";
39
+ }
40
+ // Category-aware masking
41
+ switch (category) {
42
+ case Category.EMAIL: {
43
+ const at = value.indexOf("@");
44
+ if (at > 0) {
45
+ return value[0] + "***@***" + value.slice(value.lastIndexOf("."));
46
+ }
47
+ break;
48
+ }
49
+ case Category.PHONE:
50
+ // Show last 4 digits
51
+ return "***" + value.slice(-4);
52
+ case Category.CREDIT_CARD:
53
+ // Show last 4 digits
54
+ return "****-****-****-" + value.slice(-4);
55
+ case Category.SSN:
56
+ return "***-**-" + value.slice(-4);
57
+ case Category.IP_ADDRESS:
58
+ // Mask last two octets
59
+ if (value.includes(".")) {
60
+ const parts = value.split(".");
61
+ return parts[0] + "." + parts[1] + ".*.*";
62
+ }
63
+ break;
64
+ }
65
+ // Default: show first 2 and last 2
66
+ if (len <= 6) {
67
+ return value[0] + "***" + value[len - 1];
68
+ }
69
+ return value.slice(0, 2) + "***" + value.slice(-2);
70
+ }
71
+ _placeholder(category) {
72
+ const count = (this._counters.get(category) ?? 0) + 1;
73
+ this._counters.set(category, count);
74
+ return `[${category.toUpperCase()}-${count}]`;
75
+ }
76
+ }
@@ -0,0 +1,40 @@
1
+ /** Mapping store interface and in-memory implementation. */
2
+ import { Category } from "./types.js";
3
+ /** Abstract store interface for real↔fake mappings. */
4
+ export interface MappingStore {
5
+ put(real: string, fake: string, category: Category): void;
6
+ getFake(real: string): string | undefined;
7
+ getReal(fake: string): string | undefined;
8
+ getCategory(real: string): Category | undefined;
9
+ allMappings(): Map<string, string>;
10
+ size(): number;
11
+ clear(): void;
12
+ }
13
+ /** Serialized form of a mapping store — used for session export/import. */
14
+ export interface SerializedStore {
15
+ mappings: [string, string, string][];
16
+ salt: string;
17
+ tenantId?: string;
18
+ exportedAt: string;
19
+ }
20
+ export declare class MemoryStore implements MappingStore {
21
+ private _realToFake;
22
+ private _fakeToReal;
23
+ private _categories;
24
+ /** Insertion-order list for LRU eviction (oldest first). */
25
+ private _insertionOrder;
26
+ /** Max store size (0 = unlimited). QW10. */
27
+ private _maxSize;
28
+ constructor(maxSize?: number);
29
+ put(real: string, fake: string, category: Category): void;
30
+ getFake(real: string): string | undefined;
31
+ getReal(fake: string): string | undefined;
32
+ getCategory(real: string): Category | undefined;
33
+ allMappings(): Map<string, string>;
34
+ size(): number;
35
+ clear(): void;
36
+ /** Export all mappings for session handoff. */
37
+ export(salt: string, tenantId?: string): SerializedStore;
38
+ /** Import mappings from a session export. */
39
+ import(data: SerializedStore): void;
40
+ }
package/dist/store.js ADDED
@@ -0,0 +1,79 @@
1
+ /** Mapping store interface and in-memory implementation. */
2
+ import { Category } from "./types.js";
3
+ export class MemoryStore {
4
+ _realToFake = new Map();
5
+ _fakeToReal = new Map();
6
+ _categories = new Map();
7
+ /** Insertion-order list for LRU eviction (oldest first). */
8
+ _insertionOrder = [];
9
+ /** Max store size (0 = unlimited). QW10. */
10
+ _maxSize;
11
+ constructor(maxSize = 0) {
12
+ this._maxSize = maxSize;
13
+ }
14
+ put(real, fake, category) {
15
+ // If already present, update in place (no eviction needed)
16
+ if (this._realToFake.has(real)) {
17
+ this._realToFake.set(real, fake);
18
+ this._fakeToReal.set(fake, real);
19
+ this._categories.set(real, category);
20
+ return;
21
+ }
22
+ // QW10: Evict oldest entries if at capacity
23
+ if (this._maxSize > 0) {
24
+ while (this._insertionOrder.length >= this._maxSize) {
25
+ const oldest = this._insertionOrder.shift();
26
+ const oldFake = this._realToFake.get(oldest);
27
+ this._realToFake.delete(oldest);
28
+ if (oldFake !== undefined)
29
+ this._fakeToReal.delete(oldFake);
30
+ this._categories.delete(oldest);
31
+ }
32
+ }
33
+ this._realToFake.set(real, fake);
34
+ this._fakeToReal.set(fake, real);
35
+ this._categories.set(real, category);
36
+ this._insertionOrder.push(real);
37
+ }
38
+ getFake(real) {
39
+ return this._realToFake.get(real);
40
+ }
41
+ getReal(fake) {
42
+ return this._fakeToReal.get(fake);
43
+ }
44
+ getCategory(real) {
45
+ return this._categories.get(real);
46
+ }
47
+ allMappings() {
48
+ return new Map(this._realToFake);
49
+ }
50
+ size() {
51
+ return this._realToFake.size;
52
+ }
53
+ clear() {
54
+ this._realToFake.clear();
55
+ this._fakeToReal.clear();
56
+ this._categories.clear();
57
+ this._insertionOrder = [];
58
+ }
59
+ /** Export all mappings for session handoff. */
60
+ export(salt, tenantId) {
61
+ const mappings = [];
62
+ for (const [real, fake] of this._realToFake) {
63
+ const cat = this._categories.get(real) ?? Category.CUSTOM;
64
+ mappings.push([real, fake, cat]);
65
+ }
66
+ return {
67
+ mappings,
68
+ salt,
69
+ tenantId,
70
+ exportedAt: new Date().toISOString(),
71
+ };
72
+ }
73
+ /** Import mappings from a session export. */
74
+ import(data) {
75
+ for (const [real, fake, cat] of data.mappings) {
76
+ this.put(real, fake, cat);
77
+ }
78
+ }
79
+ }
@@ -0,0 +1,101 @@
1
+ /** Core types for the Shroud OpenClaw plugin. */
2
+ import type { RedactionLevel } from "./redaction.js";
3
+ /** Categories of sensitive information. */
4
+ export declare enum Category {
5
+ PERSON_NAME = "person_name",
6
+ EMAIL = "email",
7
+ PHONE = "phone",
8
+ IP_ADDRESS = "ip_address",
9
+ API_KEY = "api_key",
10
+ URL = "url",
11
+ ORG_NAME = "org_name",
12
+ LOCATION = "location",
13
+ FILE_PATH = "file_path",
14
+ CREDIT_CARD = "credit_card",
15
+ SSN = "ssn",
16
+ MAC_ADDRESS = "mac_address",
17
+ HOSTNAME = "hostname",
18
+ SNMP_COMMUNITY = "snmp_community",
19
+ BGP_ASN = "bgp_asn",
20
+ NETWORK_CREDENTIAL = "network_credential",
21
+ VLAN_ID = "vlan_id",
22
+ INTERFACE_DESC = "interface_desc",
23
+ ROUTE_MAP = "route_map",
24
+ OSPF_ID = "ospf_id",
25
+ ACL_NAME = "acl_name",
26
+ IBAN = "iban",
27
+ NATIONAL_ID = "national_id",
28
+ JWT = "jwt",
29
+ ICS_IDENTIFIER = "ics_identifier",
30
+ GPS_COORDINATE = "gps_coordinate",
31
+ CERTIFICATE = "certificate",
32
+ CUSTOM = "custom"
33
+ }
34
+ /** A detected sensitive entity in text. */
35
+ export interface DetectedEntity {
36
+ value: string;
37
+ start: number;
38
+ end: number;
39
+ category: Category;
40
+ confidence: number;
41
+ detector: string;
42
+ }
43
+ /** Result of obfuscating text. */
44
+ export interface ObfuscationResult {
45
+ original: string;
46
+ obfuscated: string;
47
+ entities: DetectedEntity[];
48
+ mappingsUsed: Record<string, string>;
49
+ /** Filtering stats — how many entities were skipped and why. */
50
+ filterStats?: FilterStats;
51
+ }
52
+ /** Breakdown of skipped/filtered entities during obfuscation. */
53
+ export interface FilterStats {
54
+ /** Total entities detected before filtering. */
55
+ totalDetected: number;
56
+ /** Entities that passed filtering and were replaced (or would be in dryRun). */
57
+ replaced: number;
58
+ /** Entities skipped because confidence < minConfidence. */
59
+ belowThreshold: number;
60
+ /** Entities skipped by allowlist. */
61
+ allowlisted: number;
62
+ /** Entities skipped because they are doc/example values. */
63
+ docExamples: number;
64
+ /** Entities skipped because they are already-known fakes. */
65
+ alreadyObfuscated: number;
66
+ }
67
+ /** Configuration for the Shroud plugin. */
68
+ export interface ShroudConfig {
69
+ secretKey: string;
70
+ persistentSalt: string;
71
+ minConfidence: number;
72
+ allowlist: string[];
73
+ denylist: string[];
74
+ canaryEnabled: boolean;
75
+ canaryPrefix: string;
76
+ auditEnabled: boolean;
77
+ logMappings: boolean;
78
+ customPatterns: Array<{
79
+ name: string;
80
+ pattern: string;
81
+ category?: string;
82
+ }>;
83
+ verboseLogging: boolean;
84
+ auditLogFormat: "human" | "json";
85
+ auditIncludeProofHashes: boolean;
86
+ auditHashSalt: string;
87
+ auditHashTruncate: number;
88
+ auditMaxFakesSample: number;
89
+ detectorOverrides: Record<string, {
90
+ enabled?: boolean;
91
+ confidence?: number;
92
+ }>;
93
+ /** Tool chain depth awareness — max depth before warning. */
94
+ maxToolDepth: number;
95
+ /** Redaction levels — 'full' | 'masked' | 'stats'. */
96
+ redactionLevel: RedactionLevel;
97
+ /** Dry-run mode: detect entities but don't replace them. */
98
+ dryRun: boolean;
99
+ /** Max mapping store size; oldest entries evicted when exceeded. 0 = unlimited. */
100
+ maxStoreMappings: number;
101
+ }
package/dist/types.js ADDED
@@ -0,0 +1,35 @@
1
+ /** Core types for the Shroud OpenClaw plugin. */
2
+ /** Categories of sensitive information. */
3
+ export var Category;
4
+ (function (Category) {
5
+ Category["PERSON_NAME"] = "person_name";
6
+ Category["EMAIL"] = "email";
7
+ Category["PHONE"] = "phone";
8
+ Category["IP_ADDRESS"] = "ip_address";
9
+ Category["API_KEY"] = "api_key";
10
+ Category["URL"] = "url";
11
+ Category["ORG_NAME"] = "org_name";
12
+ Category["LOCATION"] = "location";
13
+ Category["FILE_PATH"] = "file_path";
14
+ Category["CREDIT_CARD"] = "credit_card";
15
+ Category["SSN"] = "ssn";
16
+ Category["MAC_ADDRESS"] = "mac_address";
17
+ Category["HOSTNAME"] = "hostname";
18
+ Category["SNMP_COMMUNITY"] = "snmp_community";
19
+ Category["BGP_ASN"] = "bgp_asn";
20
+ Category["NETWORK_CREDENTIAL"] = "network_credential";
21
+ // Network infrastructure identifiers
22
+ Category["VLAN_ID"] = "vlan_id";
23
+ Category["INTERFACE_DESC"] = "interface_desc";
24
+ Category["ROUTE_MAP"] = "route_map";
25
+ Category["OSPF_ID"] = "ospf_id";
26
+ Category["ACL_NAME"] = "acl_name";
27
+ // Regulated / enterprise categories
28
+ Category["IBAN"] = "iban";
29
+ Category["NATIONAL_ID"] = "national_id";
30
+ Category["JWT"] = "jwt";
31
+ Category["ICS_IDENTIFIER"] = "ics_identifier";
32
+ Category["GPS_COORDINATE"] = "gps_coordinate";
33
+ Category["CERTIFICATE"] = "certificate";
34
+ Category["CUSTOM"] = "custom";
35
+ })(Category || (Category = {}));