@upx-us/shield 0.2.15-beta → 0.2.16-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.
package/dist/index.js CHANGED
@@ -282,6 +282,24 @@ exports.default = {
282
282
  envelopes = envelopes.map(e => redactEvent(e));
283
283
  }
284
284
  const results = await sendEvents(envelopes, config);
285
+ // 403 needs_registration → self-halt cleanly (no point retrying)
286
+ const needsReg = results.some(r => r.needsRegistration);
287
+ if (needsReg) {
288
+ log.error('shield', 'Instance not registered on platform — Shield deactivated. Re-run: npx shield-setup');
289
+ state.running = false;
290
+ return;
291
+ }
292
+ // 202 pending_namespace → hold cursors, don't count as failure, use retry_after
293
+ const pending = results.find(r => r.pendingNamespace);
294
+ if (pending) {
295
+ const waitMs = pending.retryAfterMs ?? 300_000;
296
+ log.warn('shield', `Namespace allocation in progress — holding events, backing off ${Math.round(waitMs / 1000)}s`);
297
+ state.lastPollAt = Date.now();
298
+ persistState();
299
+ // Override next poll interval by sleeping here (schedulePoll will use normal backoff after)
300
+ await new Promise(r => setTimeout(r, waitMs));
301
+ return;
302
+ }
285
303
  const accepted = results.reduce((sum, r) => sum + (r.success ? r.eventCount : 0), 0);
286
304
  if (accepted > 0) {
287
305
  commitCursors(config, entries);
@@ -15,6 +15,11 @@ export interface SendResult {
15
15
  statusCode?: number;
16
16
  body?: string;
17
17
  eventCount: number;
18
+ /** True when Cloud Run returns 202 pending_namespace — hold cursors, backoff retry_after */
19
+ pendingNamespace?: boolean;
20
+ retryAfterMs?: number;
21
+ /** True when Cloud Run returns 403 needs_registration — plugin must self-halt */
22
+ needsRegistration?: boolean;
18
23
  }
19
24
  export declare function sendEvents(events: EnvelopeEvent[], config: Config): Promise<SendResult[]>;
20
25
  /**
@@ -115,7 +115,8 @@ async function sendEvents(events, config) {
115
115
  method: 'POST',
116
116
  headers: {
117
117
  'Content-Type': 'application/json',
118
- 'X-Shield-Instance-Id': instanceId,
118
+ 'X-Shield-Instance-Id': instanceId, // legacy name
119
+ 'X-Shield-Fingerprint': instanceId, // canonical name
119
120
  'X-Shield-Nonce': nonce,
120
121
  'X-Shield-Signature': signature,
121
122
  'X-Shield-Version': version_1.VERSION,
@@ -125,10 +126,37 @@ async function sendEvents(events, config) {
125
126
  });
126
127
  const data = await res.text();
127
128
  log.info('sender', `Batch ${batchNum}: ${batch.length} events (HTTP ${res.status})`);
128
- if (res.ok) {
129
+ if (res.ok && res.status === 200) {
129
130
  results.push({ success: true, statusCode: res.status, body: data, eventCount: batch.length });
130
131
  break;
131
132
  }
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
+ if (res.status === 202) {
136
+ let retryAfterMs = 300_000; // default 5 min
137
+ try {
138
+ retryAfterMs = (JSON.parse(data).retry_after ?? 300) * 1000;
139
+ }
140
+ catch { }
141
+ log.warn('sender', `Batch ${batchNum} — namespace pending (202). Holding events, retry in ${retryAfterMs / 1000}s`);
142
+ results.push({ success: false, statusCode: 202, body: data, eventCount: batch.length,
143
+ pendingNamespace: true, retryAfterMs });
144
+ break;
145
+ }
146
+ // 403 needs_registration — instance unknown or inactive, plugin must self-halt.
147
+ if (res.status === 403) {
148
+ let needsReg = false;
149
+ try {
150
+ needsReg = JSON.parse(data).needs_registration === true;
151
+ }
152
+ catch { }
153
+ if (needsReg) {
154
+ log.error('sender', `Batch ${batchNum} — instance not registered (403). Shield deactivated — re-run wizard.`);
155
+ results.push({ success: false, statusCode: 403, body: data, eventCount: batch.length,
156
+ needsRegistration: true });
157
+ break;
158
+ }
159
+ }
132
160
  if (res.status >= 500 && attempt === 0) {
133
161
  log.warn('sender', `Batch ${batchNum} attempt ${attempt + 1} — HTTP ${res.status}, retrying...`);
134
162
  continue;
@@ -170,7 +198,8 @@ async function reportInstance(payload, credentials) {
170
198
  method: 'PUT',
171
199
  headers: {
172
200
  'Content-Type': 'application/json',
173
- 'X-Shield-Instance-Id': instanceId,
201
+ 'X-Shield-Instance-Id': instanceId, // legacy
202
+ 'X-Shield-Fingerprint': instanceId, // canonical
174
203
  'X-Shield-Nonce': nonce,
175
204
  'X-Shield-Signature': signature,
176
205
  'X-Shield-Version': version_1.VERSION,
@@ -2,7 +2,7 @@
2
2
  "id": "shield",
3
3
  "name": "OpenClaw Shield",
4
4
  "description": "Real-time security monitoring — streams enriched, redacted security events to the Shield detection platform.",
5
- "version": "0.2.15-beta",
5
+ "version": "0.2.16-beta",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upx-us/shield",
3
- "version": "0.2.15-beta",
3
+ "version": "0.2.16-beta",
4
4
  "description": "Security monitoring plugin for OpenClaw agents — streams enriched security events to the Shield detection platform",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",