@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.
- package/LICENSE +38 -0
- package/README.md +96 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +365 -0
- package/dist/src/config.d.ts +43 -0
- package/dist/src/config.js +181 -0
- package/dist/src/events/base.d.ts +110 -0
- package/dist/src/events/base.js +61 -0
- package/dist/src/events/browser/enrich.d.ts +3 -0
- package/dist/src/events/browser/enrich.js +46 -0
- package/dist/src/events/browser/event.d.ts +10 -0
- package/dist/src/events/browser/event.js +2 -0
- package/dist/src/events/browser/index.d.ts +4 -0
- package/dist/src/events/browser/index.js +13 -0
- package/dist/src/events/browser/redactions.d.ts +2 -0
- package/dist/src/events/browser/redactions.js +4 -0
- package/dist/src/events/browser/validations.d.ts +3 -0
- package/dist/src/events/browser/validations.js +10 -0
- package/dist/src/events/cron/enrich.d.ts +3 -0
- package/dist/src/events/cron/enrich.js +44 -0
- package/dist/src/events/cron/event.d.ts +5 -0
- package/dist/src/events/cron/event.js +2 -0
- package/dist/src/events/cron/index.d.ts +4 -0
- package/dist/src/events/cron/index.js +13 -0
- package/dist/src/events/cron/redactions.d.ts +2 -0
- package/dist/src/events/cron/redactions.js +4 -0
- package/dist/src/events/cron/validations.d.ts +3 -0
- package/dist/src/events/cron/validations.js +4 -0
- package/dist/src/events/exec/enrich.d.ts +3 -0
- package/dist/src/events/exec/enrich.js +80 -0
- package/dist/src/events/exec/event.d.ts +11 -0
- package/dist/src/events/exec/event.js +2 -0
- package/dist/src/events/exec/index.d.ts +4 -0
- package/dist/src/events/exec/index.js +13 -0
- package/dist/src/events/exec/redactions.d.ts +3 -0
- package/dist/src/events/exec/redactions.js +12 -0
- package/dist/src/events/exec/validations.d.ts +3 -0
- package/dist/src/events/exec/validations.js +12 -0
- package/dist/src/events/file/enrich.d.ts +3 -0
- package/dist/src/events/file/enrich.js +63 -0
- package/dist/src/events/file/event.d.ts +11 -0
- package/dist/src/events/file/event.js +2 -0
- package/dist/src/events/file/index.d.ts +4 -0
- package/dist/src/events/file/index.js +13 -0
- package/dist/src/events/file/redactions.d.ts +2 -0
- package/dist/src/events/file/redactions.js +8 -0
- package/dist/src/events/file/validations.d.ts +3 -0
- package/dist/src/events/file/validations.js +10 -0
- package/dist/src/events/gateway/enrich.d.ts +3 -0
- package/dist/src/events/gateway/enrich.js +50 -0
- package/dist/src/events/gateway/event.d.ts +5 -0
- package/dist/src/events/gateway/event.js +2 -0
- package/dist/src/events/gateway/index.d.ts +4 -0
- package/dist/src/events/gateway/index.js +13 -0
- package/dist/src/events/gateway/redactions.d.ts +2 -0
- package/dist/src/events/gateway/redactions.js +4 -0
- package/dist/src/events/gateway/validations.d.ts +3 -0
- package/dist/src/events/gateway/validations.js +4 -0
- package/dist/src/events/generic/enrich.d.ts +3 -0
- package/dist/src/events/generic/enrich.js +30 -0
- package/dist/src/events/generic/event.d.ts +5 -0
- package/dist/src/events/generic/event.js +2 -0
- package/dist/src/events/generic/index.d.ts +5 -0
- package/dist/src/events/generic/index.js +14 -0
- package/dist/src/events/generic/redactions.d.ts +2 -0
- package/dist/src/events/generic/redactions.js +4 -0
- package/dist/src/events/generic/validations.d.ts +3 -0
- package/dist/src/events/generic/validations.js +4 -0
- package/dist/src/events/host-telemetry/enrich.d.ts +3 -0
- package/dist/src/events/host-telemetry/enrich.js +28 -0
- package/dist/src/events/host-telemetry/event.d.ts +4 -0
- package/dist/src/events/host-telemetry/event.js +2 -0
- package/dist/src/events/host-telemetry/index.d.ts +4 -0
- package/dist/src/events/host-telemetry/index.js +13 -0
- package/dist/src/events/host-telemetry/redactions.d.ts +2 -0
- package/dist/src/events/host-telemetry/redactions.js +4 -0
- package/dist/src/events/host-telemetry/validations.d.ts +3 -0
- package/dist/src/events/host-telemetry/validations.js +4 -0
- package/dist/src/events/index.d.ts +40 -0
- package/dist/src/events/index.js +39 -0
- package/dist/src/events/message/enrich.d.ts +3 -0
- package/dist/src/events/message/enrich.js +36 -0
- package/dist/src/events/message/event.d.ts +5 -0
- package/dist/src/events/message/event.js +2 -0
- package/dist/src/events/message/index.d.ts +4 -0
- package/dist/src/events/message/index.js +13 -0
- package/dist/src/events/message/redactions.d.ts +2 -0
- package/dist/src/events/message/redactions.js +4 -0
- package/dist/src/events/message/validations.d.ts +3 -0
- package/dist/src/events/message/validations.js +7 -0
- package/dist/src/events/sessions-spawn/enrich.d.ts +3 -0
- package/dist/src/events/sessions-spawn/enrich.js +40 -0
- package/dist/src/events/sessions-spawn/event.d.ts +9 -0
- package/dist/src/events/sessions-spawn/event.js +2 -0
- package/dist/src/events/sessions-spawn/index.d.ts +4 -0
- package/dist/src/events/sessions-spawn/index.js +13 -0
- package/dist/src/events/sessions-spawn/redactions.d.ts +2 -0
- package/dist/src/events/sessions-spawn/redactions.js +4 -0
- package/dist/src/events/sessions-spawn/validations.d.ts +3 -0
- package/dist/src/events/sessions-spawn/validations.js +4 -0
- package/dist/src/events/tool-result/enrich.d.ts +13 -0
- package/dist/src/events/tool-result/enrich.js +46 -0
- package/dist/src/events/tool-result/event.d.ts +7 -0
- package/dist/src/events/tool-result/event.js +2 -0
- package/dist/src/events/tool-result/index.d.ts +4 -0
- package/dist/src/events/tool-result/index.js +9 -0
- package/dist/src/events/tool-result/redactions.d.ts +2 -0
- package/dist/src/events/tool-result/redactions.js +7 -0
- package/dist/src/events/tool-result/validations.d.ts +3 -0
- package/dist/src/events/tool-result/validations.js +9 -0
- package/dist/src/events/web/enrich.d.ts +8 -0
- package/dist/src/events/web/enrich.js +78 -0
- package/dist/src/events/web/event.d.ts +10 -0
- package/dist/src/events/web/event.js +2 -0
- package/dist/src/events/web/index.d.ts +4 -0
- package/dist/src/events/web/index.js +13 -0
- package/dist/src/events/web/redactions.d.ts +2 -0
- package/dist/src/events/web/redactions.js +6 -0
- package/dist/src/events/web/validations.d.ts +3 -0
- package/dist/src/events/web/validations.js +10 -0
- package/dist/src/fetcher.d.ts +12 -0
- package/dist/src/fetcher.js +182 -0
- package/dist/src/host-collector.d.ts +1 -0
- package/dist/src/host-collector.js +200 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +210 -0
- package/dist/src/log.d.ts +39 -0
- package/dist/src/log.js +102 -0
- package/dist/src/redactor/base.d.ts +29 -0
- package/dist/src/redactor/base.js +9 -0
- package/dist/src/redactor/index.d.ts +27 -0
- package/dist/src/redactor/index.js +109 -0
- package/dist/src/redactor/strategies/command.d.ts +2 -0
- package/dist/src/redactor/strategies/command.js +19 -0
- package/dist/src/redactor/strategies/hostname.d.ts +2 -0
- package/dist/src/redactor/strategies/hostname.js +15 -0
- package/dist/src/redactor/strategies/index.d.ts +13 -0
- package/dist/src/redactor/strategies/index.js +25 -0
- package/dist/src/redactor/strategies/path.d.ts +2 -0
- package/dist/src/redactor/strategies/path.js +23 -0
- package/dist/src/redactor/strategies/secret-key.d.ts +2 -0
- package/dist/src/redactor/strategies/secret-key.js +22 -0
- package/dist/src/redactor/strategies/username.d.ts +2 -0
- package/dist/src/redactor/strategies/username.js +12 -0
- package/dist/src/redactor/vault.d.ts +25 -0
- package/dist/src/redactor/vault.js +209 -0
- package/dist/src/sender.d.ts +29 -0
- package/dist/src/sender.js +186 -0
- package/dist/src/setup.d.ts +10 -0
- package/dist/src/setup.js +222 -0
- package/dist/src/transformer.d.ts +26 -0
- package/dist/src/transformer.js +302 -0
- package/dist/src/validator.d.ts +17 -0
- package/dist/src/validator.js +110 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +19 -0
- package/openclaw.plugin.json +52 -0
- package/package.json +64 -0
- 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,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,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,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,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,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>;
|