@upx-us/shield 0.2.16-beta → 0.3.4

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 (57) hide show
  1. package/README.md +178 -53
  2. package/dist/index.d.ts +13 -15
  3. package/dist/index.js +593 -245
  4. package/dist/src/config.d.ts +1 -15
  5. package/dist/src/config.js +3 -39
  6. package/dist/src/counters.d.ts +14 -0
  7. package/dist/src/counters.js +96 -0
  8. package/dist/src/events/base.d.ts +0 -25
  9. package/dist/src/events/base.js +0 -15
  10. package/dist/src/events/browser/enrich.js +1 -1
  11. package/dist/src/events/exec/enrich.js +0 -2
  12. package/dist/src/events/exec/redactions.d.ts +0 -1
  13. package/dist/src/events/exec/redactions.js +0 -1
  14. package/dist/src/events/file/enrich.js +0 -3
  15. package/dist/src/events/generic/index.d.ts +0 -1
  16. package/dist/src/events/generic/index.js +0 -1
  17. package/dist/src/events/index.d.ts +0 -13
  18. package/dist/src/events/index.js +1 -13
  19. package/dist/src/events/message/validations.js +0 -3
  20. package/dist/src/events/sessions-spawn/enrich.js +0 -1
  21. package/dist/src/events/sessions-spawn/event.d.ts +0 -1
  22. package/dist/src/events/tool-result/enrich.js +0 -1
  23. package/dist/src/events/tool-result/redactions.js +0 -1
  24. package/dist/src/events/web/enrich.d.ts +0 -4
  25. package/dist/src/events/web/enrich.js +6 -14
  26. package/dist/src/events/web/redactions.js +1 -3
  27. package/dist/src/fetcher.d.ts +1 -0
  28. package/dist/src/fetcher.js +28 -19
  29. package/dist/src/index.js +31 -16
  30. package/dist/src/log.d.ts +0 -26
  31. package/dist/src/log.js +1 -27
  32. package/dist/src/redactor/base.d.ts +0 -23
  33. package/dist/src/redactor/base.js +0 -7
  34. package/dist/src/redactor/index.d.ts +0 -15
  35. package/dist/src/redactor/index.js +8 -27
  36. package/dist/src/redactor/strategies/command.js +0 -3
  37. package/dist/src/redactor/strategies/hostname.js +0 -1
  38. package/dist/src/redactor/strategies/index.d.ts +0 -5
  39. package/dist/src/redactor/strategies/index.js +0 -5
  40. package/dist/src/redactor/strategies/path.js +3 -3
  41. package/dist/src/redactor/strategies/secret-key.js +33 -9
  42. package/dist/src/redactor/vault.d.ts +0 -19
  43. package/dist/src/redactor/vault.js +7 -35
  44. package/dist/src/sender.d.ts +12 -20
  45. package/dist/src/sender.js +40 -36
  46. package/dist/src/setup.d.ts +11 -9
  47. package/dist/src/setup.js +33 -32
  48. package/dist/src/transformer.d.ts +1 -12
  49. package/dist/src/transformer.js +73 -48
  50. package/dist/src/validator.d.ts +0 -11
  51. package/dist/src/validator.js +19 -25
  52. package/dist/src/version.js +1 -2
  53. package/openclaw.plugin.json +10 -2
  54. package/package.json +8 -3
  55. package/dist/src/host-collector.d.ts +0 -1
  56. package/dist/src/host-collector.js +0 -200
  57. package/skills/shield/SKILL.md +0 -38
@@ -1,18 +1,4 @@
1
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
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
3
  if (k2 === undefined) k2 = k;
18
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -57,23 +43,21 @@ const crypto = __importStar(require("crypto"));
57
43
  const fs = __importStar(require("fs"));
58
44
  const os = __importStar(require("os"));
59
45
  const path = __importStar(require("path"));
60
- // ─── Paths ────────────────────────────────────────────────────────────────────
61
46
  const SHIELD_DATA_DIR = path.join(os.homedir(), '.openclaw', 'shield', 'data');
62
47
  const KEY_FILE = path.join(SHIELD_DATA_DIR, 'redaction-key.secret');
63
48
  const VAULT_FILE = path.join(SHIELD_DATA_DIR, 'redaction-vault.json');
64
49
  const VAULT_TMP = `${VAULT_FILE}.tmp`;
65
- // Legacy plaintext map (pre-vault) — migrated on first init
66
50
  const LEGACY_MAP = path.join(SHIELD_DATA_DIR, 'redaction-map.json');
67
- // ─── State ────────────────────────────────────────────────────────────────────
68
51
  let _secret = null;
69
52
  let _map = {};
70
53
  let _mapDirty = false;
71
54
  let _testMode = false;
72
- // ─── Crypto Helpers ───────────────────────────────────────────────────────────
55
+ let _derivedKey = null;
73
56
  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);
57
+ if (_derivedKey)
58
+ return _derivedKey;
59
+ _derivedKey = crypto.scryptSync(secret, 'shield-vault-salt', 32);
60
+ return _derivedKey;
77
61
  }
78
62
  function encryptMap(data, key) {
79
63
  const iv = crypto.randomBytes(16);
@@ -90,7 +74,6 @@ function decryptMap(env, key) {
90
74
  decipher.setAuthTag(Buffer.from(env.tag, 'hex'));
91
75
  return decipher.update(env.ciphertext, 'hex', 'utf8') + decipher.final('utf8');
92
76
  }
93
- // ─── Key Management ───────────────────────────────────────────────────────────
94
77
  function getSecret() {
95
78
  if (_secret)
96
79
  return _secret;
@@ -105,11 +88,9 @@ function getSecret() {
105
88
  }
106
89
  return _secret;
107
90
  }
108
- // ─── Vault I/O ────────────────────────────────────────────────────────────────
109
91
  function loadVault() {
110
92
  if (_testMode)
111
93
  return;
112
- // Try primary vault file, fall back to .tmp if primary is missing (crash recovery)
113
94
  let fileToLoad = null;
114
95
  if (fs.existsSync(VAULT_FILE))
115
96
  fileToLoad = VAULT_FILE;
@@ -131,9 +112,6 @@ function loadVault() {
131
112
  }
132
113
  return;
133
114
  }
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
115
  const OLD_PROJECT_MAP = path.join(__dirname, '..', '..', 'config', 'redaction-map.json');
138
116
  const legacyPath = [LEGACY_MAP, OLD_PROJECT_MAP].find(p => fs.existsSync(p));
139
117
  if (legacyPath) {
@@ -142,7 +120,7 @@ function loadVault() {
142
120
  const count = Object.keys(legacy).length;
143
121
  _map = legacy;
144
122
  _mapDirty = true;
145
- flushVault(); // write encrypted vault immediately
123
+ flushVault();
146
124
  fs.unlinkSync(legacyPath);
147
125
  console.log(`[vault] Migrated ${count} mappings from legacy plaintext map (${legacyPath})`);
148
126
  }
@@ -160,7 +138,6 @@ function saveVault() {
160
138
  const key = deriveVaultKey(getSecret());
161
139
  const encrypted = encryptMap(JSON.stringify(_map), key);
162
140
  const content = JSON.stringify(encrypted);
163
- // Atomic write: .tmp first, then rename
164
141
  fs.writeFileSync(VAULT_TMP, content, { mode: 0o600 });
165
142
  fs.renameSync(VAULT_TMP, VAULT_FILE);
166
143
  _mapDirty = false;
@@ -169,7 +146,6 @@ function saveVault() {
169
146
  console.error(`[vault] Failed to persist vault: ${e.message}`);
170
147
  }
171
148
  }
172
- // ─── Public API ───────────────────────────────────────────────────────────────
173
149
  function initVault() {
174
150
  getSecret();
175
151
  loadVault();
@@ -178,10 +154,6 @@ function initVault() {
178
154
  function flushVault() {
179
155
  saveVault();
180
156
  }
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
157
  function hmacHash(category, value) {
186
158
  const hash = crypto.createHmac('sha256', getSecret())
187
159
  .update(`${category}:${value}`)
@@ -200,9 +172,9 @@ function reverseLookup(token) {
200
172
  function getAllMappings() {
201
173
  return { ..._map };
202
174
  }
203
- /** For testing only — deterministic secret, zero file I/O. */
204
175
  function _initVaultForTesting(secret) {
205
176
  _secret = secret;
177
+ _derivedKey = null;
206
178
  _map = {};
207
179
  _mapDirty = false;
208
180
  _testMode = true;
@@ -1,34 +1,26 @@
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
1
  import { EnvelopeEvent } from './transformer';
9
2
  import { Config, ShieldCredentials } from './config';
10
3
  export declare const REQUEST_TIMEOUT_MS = 30000;
11
- /** Exported for testing only — computes HMAC with an explicit secret */
4
+ export declare const CIRCUIT_BREAKER_THRESHOLD = 2;
12
5
  export declare function _signRequestWithSecret(secret: string, instanceId: string, nonce: string): string;
13
6
  export interface SendResult {
14
7
  success: boolean;
15
8
  statusCode?: number;
16
9
  body?: string;
17
10
  eventCount: number;
18
- /** True when Cloud Run returns 202 pending_namespace — hold cursors, backoff retry_after */
19
11
  pendingNamespace?: boolean;
20
12
  retryAfterMs?: number;
21
- /** True when Cloud Run returns 403 needs_registration — plugin must self-halt */
22
13
  needsRegistration?: boolean;
23
14
  }
24
15
  export declare function sendEvents(events: EnvelopeEvent[], config: Config): Promise<SendResult[]>;
25
- /**
26
- * reportInstance — PUT /v1/instance
27
- *
28
- * Reports machine + software metadata to the Shield platform for score
29
- * calculation and dashboard display. This is NOT a security event and does
30
- * NOT go to Chronicle — it goes directly to the platform API via Cloud Run.
31
- *
32
- * Called periodically (default: every 5 minutes) by the bridge/plugin loop.
33
- */
34
- export declare function reportInstance(payload: Record<string, unknown>, credentials: ShieldCredentials): Promise<boolean>;
16
+ export interface InstanceScore {
17
+ total: number;
18
+ grade: string;
19
+ badge: string;
20
+ recommendations: string[];
21
+ }
22
+ export interface ReportInstanceResult {
23
+ ok: boolean;
24
+ score?: InstanceScore;
25
+ }
26
+ export declare function reportInstance(payload: Record<string, unknown>, credentials: ShieldCredentials): Promise<ReportInstanceResult>;
@@ -1,11 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * sender.ts — Shield ingest API sender
4
- *
5
- * Posts events to the Shield ingest API via HMAC-SHA256 authentication.
6
- * Events are batched and sent to the configured Shield endpoint only —
7
- * no cloud provider details are exposed in this module.
8
- */
9
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
3
  if (k2 === undefined) k2 = k;
11
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -40,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
40
33
  };
41
34
  })();
42
35
  Object.defineProperty(exports, "__esModule", { value: true });
43
- exports.REQUEST_TIMEOUT_MS = void 0;
36
+ exports.CIRCUIT_BREAKER_THRESHOLD = exports.REQUEST_TIMEOUT_MS = void 0;
44
37
  exports._signRequestWithSecret = _signRequestWithSecret;
45
38
  exports.sendEvents = sendEvents;
46
39
  exports.reportInstance = reportInstance;
@@ -49,9 +42,9 @@ const log = __importStar(require("./log"));
49
42
  const version_1 = require("./version");
50
43
  const BATCH_SIZE = 100;
51
44
  exports.REQUEST_TIMEOUT_MS = 30_000;
52
- /** Minimum delay between consecutive batch POSTs (ms). Prevents Cloud Armor rate-limit
53
- * from triggering during backfill bursts. 200ms ≈ 5 req/s = 300 req/min (well under 600/min limit). */
45
+ exports.CIRCUIT_BREAKER_THRESHOLD = 2;
54
46
  const INTER_BATCH_DELAY_MS = 200;
47
+ const MAX_ERROR_BODY_LOG_CHARS = 200;
55
48
  function errMsg(err) {
56
49
  return err instanceof Error ? err.message : String(err);
57
50
  }
@@ -59,17 +52,22 @@ function envHeaders(shieldEnv) {
59
52
  return shieldEnv ? { 'X-Shield-Env': shieldEnv } : {};
60
53
  }
61
54
  function generateNonce() {
62
- return `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
55
+ return `${Date.now()}-${(0, crypto_1.randomBytes)(8).toString('hex')}`;
63
56
  }
64
57
  function signRequest(instanceId, nonce, secret) {
65
58
  const message = `${instanceId}:${nonce}`;
66
59
  return (0, crypto_1.createHmac)('sha256', secret).update(message).digest('hex');
67
60
  }
68
- /** Exported for testing only — computes HMAC with an explicit secret */
69
61
  function _signRequestWithSecret(secret, instanceId, nonce) {
70
62
  const message = `${instanceId}:${nonce}`;
71
63
  return (0, crypto_1.createHmac)('sha256', secret).update(message).digest('hex');
72
64
  }
65
+ function sanitizeResponseBodyForLog(body) {
66
+ const redacted = body.replace(/"((?:api_?key|token|secret|password|hmac_?secret))"\s*:\s*"[^"]*"/gi, '"$1":"[REDACTED]"');
67
+ if (redacted.length <= MAX_ERROR_BODY_LOG_CHARS)
68
+ return redacted;
69
+ return `${redacted.slice(0, MAX_ERROR_BODY_LOG_CHARS)}…`;
70
+ }
73
71
  async function fetchWithTimeout(url, init, timeoutMs = exports.REQUEST_TIMEOUT_MS) {
74
72
  const controller = new AbortController();
75
73
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -99,8 +97,14 @@ async function sendEvents(events, config) {
99
97
  return [{ success: false, statusCode: 0, body: 'missing credentials', eventCount: events.length }];
100
98
  }
101
99
  const results = [];
100
+ let consecutiveBatchFailures = 0;
102
101
  for (let i = 0; i < events.length; i += BATCH_SIZE) {
103
- // Throttle between batches to avoid Cloud Armor rate-limit during backfill
102
+ if (consecutiveBatchFailures >= exports.CIRCUIT_BREAKER_THRESHOLD) {
103
+ const remaining = events.slice(i);
104
+ log.warn('sender', `Circuit breaker: ${consecutiveBatchFailures} consecutive failures — skipping ${remaining.length} remaining events`);
105
+ results.push({ success: false, statusCode: 0, body: 'circuit breaker', eventCount: remaining.length });
106
+ break;
107
+ }
104
108
  if (i > 0)
105
109
  await new Promise(r => setTimeout(r, INTER_BATCH_DELAY_MS));
106
110
  const batch = events.slice(i, i + BATCH_SIZE);
@@ -115,8 +119,8 @@ async function sendEvents(events, config) {
115
119
  method: 'POST',
116
120
  headers: {
117
121
  'Content-Type': 'application/json',
118
- 'X-Shield-Instance-Id': instanceId, // legacy name
119
- 'X-Shield-Fingerprint': instanceId, // canonical name
122
+ 'X-Shield-Instance-Id': instanceId,
123
+ 'X-Shield-Fingerprint': instanceId,
120
124
  'X-Shield-Nonce': nonce,
121
125
  'X-Shield-Signature': signature,
122
126
  'X-Shield-Version': version_1.VERSION,
@@ -127,13 +131,12 @@ async function sendEvents(events, config) {
127
131
  const data = await res.text();
128
132
  log.info('sender', `Batch ${batchNum}: ${batch.length} events (HTTP ${res.status})`);
129
133
  if (res.ok && res.status === 200) {
134
+ consecutiveBatchFailures = 0;
130
135
  results.push({ success: true, statusCode: res.status, body: data, eventCount: batch.length });
131
136
  break;
132
137
  }
133
- // 202 pending_namespace — namespace not yet allocated by SIEM.
134
- // Events must NOT be committed; plugin should hold and retry after retry_after.
135
138
  if (res.status === 202) {
136
- let retryAfterMs = 300_000; // default 5 min
139
+ let retryAfterMs = 300_000;
137
140
  try {
138
141
  retryAfterMs = (JSON.parse(data).retry_after ?? 300) * 1000;
139
142
  }
@@ -143,7 +146,6 @@ async function sendEvents(events, config) {
143
146
  pendingNamespace: true, retryAfterMs });
144
147
  break;
145
148
  }
146
- // 403 needs_registration — instance unknown or inactive, plugin must self-halt.
147
149
  if (res.status === 403) {
148
150
  let needsReg = false;
149
151
  try {
@@ -161,13 +163,16 @@ async function sendEvents(events, config) {
161
163
  log.warn('sender', `Batch ${batchNum} attempt ${attempt + 1} — HTTP ${res.status}, retrying...`);
162
164
  continue;
163
165
  }
164
- log.error('sender', `Batch ${batchNum} — HTTP ${res.status}: ${data}`);
166
+ const safeBody = sanitizeResponseBodyForLog(data);
167
+ log.error('sender', `Batch ${batchNum} — HTTP ${res.status}: ${safeBody}`);
168
+ consecutiveBatchFailures++;
165
169
  results.push({ success: false, statusCode: res.status, body: data, eventCount: batch.length });
166
170
  break;
167
171
  }
168
172
  catch (err) {
169
173
  log.error('sender', `Batch ${batchNum} attempt ${attempt + 1} — ${errMsg(err)}`);
170
174
  if (attempt === 1) {
175
+ consecutiveBatchFailures++;
171
176
  results.push({ success: false, statusCode: 0, body: errMsg(err), eventCount: batch.length });
172
177
  }
173
178
  }
@@ -175,20 +180,11 @@ async function sendEvents(events, config) {
175
180
  }
176
181
  return results;
177
182
  }
178
- /**
179
- * reportInstance — PUT /v1/instance
180
- *
181
- * Reports machine + software metadata to the Shield platform for score
182
- * calculation and dashboard display. This is NOT a security event and does
183
- * NOT go to Chronicle — it goes directly to the platform API via Cloud Run.
184
- *
185
- * Called periodically (default: every 5 minutes) by the bridge/plugin loop.
186
- */
187
183
  async function reportInstance(payload, credentials) {
188
184
  const { apiUrl, instanceId, hmacSecret, shieldEnv } = credentials;
189
185
  if (!apiUrl || !instanceId || !hmacSecret) {
190
186
  log.warn('sender', 'reportInstance: missing credentials, skipping');
191
- return false;
187
+ return { ok: false };
192
188
  }
193
189
  const nonce = generateNonce();
194
190
  const signature = signRequest(instanceId, nonce, hmacSecret);
@@ -198,8 +194,8 @@ async function reportInstance(payload, credentials) {
198
194
  method: 'PUT',
199
195
  headers: {
200
196
  'Content-Type': 'application/json',
201
- 'X-Shield-Instance-Id': instanceId, // legacy
202
- 'X-Shield-Fingerprint': instanceId, // canonical
197
+ 'X-Shield-Instance-Id': instanceId,
198
+ 'X-Shield-Fingerprint': instanceId,
203
199
  'X-Shield-Nonce': nonce,
204
200
  'X-Shield-Signature': signature,
205
201
  'X-Shield-Version': version_1.VERSION,
@@ -209,13 +205,21 @@ async function reportInstance(payload, credentials) {
209
205
  });
210
206
  if (!res.ok) {
211
207
  const body = await res.text();
212
- log.warn('sender', `reportInstance HTTP ${res.status}: ${body.slice(0, 100)}`);
213
- return false;
208
+ log.warn('sender', `reportInstance HTTP ${res.status}: ${sanitizeResponseBodyForLog(body)}`);
209
+ return { ok: false };
210
+ }
211
+ let score;
212
+ try {
213
+ const json = await res.json();
214
+ if (json.score)
215
+ score = json.score;
216
+ }
217
+ catch {
214
218
  }
215
- return true;
219
+ return { ok: true, score };
216
220
  }
217
221
  catch (err) {
218
222
  log.warn('sender', `reportInstance error: ${errMsg(err)}`);
219
- return false;
223
+ return { ok: false };
220
224
  }
221
225
  }
@@ -1,10 +1,12 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * setup.ts — Interactive configuration wizard for Shield
4
- *
5
- * Two-stage credential flow:
6
- * 1. User provides a registration key from the Shield portal
7
- * 2. POST /v1/register exchanges it for an HMAC signing secret
8
- * 3. The returned HMAC secret + generated instance ID are saved to config.env
9
- */
10
- export {};
2
+ interface SetupConfig {
3
+ apiUrl: string;
4
+ installationKey: string;
5
+ instanceName: string;
6
+ instanceId: string;
7
+ hmacSecret: string;
8
+ }
9
+ export declare function generateInstanceId(): string;
10
+ export declare function registerInstance(config: SetupConfig): Promise<string | null>;
11
+ export declare function saveConfig(config: SetupConfig, configPath?: string): void;
12
+ export type { SetupConfig };
package/dist/src/setup.js CHANGED
@@ -1,13 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
- /**
4
- * setup.ts — Interactive configuration wizard for Shield
5
- *
6
- * Two-stage credential flow:
7
- * 1. User provides a registration key from the Shield portal
8
- * 2. POST /v1/register exchanges it for an HMAC signing secret
9
- * 3. The returned HMAC secret + generated instance ID are saved to config.env
10
- */
11
3
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
4
  if (k2 === undefined) k2 = k;
13
5
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -42,17 +34,23 @@ var __importStar = (this && this.__importStar) || (function () {
42
34
  };
43
35
  })();
44
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.generateInstanceId = generateInstanceId;
38
+ exports.registerInstance = registerInstance;
39
+ exports.saveConfig = saveConfig;
45
40
  const readline = __importStar(require("readline"));
46
41
  const fs_1 = require("fs");
47
42
  const path_1 = require("path");
48
43
  const os_1 = require("os");
49
44
  const config_1 = require("./config");
45
+ const version_1 = require("./version");
50
46
  const crypto_1 = require("crypto");
51
- const rl = readline.createInterface({
52
- input: process.stdin,
53
- output: process.stdout
54
- });
55
- function prompt(question) {
47
+ function createRl() {
48
+ return readline.createInterface({
49
+ input: process.stdin,
50
+ output: process.stdout
51
+ });
52
+ }
53
+ function prompt(rl, question) {
56
54
  return new Promise((resolve) => {
57
55
  rl.question(question, (answer) => resolve(answer.trim()));
58
56
  });
@@ -87,9 +85,8 @@ async function testConnection(apiUrl) {
87
85
  async function registerInstance(config) {
88
86
  try {
89
87
  process.stdout.write('Registering instance... ');
90
- // Cloud Run /v1/register expects: installation_code + fingerprint
91
88
  const payload = {
92
- installation_code: config.registrationKey,
89
+ installation_code: config.installationKey,
93
90
  fingerprint: config.instanceId,
94
91
  machine: {
95
92
  hostname: (0, os_1.hostname)(),
@@ -99,7 +96,7 @@ async function registerInstance(config) {
99
96
  node_version: process.version,
100
97
  },
101
98
  software: {
102
- plugin_version: '0.2.8-beta',
99
+ plugin_version: version_1.VERSION,
103
100
  instance_name: config.instanceName,
104
101
  },
105
102
  };
@@ -129,7 +126,7 @@ async function registerInstance(config) {
129
126
  const parsed = JSON.parse(body);
130
127
  msg = parsed.message || parsed.error || msg;
131
128
  }
132
- catch { /* raw */ }
129
+ catch { }
133
130
  console.log(`failed — ${msg}`);
134
131
  return null;
135
132
  }
@@ -139,14 +136,14 @@ async function registerInstance(config) {
139
136
  return null;
140
137
  }
141
138
  }
142
- function saveConfig(config) {
143
- const configDir = (0, path_1.join)(config_1.SHIELD_CONFIG_PATH, '..');
139
+ function saveConfig(config, configPath = config_1.SHIELD_CONFIG_PATH) {
140
+ const configDir = (0, path_1.join)(configPath, '..');
144
141
  if (!(0, fs_1.existsSync)(configDir)) {
145
142
  (0, fs_1.mkdirSync)(configDir, { recursive: true });
146
143
  }
147
144
  const envContent = `# Shield API Configuration
148
145
  # Generated by shield-setup on ${new Date().toISOString()}
149
- # Location: ${config_1.SHIELD_CONFIG_PATH}
146
+ # Location: ${configPath}
150
147
 
151
148
  SHIELD_API_URL=${config.apiUrl}
152
149
  SHIELD_INSTANCE_ID=${config.instanceId}
@@ -159,44 +156,53 @@ INSTANCE_NAME=${config.instanceName}
159
156
  # REDACTION_ENABLED=true
160
157
  # SESSION_DIR=/path/to/custom/sessions
161
158
  `;
162
- (0, fs_1.writeFileSync)(config_1.SHIELD_CONFIG_PATH, envContent, { encoding: 'utf-8', mode: 0o600 });
163
- console.log(`✅ Configuration saved to ${config_1.SHIELD_CONFIG_PATH}`);
159
+ (0, fs_1.writeFileSync)(configPath, envContent, { encoding: 'utf-8', mode: 0o600 });
160
+ console.log(`✅ Configuration saved to ${configPath}`);
164
161
  }
165
162
  const SHIELD_API_URL = 'https://openclaw-shield.upx.com';
166
163
  async function main() {
164
+ const rl = createRl();
167
165
  console.log('🛡️ OpenClaw Shield Setup');
168
166
  console.log('==========================\n');
167
+ process.on('SIGINT', () => {
168
+ console.log('\n\nSetup cancelled by user.');
169
+ rl.close();
170
+ process.exit(0);
171
+ });
169
172
  try {
170
- const registrationKey = await prompt('Installation Key (from Shield portal): ');
171
- if (!registrationKey) {
173
+ const installationKey = await prompt(rl, 'Installation Key (from Shield portal): ');
174
+ if (!installationKey) {
172
175
  console.log('❌ Installation key is required');
176
+ rl.close();
173
177
  process.exit(1);
174
178
  }
175
179
  const instanceName = `openclaw-${(0, os_1.hostname)()}`;
176
180
  const instanceId = generateInstanceId();
177
181
  const config = {
178
182
  apiUrl: SHIELD_API_URL,
179
- registrationKey,
183
+ installationKey,
180
184
  instanceName,
181
185
  instanceId,
182
186
  hmacSecret: '',
183
187
  };
184
- // Warn if credentials already exist
185
188
  if ((0, fs_1.existsSync)(config_1.SHIELD_CONFIG_PATH)) {
186
- const overwrite = await prompt('⚠️ Shield is already configured. Overwrite? (y/N): ');
189
+ const overwrite = await prompt(rl, '⚠️ Shield is already configured. Overwrite? (y/N): ');
187
190
  if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
188
191
  console.log('Setup cancelled. Existing configuration preserved.');
192
+ rl.close();
189
193
  process.exit(0);
190
194
  }
191
195
  }
192
196
  const connectionOk = await testConnection(SHIELD_API_URL);
193
197
  if (!connectionOk) {
194
198
  console.log('❌ Cannot reach the Shield API. Check your internet connection and try again.');
199
+ rl.close();
195
200
  process.exit(1);
196
201
  }
197
202
  const exchangedSecret = await registerInstance(config);
198
203
  if (!exchangedSecret) {
199
204
  console.log('❌ Registration failed. Please verify your installation key and try again.');
205
+ rl.close();
200
206
  process.exit(1);
201
207
  }
202
208
  config.hmacSecret = exchangedSecret;
@@ -212,11 +218,6 @@ async function main() {
212
218
  rl.close();
213
219
  }
214
220
  }
215
- process.on('SIGINT', () => {
216
- console.log('\n\nSetup cancelled by user.');
217
- rl.close();
218
- process.exit(0);
219
- });
220
221
  if (require.main === module) {
221
222
  main();
222
223
  }
@@ -1,11 +1,3 @@
1
- /**
2
- * transformer.ts — Thin orchestration layer
3
- *
4
- * Iterates raw JSONL entries, routes each tool call to the matching schema,
5
- * and wraps the resulting ShieldEvent in an EnvelopeEvent for transmission.
6
- *
7
- * All enrichment logic lives in src/events/{type}/enrich.ts.
8
- */
9
1
  import { RawEntry } from './fetcher';
10
2
  import type { ShieldEvent, SourceInfo } from './events';
11
3
  export type { SourceInfo };
@@ -13,14 +5,11 @@ export interface EnvelopeEvent {
13
5
  source: SourceInfo;
14
6
  event: ShieldEvent;
15
7
  }
16
- /** Top-level request body for POST /v1/ingest — used for JSON Schema generation */
17
8
  export interface IngestPayload {
18
9
  entries: EnvelopeEvent[];
19
10
  }
20
- /** Resolve the installed OpenClaw version from known package.json paths. */
21
11
  export declare function resolveOpenClawVersion(): string;
22
- /** Resolve agent_label from IDENTITY.md in the configured workspace. */
23
12
  export declare function resolveAgentLabel(agentId: string): string;
13
+ export declare function resolveOutboundIp(): Promise<string | null>;
24
14
  export declare function transformEntries(entries: RawEntry[]): EnvelopeEvent[];
25
- /** Generate a HOST_TELEMETRY envelope with version + config info for security rules */
26
15
  export declare function generateHostTelemetry(): EnvelopeEvent | null;