@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.
- package/README.md +178 -53
- package/dist/index.d.ts +13 -15
- package/dist/index.js +593 -245
- package/dist/src/config.d.ts +1 -15
- package/dist/src/config.js +3 -39
- package/dist/src/counters.d.ts +14 -0
- package/dist/src/counters.js +96 -0
- package/dist/src/events/base.d.ts +0 -25
- package/dist/src/events/base.js +0 -15
- package/dist/src/events/browser/enrich.js +1 -1
- package/dist/src/events/exec/enrich.js +0 -2
- package/dist/src/events/exec/redactions.d.ts +0 -1
- package/dist/src/events/exec/redactions.js +0 -1
- package/dist/src/events/file/enrich.js +0 -3
- package/dist/src/events/generic/index.d.ts +0 -1
- package/dist/src/events/generic/index.js +0 -1
- package/dist/src/events/index.d.ts +0 -13
- package/dist/src/events/index.js +1 -13
- package/dist/src/events/message/validations.js +0 -3
- package/dist/src/events/sessions-spawn/enrich.js +0 -1
- package/dist/src/events/sessions-spawn/event.d.ts +0 -1
- package/dist/src/events/tool-result/enrich.js +0 -1
- package/dist/src/events/tool-result/redactions.js +0 -1
- package/dist/src/events/web/enrich.d.ts +0 -4
- package/dist/src/events/web/enrich.js +6 -14
- package/dist/src/events/web/redactions.js +1 -3
- package/dist/src/fetcher.d.ts +1 -0
- package/dist/src/fetcher.js +28 -19
- package/dist/src/index.js +31 -16
- package/dist/src/log.d.ts +0 -26
- package/dist/src/log.js +1 -27
- package/dist/src/redactor/base.d.ts +0 -23
- package/dist/src/redactor/base.js +0 -7
- package/dist/src/redactor/index.d.ts +0 -15
- package/dist/src/redactor/index.js +8 -27
- package/dist/src/redactor/strategies/command.js +0 -3
- package/dist/src/redactor/strategies/hostname.js +0 -1
- package/dist/src/redactor/strategies/index.d.ts +0 -5
- package/dist/src/redactor/strategies/index.js +0 -5
- package/dist/src/redactor/strategies/path.js +3 -3
- package/dist/src/redactor/strategies/secret-key.js +33 -9
- package/dist/src/redactor/vault.d.ts +0 -19
- package/dist/src/redactor/vault.js +7 -35
- package/dist/src/sender.d.ts +12 -20
- package/dist/src/sender.js +40 -36
- package/dist/src/setup.d.ts +11 -9
- package/dist/src/setup.js +33 -32
- package/dist/src/transformer.d.ts +1 -12
- package/dist/src/transformer.js +73 -48
- package/dist/src/validator.d.ts +0 -11
- package/dist/src/validator.js +19 -25
- package/dist/src/version.js +1 -2
- package/openclaw.plugin.json +10 -2
- package/package.json +8 -3
- package/dist/src/host-collector.d.ts +0 -1
- package/dist/src/host-collector.js +0 -200
- 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
|
-
|
|
55
|
+
let _derivedKey = null;
|
|
73
56
|
function deriveVaultKey(secret) {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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();
|
|
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;
|
package/dist/src/sender.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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>;
|
package/dist/src/sender.js
CHANGED
|
@@ -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
|
-
|
|
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()}-${
|
|
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
|
-
|
|
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,
|
|
119
|
-
'X-Shield-Fingerprint': instanceId,
|
|
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;
|
|
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
|
-
|
|
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,
|
|
202
|
-
'X-Shield-Fingerprint': instanceId,
|
|
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
|
|
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
|
}
|
package/dist/src/setup.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
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:
|
|
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 {
|
|
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)(
|
|
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: ${
|
|
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)(
|
|
163
|
-
console.log(`✅ Configuration saved to ${
|
|
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
|
|
171
|
-
if (!
|
|
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
|
-
|
|
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;
|