@upx-us/shield 0.2.12-beta

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.

Potentially problematic release.


This version of @upx-us/shield might be problematic. Click here for more details.

Files changed (159) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +96 -0
  3. package/dist/index.d.ts +43 -0
  4. package/dist/index.js +365 -0
  5. package/dist/src/config.d.ts +43 -0
  6. package/dist/src/config.js +181 -0
  7. package/dist/src/events/base.d.ts +110 -0
  8. package/dist/src/events/base.js +61 -0
  9. package/dist/src/events/browser/enrich.d.ts +3 -0
  10. package/dist/src/events/browser/enrich.js +46 -0
  11. package/dist/src/events/browser/event.d.ts +10 -0
  12. package/dist/src/events/browser/event.js +2 -0
  13. package/dist/src/events/browser/index.d.ts +4 -0
  14. package/dist/src/events/browser/index.js +13 -0
  15. package/dist/src/events/browser/redactions.d.ts +2 -0
  16. package/dist/src/events/browser/redactions.js +4 -0
  17. package/dist/src/events/browser/validations.d.ts +3 -0
  18. package/dist/src/events/browser/validations.js +10 -0
  19. package/dist/src/events/cron/enrich.d.ts +3 -0
  20. package/dist/src/events/cron/enrich.js +44 -0
  21. package/dist/src/events/cron/event.d.ts +5 -0
  22. package/dist/src/events/cron/event.js +2 -0
  23. package/dist/src/events/cron/index.d.ts +4 -0
  24. package/dist/src/events/cron/index.js +13 -0
  25. package/dist/src/events/cron/redactions.d.ts +2 -0
  26. package/dist/src/events/cron/redactions.js +4 -0
  27. package/dist/src/events/cron/validations.d.ts +3 -0
  28. package/dist/src/events/cron/validations.js +4 -0
  29. package/dist/src/events/exec/enrich.d.ts +3 -0
  30. package/dist/src/events/exec/enrich.js +80 -0
  31. package/dist/src/events/exec/event.d.ts +11 -0
  32. package/dist/src/events/exec/event.js +2 -0
  33. package/dist/src/events/exec/index.d.ts +4 -0
  34. package/dist/src/events/exec/index.js +13 -0
  35. package/dist/src/events/exec/redactions.d.ts +3 -0
  36. package/dist/src/events/exec/redactions.js +12 -0
  37. package/dist/src/events/exec/validations.d.ts +3 -0
  38. package/dist/src/events/exec/validations.js +12 -0
  39. package/dist/src/events/file/enrich.d.ts +3 -0
  40. package/dist/src/events/file/enrich.js +63 -0
  41. package/dist/src/events/file/event.d.ts +11 -0
  42. package/dist/src/events/file/event.js +2 -0
  43. package/dist/src/events/file/index.d.ts +4 -0
  44. package/dist/src/events/file/index.js +13 -0
  45. package/dist/src/events/file/redactions.d.ts +2 -0
  46. package/dist/src/events/file/redactions.js +8 -0
  47. package/dist/src/events/file/validations.d.ts +3 -0
  48. package/dist/src/events/file/validations.js +10 -0
  49. package/dist/src/events/gateway/enrich.d.ts +3 -0
  50. package/dist/src/events/gateway/enrich.js +50 -0
  51. package/dist/src/events/gateway/event.d.ts +5 -0
  52. package/dist/src/events/gateway/event.js +2 -0
  53. package/dist/src/events/gateway/index.d.ts +4 -0
  54. package/dist/src/events/gateway/index.js +13 -0
  55. package/dist/src/events/gateway/redactions.d.ts +2 -0
  56. package/dist/src/events/gateway/redactions.js +4 -0
  57. package/dist/src/events/gateway/validations.d.ts +3 -0
  58. package/dist/src/events/gateway/validations.js +4 -0
  59. package/dist/src/events/generic/enrich.d.ts +3 -0
  60. package/dist/src/events/generic/enrich.js +30 -0
  61. package/dist/src/events/generic/event.d.ts +5 -0
  62. package/dist/src/events/generic/event.js +2 -0
  63. package/dist/src/events/generic/index.d.ts +5 -0
  64. package/dist/src/events/generic/index.js +14 -0
  65. package/dist/src/events/generic/redactions.d.ts +2 -0
  66. package/dist/src/events/generic/redactions.js +4 -0
  67. package/dist/src/events/generic/validations.d.ts +3 -0
  68. package/dist/src/events/generic/validations.js +4 -0
  69. package/dist/src/events/host-telemetry/enrich.d.ts +3 -0
  70. package/dist/src/events/host-telemetry/enrich.js +28 -0
  71. package/dist/src/events/host-telemetry/event.d.ts +4 -0
  72. package/dist/src/events/host-telemetry/event.js +2 -0
  73. package/dist/src/events/host-telemetry/index.d.ts +4 -0
  74. package/dist/src/events/host-telemetry/index.js +13 -0
  75. package/dist/src/events/host-telemetry/redactions.d.ts +2 -0
  76. package/dist/src/events/host-telemetry/redactions.js +4 -0
  77. package/dist/src/events/host-telemetry/validations.d.ts +3 -0
  78. package/dist/src/events/host-telemetry/validations.js +4 -0
  79. package/dist/src/events/index.d.ts +40 -0
  80. package/dist/src/events/index.js +39 -0
  81. package/dist/src/events/message/enrich.d.ts +3 -0
  82. package/dist/src/events/message/enrich.js +36 -0
  83. package/dist/src/events/message/event.d.ts +5 -0
  84. package/dist/src/events/message/event.js +2 -0
  85. package/dist/src/events/message/index.d.ts +4 -0
  86. package/dist/src/events/message/index.js +13 -0
  87. package/dist/src/events/message/redactions.d.ts +2 -0
  88. package/dist/src/events/message/redactions.js +4 -0
  89. package/dist/src/events/message/validations.d.ts +3 -0
  90. package/dist/src/events/message/validations.js +7 -0
  91. package/dist/src/events/sessions-spawn/enrich.d.ts +3 -0
  92. package/dist/src/events/sessions-spawn/enrich.js +40 -0
  93. package/dist/src/events/sessions-spawn/event.d.ts +9 -0
  94. package/dist/src/events/sessions-spawn/event.js +2 -0
  95. package/dist/src/events/sessions-spawn/index.d.ts +4 -0
  96. package/dist/src/events/sessions-spawn/index.js +13 -0
  97. package/dist/src/events/sessions-spawn/redactions.d.ts +2 -0
  98. package/dist/src/events/sessions-spawn/redactions.js +4 -0
  99. package/dist/src/events/sessions-spawn/validations.d.ts +3 -0
  100. package/dist/src/events/sessions-spawn/validations.js +4 -0
  101. package/dist/src/events/tool-result/enrich.d.ts +13 -0
  102. package/dist/src/events/tool-result/enrich.js +46 -0
  103. package/dist/src/events/tool-result/event.d.ts +7 -0
  104. package/dist/src/events/tool-result/event.js +2 -0
  105. package/dist/src/events/tool-result/index.d.ts +4 -0
  106. package/dist/src/events/tool-result/index.js +9 -0
  107. package/dist/src/events/tool-result/redactions.d.ts +2 -0
  108. package/dist/src/events/tool-result/redactions.js +7 -0
  109. package/dist/src/events/tool-result/validations.d.ts +3 -0
  110. package/dist/src/events/tool-result/validations.js +9 -0
  111. package/dist/src/events/web/enrich.d.ts +8 -0
  112. package/dist/src/events/web/enrich.js +78 -0
  113. package/dist/src/events/web/event.d.ts +10 -0
  114. package/dist/src/events/web/event.js +2 -0
  115. package/dist/src/events/web/index.d.ts +4 -0
  116. package/dist/src/events/web/index.js +13 -0
  117. package/dist/src/events/web/redactions.d.ts +2 -0
  118. package/dist/src/events/web/redactions.js +6 -0
  119. package/dist/src/events/web/validations.d.ts +3 -0
  120. package/dist/src/events/web/validations.js +10 -0
  121. package/dist/src/fetcher.d.ts +12 -0
  122. package/dist/src/fetcher.js +182 -0
  123. package/dist/src/host-collector.d.ts +1 -0
  124. package/dist/src/host-collector.js +200 -0
  125. package/dist/src/index.d.ts +1 -0
  126. package/dist/src/index.js +210 -0
  127. package/dist/src/log.d.ts +39 -0
  128. package/dist/src/log.js +102 -0
  129. package/dist/src/redactor/base.d.ts +29 -0
  130. package/dist/src/redactor/base.js +9 -0
  131. package/dist/src/redactor/index.d.ts +27 -0
  132. package/dist/src/redactor/index.js +109 -0
  133. package/dist/src/redactor/strategies/command.d.ts +2 -0
  134. package/dist/src/redactor/strategies/command.js +19 -0
  135. package/dist/src/redactor/strategies/hostname.d.ts +2 -0
  136. package/dist/src/redactor/strategies/hostname.js +15 -0
  137. package/dist/src/redactor/strategies/index.d.ts +13 -0
  138. package/dist/src/redactor/strategies/index.js +25 -0
  139. package/dist/src/redactor/strategies/path.d.ts +2 -0
  140. package/dist/src/redactor/strategies/path.js +23 -0
  141. package/dist/src/redactor/strategies/secret-key.d.ts +2 -0
  142. package/dist/src/redactor/strategies/secret-key.js +22 -0
  143. package/dist/src/redactor/strategies/username.d.ts +2 -0
  144. package/dist/src/redactor/strategies/username.js +12 -0
  145. package/dist/src/redactor/vault.d.ts +25 -0
  146. package/dist/src/redactor/vault.js +209 -0
  147. package/dist/src/sender.d.ts +29 -0
  148. package/dist/src/sender.js +186 -0
  149. package/dist/src/setup.d.ts +10 -0
  150. package/dist/src/setup.js +222 -0
  151. package/dist/src/transformer.d.ts +26 -0
  152. package/dist/src/transformer.js +302 -0
  153. package/dist/src/validator.d.ts +17 -0
  154. package/dist/src/validator.js +110 -0
  155. package/dist/src/version.d.ts +1 -0
  156. package/dist/src/version.js +19 -0
  157. package/openclaw.plugin.json +52 -0
  158. package/package.json +64 -0
  159. package/skills/shield/SKILL.md +38 -0
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ /**
3
+ * src/redactor/index.ts — Strategy engine, event redaction, and public API.
4
+ *
5
+ * This module wires vault + strategies into a single redaction engine.
6
+ * All imports of './redactor' resolve here (TypeScript: directory + index.ts).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.applyFieldRedaction = applyFieldRedaction;
10
+ exports.redactEvent = redactEvent;
11
+ exports.redactUsername = redactUsername;
12
+ exports.redactHostname = redactHostname;
13
+ exports.redactPath = redactPath;
14
+ exports.redactCommand = redactCommand;
15
+ exports.init = init;
16
+ exports.flush = flush;
17
+ exports.reverseLookup = reverseLookup;
18
+ exports.getAllMappings = getAllMappings;
19
+ exports._initForTesting = _initForTesting;
20
+ const strategies_1 = require("./strategies");
21
+ const vault_1 = require("./vault");
22
+ const events_1 = require("../events");
23
+ const base_1 = require("../events/base");
24
+ // ─── Strategy Registry ────────────────────────────────────────────────────────
25
+ const strategyMap = new Map(strategies_1.strategies.map(s => [s.key, s]));
26
+ // ─── Engine ───────────────────────────────────────────────────────────────────
27
+ /**
28
+ * Apply a single FieldRedaction rule to an object via dot-notation path.
29
+ * Missing fields and null values are silently skipped.
30
+ * Unknown strategy keys throw a descriptive error.
31
+ */
32
+ function applyFieldRedaction(obj, rule) {
33
+ const strategy = strategyMap.get(rule.strategy);
34
+ if (!strategy)
35
+ throw new Error(`[redactor] Unknown redaction strategy: '${rule.strategy}'. Register it in src/redactor/strategies/index.ts`);
36
+ const parts = rule.path.split('.');
37
+ let cur = obj;
38
+ for (let i = 0; i < parts.length - 1; i++) {
39
+ if (cur == null || typeof cur !== 'object')
40
+ return;
41
+ cur = cur[parts[i]];
42
+ }
43
+ const last = parts[parts.length - 1];
44
+ if (cur == null || typeof cur !== 'object' || cur[last] == null)
45
+ return;
46
+ cur[last] = strategy.redact(String(cur[last]), vault_1.hmacHash);
47
+ }
48
+ // ─── High-Level API ───────────────────────────────────────────────────────────
49
+ /**
50
+ * Deep-redact a Shield envelope. Returns a new object (does not mutate input).
51
+ * Applies base redactions + schema-specific redactions based on tool_category.
52
+ */
53
+ function redactEvent(envelope) {
54
+ const e = JSON.parse(JSON.stringify(envelope));
55
+ // Source-level: hostname in the source block always gets redacted
56
+ if (e.source?.hostname) {
57
+ const hs = strategyMap.get('hostname');
58
+ if (hs)
59
+ e.source.hostname = hs.redact(e.source.hostname, vault_1.hmacHash);
60
+ }
61
+ if (!e.event)
62
+ return e;
63
+ // Base redactions (principal.hostname, principal.user) — always apply
64
+ for (const rule of base_1.baseRedactions) {
65
+ applyFieldRedaction(e.event, rule);
66
+ }
67
+ // Schema-specific redactions via tool_category discriminator
68
+ const category = e.event.tool_category;
69
+ if (category) {
70
+ const schema = events_1.schemas.find(s => s.category === category);
71
+ if (schema) {
72
+ for (const rule of schema.redactions) {
73
+ applyFieldRedaction(e.event, rule);
74
+ }
75
+ }
76
+ }
77
+ return e;
78
+ }
79
+ // ─── Individual Strategy Functions (backwards-compat + direct use) ────────────
80
+ function redactUsername(value) {
81
+ const s = strategyMap.get('username');
82
+ return s ? s.redact(value, vault_1.hmacHash) : value;
83
+ }
84
+ function redactHostname(value) {
85
+ const s = strategyMap.get('hostname');
86
+ return s ? s.redact(value, vault_1.hmacHash) : value;
87
+ }
88
+ function redactPath(value) {
89
+ const s = strategyMap.get('path');
90
+ return s ? s.redact(value, vault_1.hmacHash) : value;
91
+ }
92
+ function redactCommand(value) {
93
+ const s = strategyMap.get('command');
94
+ return s ? s.redact(value, vault_1.hmacHash) : value;
95
+ }
96
+ // ─── Lifecycle ────────────────────────────────────────────────────────────────
97
+ function init() { (0, vault_1.initVault)(); }
98
+ function flush() { (0, vault_1.flushVault)(); }
99
+ // ─── Reverse Lookup ───────────────────────────────────────────────────────────
100
+ function reverseLookup(token) {
101
+ return (0, vault_1.reverseLookup)(token);
102
+ }
103
+ function getAllMappings() {
104
+ return (0, vault_1.getAllMappings)();
105
+ }
106
+ // ─── Testing ──────────────────────────────────────────────────────────────────
107
+ function _initForTesting(secret) {
108
+ (0, vault_1._initVaultForTesting)(secret);
109
+ }
@@ -0,0 +1,2 @@
1
+ import type { RedactionStrategy } from '../base';
2
+ export declare const commandStrategy: RedactionStrategy;
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.commandStrategy = void 0;
4
+ exports.commandStrategy = {
5
+ key: 'command',
6
+ description: 'Redacts usernames embedded in shell command strings: SSH user@host patterns, /Users/X/ paths, /home/X/ paths. Produces user:HASH tokens.',
7
+ redact(value, hmac) {
8
+ if (!value || value.length === 0)
9
+ return value;
10
+ let result = value;
11
+ // SSH user@host patterns (e.g. ssh alice@192.168.1.1, scp bob@server:/path)
12
+ result = result.replace(/(\b)([\w.-]+)@([\d.]+|[\w.-]+\.\w+)/g, (_, pre, user, host) => `${pre}${hmac('user', user)}@${host}`);
13
+ // macOS home paths
14
+ result = result.replace(/\/Users\/([\w.-]+)\//g, (_, user) => `/Users/${hmac('user', user)}/`);
15
+ // Linux home paths
16
+ result = result.replace(/\/home\/([\w.-]+)\//g, (_, user) => `/home/${hmac('user', user)}/`);
17
+ return result;
18
+ },
19
+ };
@@ -0,0 +1,2 @@
1
+ import type { RedactionStrategy } from '../base';
2
+ export declare const hostnameStrategy: RedactionStrategy;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.hostnameStrategy = void 0;
4
+ exports.hostnameStrategy = {
5
+ key: 'hostname',
6
+ description: 'Redacts machine hostnames. IPv4 addresses are passed through unchanged — they have detection value in security rules. Produces host:HASH tokens.',
7
+ redact(value, hmac) {
8
+ if (!value || value.length === 0)
9
+ return value;
10
+ // IPv4 addresses are not redacted — they have detection value for lateral movement rules
11
+ if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(value))
12
+ return value;
13
+ return hmac('host', value);
14
+ },
15
+ };
@@ -0,0 +1,13 @@
1
+ import { usernameStrategy } from './username';
2
+ import { hostnameStrategy } from './hostname';
3
+ import { pathStrategy } from './path';
4
+ import { commandStrategy } from './command';
5
+ import { secretKeyStrategy } from './secret-key';
6
+ import type { RedactionStrategy } from '../base';
7
+ export { usernameStrategy, hostnameStrategy, pathStrategy, commandStrategy, secretKeyStrategy };
8
+ /**
9
+ * Ordered registry of all redaction strategies.
10
+ * The engine resolves strategies by their `key` field.
11
+ * Order here does not affect execution — strategies are looked up by key.
12
+ */
13
+ export declare const strategies: RedactionStrategy[];
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.strategies = exports.secretKeyStrategy = exports.commandStrategy = exports.pathStrategy = exports.hostnameStrategy = exports.usernameStrategy = void 0;
4
+ const username_1 = require("./username");
5
+ Object.defineProperty(exports, "usernameStrategy", { enumerable: true, get: function () { return username_1.usernameStrategy; } });
6
+ const hostname_1 = require("./hostname");
7
+ Object.defineProperty(exports, "hostnameStrategy", { enumerable: true, get: function () { return hostname_1.hostnameStrategy; } });
8
+ const path_1 = require("./path");
9
+ Object.defineProperty(exports, "pathStrategy", { enumerable: true, get: function () { return path_1.pathStrategy; } });
10
+ const command_1 = require("./command");
11
+ Object.defineProperty(exports, "commandStrategy", { enumerable: true, get: function () { return command_1.commandStrategy; } });
12
+ const secret_key_1 = require("./secret-key");
13
+ Object.defineProperty(exports, "secretKeyStrategy", { enumerable: true, get: function () { return secret_key_1.secretKeyStrategy; } });
14
+ /**
15
+ * Ordered registry of all redaction strategies.
16
+ * The engine resolves strategies by their `key` field.
17
+ * Order here does not affect execution — strategies are looked up by key.
18
+ */
19
+ exports.strategies = [
20
+ username_1.usernameStrategy,
21
+ hostname_1.hostnameStrategy,
22
+ path_1.pathStrategy,
23
+ command_1.commandStrategy,
24
+ secret_key_1.secretKeyStrategy,
25
+ ];
@@ -0,0 +1,2 @@
1
+ import type { RedactionStrategy } from '../base';
2
+ export declare const pathStrategy: RedactionStrategy;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pathStrategy = void 0;
4
+ const HOME_PATTERNS = [
5
+ [/^(\/Users\/)([\w.-]+)(\/.*|$)/, '$1', '$3'], // macOS
6
+ [/^(\/home\/)([\w.-]+)(\/.*|$)/, '$1', '$3'], // Linux
7
+ [/^(C:\\Users\\)([\w.-]+)(\\.*|$)/i, '$1', '$3'], // Windows
8
+ ];
9
+ exports.pathStrategy = {
10
+ key: 'path',
11
+ description: 'Redacts the username segment in home directory paths (/Users/X/, /home/X/, C:\\Users\\X\\). Preserves the rest of the path for investigation value. Produces user:HASH tokens (same category as username — both identify the same person).',
12
+ redact(value, hmac) {
13
+ if (!value || value.length === 0)
14
+ return value;
15
+ for (const [pattern] of HOME_PATTERNS) {
16
+ const match = value.match(pattern);
17
+ if (match) {
18
+ return `${match[1]}${hmac('user', match[2])}${match[3] ?? ''}`;
19
+ }
20
+ }
21
+ return value;
22
+ },
23
+ };
@@ -0,0 +1,2 @@
1
+ import type { RedactionStrategy } from '../base';
2
+ export declare const secretKeyStrategy: RedactionStrategy;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.secretKeyStrategy = void 0;
4
+ exports.secretKeyStrategy = {
5
+ key: 'secret-key',
6
+ description: 'Detects and redacts API keys, bearer tokens, and secrets in free-text fields (command strings, arguments). Produces secret:HASH tokens.',
7
+ redact(value, hmac) {
8
+ if (!value || value.length === 0)
9
+ return value;
10
+ let result = value;
11
+ // AWS access key IDs (e.g. AKIAIOSFODNN7EXAMPLE)
12
+ // Note: no capture groups — the entire match is the secret
13
+ result = result.replace(/(AKIA[0-9A-Z]{16})/g, (_, key) => hmac('secret', key));
14
+ // CLI flags with secrets: --token=sk_live_xxx, --api-key sk_live_xxx, --password=abc123
15
+ result = result.replace(/(--(?:token|api[_-]?key|secret|password)[=\s]+)(\S+)/gi, (_, flag, secret) => `${flag}${hmac('secret', secret)}`);
16
+ // Bearer tokens in Authorization headers
17
+ result = result.replace(/(Bearer\s+)(\S+)/gi, (_, prefix, token) => `${prefix}${hmac('secret', token)}`);
18
+ // ENV-style assignments: SECRET=value, API_KEY=value, TOKEN=value, PASSWORD=value
19
+ result = result.replace(/((?:SECRET|TOKEN|KEY|PASSWORD|API_KEY)=)(\S+)/gi, (_, prefix, secret) => `${prefix}${hmac('secret', secret)}`);
20
+ return result;
21
+ },
22
+ };
@@ -0,0 +1,2 @@
1
+ import type { RedactionStrategy } from '../base';
2
+ export declare const usernameStrategy: RedactionStrategy;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.usernameStrategy = void 0;
4
+ exports.usernameStrategy = {
5
+ key: 'username',
6
+ description: 'Redacts OS usernames. Produces user:HASH tokens.',
7
+ redact(value, hmac) {
8
+ if (!value || value.length === 0)
9
+ return value;
10
+ return hmac('user', value);
11
+ },
12
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * src/redactor/vault.ts — Key management and encrypted mapping store.
3
+ *
4
+ * The vault owns all I/O. Key lifecycle, map persistence, reverse lookup —
5
+ * everything involving files or crypto state lives here. No other module in
6
+ * the redactor touches the filesystem.
7
+ *
8
+ * Security layers:
9
+ * - File permissions: 0o600 on key and vault files
10
+ * - Encryption at rest: AES-256-GCM with scrypt-derived key
11
+ * - Atomic writes: write to .tmp then rename (prevents mid-write corruption)
12
+ * - In-memory isolation: state fully encapsulated in this module
13
+ * - Test isolation: _initVaultForTesting() uses deterministic secret, no file I/O
14
+ */
15
+ export declare function initVault(): void;
16
+ export declare function flushVault(): void;
17
+ /**
18
+ * Produce a deterministic token for a (category, value) pair and record the
19
+ * reverse mapping. This is the HmacFn injected into every strategy.
20
+ */
21
+ export declare function hmacHash(category: string, value: string): string;
22
+ export declare function reverseLookup(token: string): string | null;
23
+ export declare function getAllMappings(): Record<string, string>;
24
+ /** For testing only — deterministic secret, zero file I/O. */
25
+ export declare function _initVaultForTesting(secret: string): void;
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ /**
3
+ * src/redactor/vault.ts — Key management and encrypted mapping store.
4
+ *
5
+ * The vault owns all I/O. Key lifecycle, map persistence, reverse lookup —
6
+ * everything involving files or crypto state lives here. No other module in
7
+ * the redactor touches the filesystem.
8
+ *
9
+ * Security layers:
10
+ * - File permissions: 0o600 on key and vault files
11
+ * - Encryption at rest: AES-256-GCM with scrypt-derived key
12
+ * - Atomic writes: write to .tmp then rename (prevents mid-write corruption)
13
+ * - In-memory isolation: state fully encapsulated in this module
14
+ * - Test isolation: _initVaultForTesting() uses deterministic secret, no file I/O
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.initVault = initVault;
51
+ exports.flushVault = flushVault;
52
+ exports.hmacHash = hmacHash;
53
+ exports.reverseLookup = reverseLookup;
54
+ exports.getAllMappings = getAllMappings;
55
+ exports._initVaultForTesting = _initVaultForTesting;
56
+ const crypto = __importStar(require("crypto"));
57
+ const fs = __importStar(require("fs"));
58
+ const os = __importStar(require("os"));
59
+ const path = __importStar(require("path"));
60
+ // ─── Paths ────────────────────────────────────────────────────────────────────
61
+ const SHIELD_DATA_DIR = path.join(os.homedir(), '.openclaw', 'shield', 'data');
62
+ const KEY_FILE = path.join(SHIELD_DATA_DIR, 'redaction-key.secret');
63
+ const VAULT_FILE = path.join(SHIELD_DATA_DIR, 'redaction-vault.json');
64
+ const VAULT_TMP = `${VAULT_FILE}.tmp`;
65
+ // Legacy plaintext map (pre-vault) — migrated on first init
66
+ const LEGACY_MAP = path.join(SHIELD_DATA_DIR, 'redaction-map.json');
67
+ // ─── State ────────────────────────────────────────────────────────────────────
68
+ let _secret = null;
69
+ let _map = {};
70
+ let _mapDirty = false;
71
+ let _testMode = false;
72
+ // ─── Crypto Helpers ───────────────────────────────────────────────────────────
73
+ function deriveVaultKey(secret) {
74
+ // Fixed salt is acceptable for v1: the HMAC secret is already random (32 hex bytes),
75
+ // so we're protecting against at-rest reads, not dictionary attacks on the key.
76
+ return crypto.scryptSync(secret, 'shield-vault-salt', 32);
77
+ }
78
+ function encryptMap(data, key) {
79
+ const iv = crypto.randomBytes(16);
80
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
81
+ const encrypted = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);
82
+ return {
83
+ iv: iv.toString('hex'),
84
+ tag: cipher.getAuthTag().toString('hex'),
85
+ ciphertext: encrypted.toString('hex'),
86
+ };
87
+ }
88
+ function decryptMap(env, key) {
89
+ const decipher = crypto.createDecipheriv('aes-256-gcm', key, Buffer.from(env.iv, 'hex'));
90
+ decipher.setAuthTag(Buffer.from(env.tag, 'hex'));
91
+ return decipher.update(env.ciphertext, 'hex', 'utf8') + decipher.final('utf8');
92
+ }
93
+ // ─── Key Management ───────────────────────────────────────────────────────────
94
+ function getSecret() {
95
+ if (_secret)
96
+ return _secret;
97
+ if (!_testMode && fs.existsSync(KEY_FILE)) {
98
+ _secret = fs.readFileSync(KEY_FILE, 'utf8').trim();
99
+ }
100
+ else if (!_testMode) {
101
+ _secret = crypto.randomBytes(32).toString('hex');
102
+ fs.mkdirSync(SHIELD_DATA_DIR, { recursive: true });
103
+ fs.writeFileSync(KEY_FILE, _secret, { mode: 0o600 });
104
+ console.log(`[vault] Generated new HMAC key at ${KEY_FILE}`);
105
+ }
106
+ return _secret;
107
+ }
108
+ // ─── Vault I/O ────────────────────────────────────────────────────────────────
109
+ function loadVault() {
110
+ if (_testMode)
111
+ return;
112
+ // Try primary vault file, fall back to .tmp if primary is missing (crash recovery)
113
+ let fileToLoad = null;
114
+ if (fs.existsSync(VAULT_FILE))
115
+ fileToLoad = VAULT_FILE;
116
+ else if (fs.existsSync(VAULT_TMP)) {
117
+ console.warn('[vault] Primary vault missing, recovering from .tmp file');
118
+ fileToLoad = VAULT_TMP;
119
+ }
120
+ if (fileToLoad) {
121
+ try {
122
+ const raw = fs.readFileSync(fileToLoad, 'utf8');
123
+ const envelope = JSON.parse(raw);
124
+ const key = deriveVaultKey(getSecret());
125
+ const decrypted = decryptMap(envelope, key);
126
+ _map = JSON.parse(decrypted);
127
+ }
128
+ catch (e) {
129
+ console.warn(`[vault] Could not load vault file (${e.message}) — starting fresh`);
130
+ _map = {};
131
+ }
132
+ return;
133
+ }
134
+ // Migrate from legacy plaintext redaction-map.json if it exists.
135
+ // Check both locations: the new data dir AND the old project-root config/ directory
136
+ // (pre-v0.2 bridge stored it at config/redaction-map.json relative to the repo root).
137
+ const OLD_PROJECT_MAP = path.join(__dirname, '..', '..', 'config', 'redaction-map.json');
138
+ const legacyPath = [LEGACY_MAP, OLD_PROJECT_MAP].find(p => fs.existsSync(p));
139
+ if (legacyPath) {
140
+ try {
141
+ const legacy = JSON.parse(fs.readFileSync(legacyPath, 'utf8'));
142
+ const count = Object.keys(legacy).length;
143
+ _map = legacy;
144
+ _mapDirty = true;
145
+ flushVault(); // write encrypted vault immediately
146
+ fs.unlinkSync(legacyPath);
147
+ console.log(`[vault] Migrated ${count} mappings from legacy plaintext map (${legacyPath})`);
148
+ }
149
+ catch (e) {
150
+ console.warn(`[vault] Legacy map migration failed: ${e.message}`);
151
+ _map = {};
152
+ }
153
+ }
154
+ }
155
+ function saveVault() {
156
+ if (!_mapDirty)
157
+ return;
158
+ try {
159
+ fs.mkdirSync(SHIELD_DATA_DIR, { recursive: true });
160
+ const key = deriveVaultKey(getSecret());
161
+ const encrypted = encryptMap(JSON.stringify(_map), key);
162
+ const content = JSON.stringify(encrypted);
163
+ // Atomic write: .tmp first, then rename
164
+ fs.writeFileSync(VAULT_TMP, content, { mode: 0o600 });
165
+ fs.renameSync(VAULT_TMP, VAULT_FILE);
166
+ _mapDirty = false;
167
+ }
168
+ catch (e) {
169
+ console.error(`[vault] Failed to persist vault: ${e.message}`);
170
+ }
171
+ }
172
+ // ─── Public API ───────────────────────────────────────────────────────────────
173
+ function initVault() {
174
+ getSecret();
175
+ loadVault();
176
+ console.log(`[vault] Initialized. ${Object.keys(_map).length} existing mappings.`);
177
+ }
178
+ function flushVault() {
179
+ saveVault();
180
+ }
181
+ /**
182
+ * Produce a deterministic token for a (category, value) pair and record the
183
+ * reverse mapping. This is the HmacFn injected into every strategy.
184
+ */
185
+ function hmacHash(category, value) {
186
+ const hash = crypto.createHmac('sha256', getSecret())
187
+ .update(`${category}:${value}`)
188
+ .digest('hex')
189
+ .substring(0, 12);
190
+ const token = `${category}:${hash}`;
191
+ if (!_map[token]) {
192
+ _map[token] = value;
193
+ _mapDirty = true;
194
+ }
195
+ return token;
196
+ }
197
+ function reverseLookup(token) {
198
+ return _map[token] ?? null;
199
+ }
200
+ function getAllMappings() {
201
+ return { ..._map };
202
+ }
203
+ /** For testing only — deterministic secret, zero file I/O. */
204
+ function _initVaultForTesting(secret) {
205
+ _secret = secret;
206
+ _map = {};
207
+ _mapDirty = false;
208
+ _testMode = true;
209
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * sender.ts — Shield ingest API sender
3
+ *
4
+ * Posts events to the Shield ingest API via HMAC-SHA256 authentication.
5
+ * Events are batched and sent to the configured Shield endpoint only —
6
+ * no cloud provider details are exposed in this module.
7
+ */
8
+ import { EnvelopeEvent } from './transformer';
9
+ import { Config, ShieldCredentials } from './config';
10
+ export declare const REQUEST_TIMEOUT_MS = 30000;
11
+ /** Exported for testing only — computes HMAC with an explicit secret */
12
+ export declare function _signRequestWithSecret(secret: string, instanceId: string, nonce: string): string;
13
+ export interface SendResult {
14
+ success: boolean;
15
+ statusCode?: number;
16
+ body?: string;
17
+ eventCount: number;
18
+ }
19
+ export declare function sendEvents(events: EnvelopeEvent[], config: Config): Promise<SendResult[]>;
20
+ /**
21
+ * reportInstance — PUT /v1/instance
22
+ *
23
+ * Reports machine + software metadata to the Shield platform for score
24
+ * calculation and dashboard display. This is NOT a security event and does
25
+ * NOT go to Chronicle — it goes directly to the platform API via Cloud Run.
26
+ *
27
+ * Called periodically (default: every 5 minutes) by the bridge/plugin loop.
28
+ */
29
+ export declare function reportInstance(payload: Record<string, unknown>, credentials: ShieldCredentials): Promise<boolean>;