@upx-us/shield 0.3.5 → 0.3.16
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 +168 -57
- package/dist/index.d.ts +10 -0
- package/dist/index.js +140 -45
- package/dist/src/index.js +4 -1
- package/dist/src/sender.js +10 -0
- package/dist/src/transformer.d.ts +1 -0
- package/dist/src/transformer.js +4 -0
- package/openclaw.plugin.json +56 -56
- package/package.json +70 -67
package/README.md
CHANGED
|
@@ -20,29 +20,32 @@ Your Agent → Shield (local: capture + redact) → UPX Platform (analysis, aler
|
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Installation
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
**If you have an OpenClaw agent running**, use Quick Start — paste a single prompt and you're done.
|
|
26
|
+
**If you prefer to configure manually**, follow Manual Installation below.
|
|
26
27
|
|
|
27
|
-
|
|
28
|
+
### Quick Start — Let your agent do it
|
|
28
29
|
|
|
29
|
-
|
|
30
|
+
Copy this prompt and paste it to your AI agent. Replace `<YOUR_KEY>` with your Installation Key:
|
|
30
31
|
|
|
31
|
-
|
|
32
|
+
> Install the OpenClaw Shield plugin (@upx-us/shield) with installation key `<YOUR_KEY>`. Add "shield" to plugins.allow and set installationKey in plugins.entries.shield.config. Restart the gateway and confirm Shield is active with openclaw shield status.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
The agent installs the plugin, edits the config, restarts the gateway, and Shield activates automatically.
|
|
34
35
|
|
|
35
|
-
###
|
|
36
|
+
### Manual Installation
|
|
37
|
+
|
|
38
|
+
#### Step 1 — Install the plugin
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
41
|
openclaw plugins install @upx-us/shield
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
> OpenClaw will
|
|
44
|
+
> **Security prompt:** OpenClaw will warn about environment variable access combined with network calls. This is **expected** — Shield reads signing credentials and sends telemetry to the Shield API. To acknowledge, add `"shield"` to `plugins.allow` in `~/.openclaw/openclaw.json` (shown in Step 2). If automating via a script or agent, pre-populate `plugins.allow` before running the install command to avoid interactive prompts.
|
|
42
45
|
|
|
43
|
-
|
|
46
|
+
#### Step 2 — Add your Installation Key
|
|
44
47
|
|
|
45
|
-
Open `~/.openclaw/openclaw.json` and add
|
|
48
|
+
Open `~/.openclaw/openclaw.json` and add:
|
|
46
49
|
|
|
47
50
|
```json
|
|
48
51
|
{
|
|
@@ -59,9 +62,9 @@ Open `~/.openclaw/openclaw.json` and add the following:
|
|
|
59
62
|
}
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
> **
|
|
65
|
+
> **Installation Keys are single-use** — the plugin exchanges your key for permanent credentials on first startup and saves them locally. The key is consumed on the first *successful* activation; if the first attempt fails (network issue, config typo), the key is not burned and you can retry. Once activation succeeds, you can safely remove `installationKey` from the config.
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
#### Step 3 — Restart the Gateway
|
|
65
68
|
|
|
66
69
|
```bash
|
|
67
70
|
openclaw gateway restart
|
|
@@ -69,9 +72,7 @@ openclaw gateway restart
|
|
|
69
72
|
|
|
70
73
|
Shield auto-registers, saves credentials to `~/.openclaw/shield/config.env`, and starts monitoring.
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
## Alternative: CLI Activation
|
|
75
|
+
### Alternative: CLI Activation
|
|
75
76
|
|
|
76
77
|
If the config-based flow didn't work (e.g. the key was added to the wrong path), you can activate directly:
|
|
77
78
|
|
|
@@ -82,22 +83,45 @@ openclaw gateway restart
|
|
|
82
83
|
|
|
83
84
|
---
|
|
84
85
|
|
|
85
|
-
##
|
|
86
|
+
## What to expect after activation
|
|
87
|
+
|
|
88
|
+
After restart, Shield exchanges your key for permanent credentials — this takes a few seconds. You should see your first events within the first poll cycle (~30 seconds). Within 1–2 minutes, those events will appear on the platform at [uss.upx.com](https://uss.upx.com).
|
|
89
|
+
|
|
90
|
+
Run `openclaw shield status` to confirm:
|
|
91
|
+
|
|
92
|
+
**Just activated (first minute):**
|
|
86
93
|
|
|
87
|
-
```bash
|
|
88
|
-
openclaw shield status
|
|
89
94
|
```
|
|
95
|
+
OpenClaw Shield — v0.3.x (5s ago)
|
|
90
96
|
|
|
91
|
-
|
|
97
|
+
── Plugin Health ─────────────────────────────
|
|
98
|
+
Connection: ✅ Connected
|
|
99
|
+
Version: 0.3.x
|
|
100
|
+
Instance: a1b2c3d4…
|
|
101
|
+
Last poll: 5s ago
|
|
102
|
+
Last capture: 5s ago
|
|
103
|
+
Events sent: 3 (all-time)
|
|
104
|
+
Quarantine: 0 (all-time)
|
|
105
|
+
Failures: 0 (consecutive)
|
|
106
|
+
Daemon PID: 12345
|
|
107
|
+
Session: 0m
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
A low event count and a recent `Last capture` time means Shield is working correctly. Events accumulate as your agent uses tools.
|
|
111
|
+
|
|
112
|
+
> **Note:** The Activity and Redactions sections appear after your first sync cycle (~30s). If your status looks shorter than the "extended use" example below, that's normal — they'll populate automatically.
|
|
113
|
+
|
|
114
|
+
**After extended use:**
|
|
92
115
|
|
|
93
116
|
```
|
|
94
|
-
OpenClaw Shield — v0.3.
|
|
117
|
+
OpenClaw Shield — v0.3.x (5s ago)
|
|
95
118
|
|
|
96
119
|
── Plugin Health ─────────────────────────────
|
|
97
120
|
Connection: ✅ Connected
|
|
98
|
-
Version: 0.3.
|
|
121
|
+
Version: 0.3.x
|
|
99
122
|
Instance: a1b2c3d4…
|
|
100
123
|
Last poll: 5s ago
|
|
124
|
+
Last capture: 14s ago
|
|
101
125
|
Events sent: 1,842 (all-time)
|
|
102
126
|
Quarantine: 0 (all-time)
|
|
103
127
|
Failures: 0 (consecutive)
|
|
@@ -107,8 +131,8 @@ OpenClaw Shield — v0.3.0 (5s ago)
|
|
|
107
131
|
── Activity ──────────────────────────────────
|
|
108
132
|
|
|
109
133
|
📡 Last sync (29s ago — 5 events)
|
|
110
|
-
|
|
111
|
-
|
|
134
|
+
exec ████████ 3
|
|
135
|
+
file █████ 2
|
|
112
136
|
|
|
113
137
|
📊 This session (since restart 4h 12m ago — 142 events)
|
|
114
138
|
TOOL_CALL ████████ 78
|
|
@@ -123,7 +147,7 @@ OpenClaw Shield — v0.3.0 (5s ago)
|
|
|
123
147
|
**When not activated:**
|
|
124
148
|
|
|
125
149
|
```
|
|
126
|
-
OpenClaw Shield — v0.3.
|
|
150
|
+
OpenClaw Shield — v0.3.x
|
|
127
151
|
|
|
128
152
|
Status: Loaded (not activated)
|
|
129
153
|
|
|
@@ -142,11 +166,18 @@ OpenClaw Shield — v0.3.0
|
|
|
142
166
|
|
|
143
167
|
### Event types
|
|
144
168
|
|
|
145
|
-
Shield captures what your agent *does* —
|
|
169
|
+
Shield captures what your agent *does* — tool invocations and their results — not what it says.
|
|
170
|
+
|
|
171
|
+
The Activity section shows two levels:
|
|
172
|
+
|
|
173
|
+
- **Session-level** (`TOOL_CALL`, `TOOL_RESULT`) — every tool invocation and result, shown in the "This session" summary
|
|
174
|
+
- **Sync-level** (`exec`, `file`, `web`, etc.) — the specific event type after parsing, shown in "Last sync"
|
|
175
|
+
|
|
176
|
+
`exec: 2` in Last sync means 2 of those synced events were shell commands. Those same 2 are also counted within the `TOOL_CALL` total in the session view. They are not separate.
|
|
146
177
|
|
|
147
178
|
| Event type | What it means |
|
|
148
179
|
|---|---|
|
|
149
|
-
| `TOOL_CALL` | A tool was invoked by the agent
|
|
180
|
+
| `TOOL_CALL` | A tool was invoked by the agent |
|
|
150
181
|
| `TOOL_RESULT` | The result returned from a tool call |
|
|
151
182
|
| `exec` | Shell command executed |
|
|
152
183
|
| `file` | File read, written, or edited |
|
|
@@ -156,23 +187,27 @@ Shield captures what your agent *does* — not what it says. Each line in the Ac
|
|
|
156
187
|
| `browser` | Browser automation action |
|
|
157
188
|
| `cron` | A scheduled task fired |
|
|
158
189
|
|
|
159
|
-
Shield captures what your agent *does* — tool invocations and their results. Each event carries metadata about the operation (tool name, paths, URLs) along with a redacted summary of the result.
|
|
160
|
-
|
|
161
190
|
### Quarantine
|
|
162
191
|
|
|
163
|
-
Quarantine counts events that failed local schema validation and were **held back** rather than forwarded to the platform. They are not lost —
|
|
192
|
+
Quarantine counts events that failed local schema validation and were **held back** rather than forwarded to the platform. They are not lost — written to `~/.openclaw/shield/data/quarantine.jsonl` for inspection. A non-zero quarantine count usually means a plugin version mismatch; upgrading Shield typically resolves it.
|
|
164
193
|
|
|
165
194
|
### Redactions
|
|
166
195
|
|
|
167
|
-
Redaction runs locally before anything leaves your machine. The counts shown are **occurrences** — how many times
|
|
196
|
+
Redaction runs locally before anything leaves your machine. The counts shown are **occurrences** — how many times sensitive data was detected and replaced with a reversible token (e.g. `host:a3f9b1c2`, `user:7b2c4a1f`). Original values are stored in an encrypted local vault and never transmitted.
|
|
168
197
|
|
|
169
198
|
### All-time vs. session
|
|
170
199
|
|
|
171
|
-
- **Events sent / Quarantine**
|
|
172
|
-
- **This session**
|
|
200
|
+
- **Events sent / Quarantine** = all-time totals, persisted across gateway restarts
|
|
201
|
+
- **This session** = since the last gateway restart
|
|
173
202
|
- **Last sync** = the most recent poll cycle only
|
|
174
203
|
|
|
175
|
-
|
|
204
|
+
### Warnings
|
|
205
|
+
|
|
206
|
+
When status shows `⚠️ Capture health degraded`, the plugin is connected but capture activity is not syncing successfully. This is not triggered by idle sessions.
|
|
207
|
+
|
|
208
|
+
Use `Last capture` as the first diagnostic:
|
|
209
|
+
- Recent `Last capture` + warning present → investigate sync pipeline health
|
|
210
|
+
- Old `Last capture` + no warning → normal idle behavior
|
|
176
211
|
|
|
177
212
|
---
|
|
178
213
|
|
|
@@ -195,11 +230,47 @@ Shield captures agent activity locally, applies on-device redaction, and forward
|
|
|
195
230
|
|
|
196
231
|
---
|
|
197
232
|
|
|
233
|
+
## What is sent to the platform
|
|
234
|
+
|
|
235
|
+
Shield uses two separate channels with different privacy properties:
|
|
236
|
+
|
|
237
|
+
### Telemetry (operational identity)
|
|
238
|
+
|
|
239
|
+
Sent every ~5 minutes over HTTPS. This tells the platform *where* the instance is running, not *what* it does. It does **not** pass through the redaction pipeline.
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"machine": {
|
|
244
|
+
"hostname": "openclaw-agent",
|
|
245
|
+
"os": "linux",
|
|
246
|
+
"arch": "x64",
|
|
247
|
+
"node_version": "v22.x.x",
|
|
248
|
+
"public_ip": "1.2.3.4"
|
|
249
|
+
},
|
|
250
|
+
"software": {
|
|
251
|
+
"plugin_version": "0.3.x",
|
|
252
|
+
"openclaw_version": "2026.x.x",
|
|
253
|
+
"agent_label": "main",
|
|
254
|
+
"instance_name": "openclaw-agent"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Telemetry fields like `hostname`, `public_ip`, and `instance_name` are operational identity signals used for geo-correlation in detection rules and instance health monitoring. They travel over HTTPS and are never exposed publicly.
|
|
260
|
+
|
|
261
|
+
### Events (behavioural data)
|
|
262
|
+
|
|
263
|
+
Sent every poll cycle over HTTPS. This is the data stream subject to the full redaction pipeline. Per-event fields include: tool name, redacted command/path/output, timestamp, session ID, and action type. Sensitive values are replaced with `category:hash` tokens before transmission.
|
|
264
|
+
|
|
265
|
+
> **The distinction:** telemetry = *who/where the instance is* (slim, operational). Events = *what the agent did* (redacted, privacy-protected).
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
198
269
|
## How your data is protected
|
|
199
270
|
|
|
200
|
-
**Redaction** runs locally before any data leaves your machine. The redactor detects hostnames, usernames, file paths, API keys, and secrets — replacing them with deterministic `category:hash` tokens. The token→original mapping is stored in an AES-256-GCM encrypted vault (`~/.openclaw/shield/data/redaction-vault.json`, mode 0600).
|
|
271
|
+
**Redaction** runs locally before any data leaves your machine. The redactor detects hostnames, usernames, file paths, API keys, and secrets — replacing them with deterministic `category:hash` tokens. The token→original mapping is stored in an AES-256-GCM encrypted vault (`~/.openclaw/shield/data/redaction-vault.json`, mode 0600).
|
|
201
272
|
|
|
202
|
-
**Authentication** uses HMAC-SHA256 with a per-instance
|
|
273
|
+
**Authentication** uses HMAC-SHA256 with a per-instance key. Every request is signed — requests with invalid signatures are rejected.
|
|
203
274
|
|
|
204
275
|
**Credentials** are stored locally at `~/.openclaw/shield/config.env` (mode 0600) and are never transmitted.
|
|
205
276
|
|
|
@@ -207,10 +278,14 @@ Shield captures agent activity locally, applies on-device redaction, and forward
|
|
|
207
278
|
|
|
208
279
|
## Upgrading
|
|
209
280
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
281
|
+
```bash
|
|
282
|
+
openclaw plugins update shield
|
|
283
|
+
openclaw shield status
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Cursors and credentials are preserved across upgrades. See the CHANGELOG (available on the Shield portal at uss.upx.com) for version history.
|
|
287
|
+
|
|
288
|
+
> **"Integrity drift detected"** during upgrade is expected — OpenClaw warns when plugin files change, which always happens on a legitimate upgrade. This only indicates a real problem if you see it without having explicitly upgraded.
|
|
214
289
|
|
|
215
290
|
---
|
|
216
291
|
|
|
@@ -218,43 +293,79 @@ Shield captures agent activity locally, applies on-device redaction, and forward
|
|
|
218
293
|
|
|
219
294
|
**Shield shows "not activated"**
|
|
220
295
|
→ Get your Installation Key at [uss.upx.com](https://uss.upx.com) → APPS → OpenClaw Shield → Install
|
|
221
|
-
→ Add to `openclaw.json
|
|
222
|
-
```json
|
|
223
|
-
"plugins": { "entries": { "shield": { "config": { "installationKey": "YOUR_KEY" } } } }
|
|
224
|
-
```
|
|
225
|
-
→ Restart OpenClaw
|
|
296
|
+
→ Add to `openclaw.json` and restart the gateway (see Manual Installation above)
|
|
226
297
|
|
|
227
|
-
**Activated but no events
|
|
228
|
-
→ Run `openclaw shield status` — check Failures count and Last sync time
|
|
229
|
-
→
|
|
230
|
-
→
|
|
298
|
+
**Activated but no events after 5 minutes**
|
|
299
|
+
→ Run `openclaw shield status` — check `Failures` count and `Last sync` time
|
|
300
|
+
→ Check `Last capture`: if it's recent, Shield is capturing but may have a sync issue — restart the gateway
|
|
301
|
+
→ If `Last capture` is old, your agent may not have used any tools yet — try running a command and check again
|
|
302
|
+
→ Enable debug logging: `LOG_LEVEL=debug openclaw start`
|
|
231
303
|
|
|
232
304
|
**High CPU or memory usage**
|
|
233
|
-
→ Increase poll interval
|
|
234
|
-
→ Shield processes at most 5000 events per poll cycle regardless of backlog size
|
|
305
|
+
→ Increase poll interval: `"pollIntervalMs": 60000` in plugin config (default: 30000ms)
|
|
235
306
|
|
|
236
307
|
**Cursor file issues**
|
|
237
|
-
→
|
|
238
|
-
→ To reset: delete the file — Shield will reinitialize on next poll (some events may re-process)
|
|
239
|
-
|
|
240
|
-
**Enable debug logging**
|
|
241
|
-
→ `LOG_LEVEL=debug openclaw start` — verbose output including poll cycles and send results
|
|
308
|
+
→ To reset: `rm ~/.openclaw/shield/data/cursors.json` — Shield reinitializes on next poll
|
|
242
309
|
|
|
243
310
|
**Dry-run mode (no events sent)**
|
|
244
311
|
→ Add `"dryRun": true` to plugin config — events are processed and logged but never transmitted
|
|
245
312
|
|
|
246
313
|
---
|
|
247
314
|
|
|
315
|
+
## FAQ
|
|
316
|
+
|
|
317
|
+
**How long until I see my first event on the platform?**
|
|
318
|
+
Within 1–2 minutes of activation. Shield polls every ~30 seconds; the platform processes events within seconds of receipt.
|
|
319
|
+
|
|
320
|
+
**How do I verify it's working end-to-end?**
|
|
321
|
+
Run `openclaw shield status` and check that `Events sent` is increasing and `Last capture` is recent. Then visit [uss.upx.com](https://uss.upx.com) and check your instance — you should see events flowing within 2 minutes.
|
|
322
|
+
|
|
323
|
+
**What if I don't see any events after 5 minutes?**
|
|
324
|
+
Check `openclaw shield status` for elevated `Failures` or a stale `Last sync`. If failures are high, restart the gateway. If `Last capture` is old, your agent hasn't used any tools — run a command to generate activity.
|
|
325
|
+
|
|
326
|
+
**Can I run Shield alongside other plugins?**
|
|
327
|
+
Yes. Shield runs as a passive observer — it hooks into the event stream and does not interfere with other plugins or agent behavior.
|
|
328
|
+
|
|
329
|
+
**Is my Installation Key burned if activation fails?**
|
|
330
|
+
No. The key is consumed only on the first *successful* activation. If the attempt fails (network issue, config error), you can fix the issue and retry with the same key.
|
|
331
|
+
|
|
332
|
+
**Where is the changelog?**
|
|
333
|
+
the CHANGELOG, available on the Shield portal at uss.upx.com
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## Subscription expiry
|
|
338
|
+
|
|
339
|
+
When a subscription lapses or the monthly event quota is exhausted, Shield detects this on the next ingest call and will:
|
|
340
|
+
|
|
341
|
+
1. Log a clear warning
|
|
342
|
+
2. Stop transmitting events — **events are dropped, not queued**
|
|
343
|
+
3. Show elevated consecutive failures in `openclaw shield status`
|
|
344
|
+
|
|
345
|
+
Events generated during an expiry or quota period are permanently lost. Renew your subscription at [uss.upx.com](https://uss.upx.com) and restart the gateway to resume.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
248
349
|
## Uninstalling
|
|
249
350
|
|
|
250
351
|
```bash
|
|
251
352
|
openclaw plugins uninstall shield
|
|
252
353
|
```
|
|
253
354
|
|
|
254
|
-
|
|
355
|
+
This removes the plugin code. To fully remove all local Shield data:
|
|
356
|
+
|
|
357
|
+
```bash
|
|
358
|
+
rm -rf ~/.openclaw/shield/
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
This deletes credentials, cursors, IP cache, and the redaction vault.
|
|
362
|
+
|
|
363
|
+
> **Note:** The redaction vault (`data/redaction-vault.json`) contains the mapping from redaction tokens back to original values. Deleting it means you can no longer reverse-lookup redacted values from past events. Retain this file for as long as your data retention policy requires.
|
|
364
|
+
|
|
365
|
+
Platform-side instance data can be managed via [uss.upx.com](https://uss.upx.com).
|
|
255
366
|
|
|
256
367
|
---
|
|
257
368
|
|
|
258
369
|
## Need help?
|
|
259
370
|
|
|
260
|
-
Visit
|
|
371
|
+
Visit [uss.upx.com](https://uss.upx.com) or contact your Shield administrator.
|
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,16 @@ interface StartGuard {
|
|
|
10
10
|
reset: () => void;
|
|
11
11
|
}
|
|
12
12
|
export declare function createStartGuard(): StartGuard;
|
|
13
|
+
interface StatusWarningInput {
|
|
14
|
+
running: boolean;
|
|
15
|
+
lastPollAt?: number | null;
|
|
16
|
+
lastSyncAt?: number | null;
|
|
17
|
+
lastCaptureAt?: number | null;
|
|
18
|
+
captureSeenSinceLastSync?: boolean;
|
|
19
|
+
consecutiveFailures?: number | null;
|
|
20
|
+
now?: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function getStatusWarnings(input: StatusWarningInput): string[];
|
|
13
23
|
interface OpenClawPluginAPI {
|
|
14
24
|
config?: Record<string, unknown>;
|
|
15
25
|
pluginConfig?: Record<string, unknown>;
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ exports.resolveInstallationKey = resolveInstallationKey;
|
|
|
38
38
|
exports.maskPluginConfigForLogs = maskPluginConfigForLogs;
|
|
39
39
|
exports.createSingleflightRunner = createSingleflightRunner;
|
|
40
40
|
exports.createStartGuard = createStartGuard;
|
|
41
|
+
exports.getStatusWarnings = getStatusWarnings;
|
|
41
42
|
const config_1 = require("./src/config");
|
|
42
43
|
const log_1 = require("./src/log");
|
|
43
44
|
const log = __importStar(require("./src/log"));
|
|
@@ -94,6 +95,13 @@ async function performAutoRegistration(installationKey) {
|
|
|
94
95
|
const configDir = (0, path_1.join)(config_1.SHIELD_CONFIG_PATH, '..');
|
|
95
96
|
if (!(0, fs_1.existsSync)(configDir))
|
|
96
97
|
(0, fs_1.mkdirSync)(configDir, { recursive: true });
|
|
98
|
+
if ((0, fs_1.existsSync)(config_1.SHIELD_CONFIG_PATH)) {
|
|
99
|
+
const existing = (0, config_1.loadCredentials)();
|
|
100
|
+
if (hasValidCredentials(existing)) {
|
|
101
|
+
log.info('shield', 'Existing credentials found and valid — skipping config.env overwrite.');
|
|
102
|
+
return existing;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
97
105
|
const envContent = [
|
|
98
106
|
`# Shield API Configuration — auto-generated by OpenClaw plugin on ${new Date().toISOString()}`,
|
|
99
107
|
`SHIELD_API_URL=${SHIELD_API_URL}`,
|
|
@@ -262,6 +270,35 @@ function readPersistedState() {
|
|
|
262
270
|
return null;
|
|
263
271
|
}
|
|
264
272
|
}
|
|
273
|
+
const STALE_POLL_WARN_MS = 2 * 60 * 1000;
|
|
274
|
+
const STALE_SYNC_WARN_MS = 15 * 60 * 1000;
|
|
275
|
+
const CONSECUTIVE_FAILURES_WARN = 5;
|
|
276
|
+
function getStatusWarnings(input) {
|
|
277
|
+
const warnings = [];
|
|
278
|
+
if (!input.running)
|
|
279
|
+
return warnings;
|
|
280
|
+
const now = input.now ?? Date.now();
|
|
281
|
+
const lastPollAt = input.lastPollAt ?? 0;
|
|
282
|
+
const lastSyncAt = input.lastSyncAt ?? 0;
|
|
283
|
+
const lastCaptureAt = input.lastCaptureAt ?? 0;
|
|
284
|
+
const captureSeenSinceLastSync = input.captureSeenSinceLastSync ?? false;
|
|
285
|
+
const consecutiveFailures = input.consecutiveFailures ?? 0;
|
|
286
|
+
if (!lastPollAt) {
|
|
287
|
+
warnings.push('Hooks may not be initialized: service is running but no poll has executed yet.');
|
|
288
|
+
}
|
|
289
|
+
else if (now - lastPollAt > STALE_POLL_WARN_MS) {
|
|
290
|
+
warnings.push('Polling looks stale while connected; event capture may be paused after reload.');
|
|
291
|
+
}
|
|
292
|
+
const staleSinceSync = Boolean(lastSyncAt) && (now - lastSyncAt > STALE_SYNC_WARN_MS);
|
|
293
|
+
const staleSinceCaptureWithoutSync = !lastSyncAt && Boolean(lastCaptureAt) && (now - lastCaptureAt > STALE_SYNC_WARN_MS);
|
|
294
|
+
if (captureSeenSinceLastSync && (staleSinceSync || staleSinceCaptureWithoutSync)) {
|
|
295
|
+
warnings.push('Capture activity observed, but no successful event sync in a prolonged interval.');
|
|
296
|
+
}
|
|
297
|
+
if (consecutiveFailures >= CONSECUTIVE_FAILURES_WARN) {
|
|
298
|
+
warnings.push(`Consecutive poll failures are elevated (${consecutiveFailures}).`);
|
|
299
|
+
}
|
|
300
|
+
return warnings;
|
|
301
|
+
}
|
|
265
302
|
const state = {
|
|
266
303
|
activated: false,
|
|
267
304
|
running: false,
|
|
@@ -271,8 +308,11 @@ const state = {
|
|
|
271
308
|
quarantineCount: 0,
|
|
272
309
|
consecutiveFailures: 0,
|
|
273
310
|
instanceId: '',
|
|
311
|
+
lastCaptureAt: 0,
|
|
312
|
+
captureSeenSinceLastSync: false,
|
|
274
313
|
lastSync: null,
|
|
275
314
|
};
|
|
315
|
+
let teardownPreviousRuntime = null;
|
|
276
316
|
const MAX_BACKOFF_MS = 5 * 60 * 1000;
|
|
277
317
|
const TELEMETRY_INTERVAL_MS = 5 * 60 * 1000;
|
|
278
318
|
function getBackoffInterval(baseMs) {
|
|
@@ -319,12 +359,35 @@ function printActivatedStatus() {
|
|
|
319
359
|
if (shortId)
|
|
320
360
|
console.log(` Instance: ${shortId}`);
|
|
321
361
|
console.log(` Last poll: ${lastPollLabel}`);
|
|
362
|
+
const lastCaptureMs = s.lastCaptureAt ? Date.now() - s.lastCaptureAt : null;
|
|
363
|
+
const lastCaptureLabel = s.lastCaptureAt
|
|
364
|
+
? (lastCaptureMs < 60_000 ? `${Math.round(lastCaptureMs / 1000)}s ago`
|
|
365
|
+
: lastCaptureMs < 3_600_000 ? `${Math.floor(lastCaptureMs / 60_000)}m ago`
|
|
366
|
+
: `${(lastCaptureMs / 3_600_000).toFixed(1)}h ago`)
|
|
367
|
+
: null;
|
|
368
|
+
if (lastCaptureLabel)
|
|
369
|
+
console.log(` Last capture: ${lastCaptureLabel}`);
|
|
322
370
|
const allTime = (s.allTime ?? readAllTimeStats());
|
|
323
371
|
console.log(` Events sent: ${allTime.eventsProcessed.toLocaleString()} (all-time)`);
|
|
324
372
|
console.log(` Quarantine: ${allTime.quarantineCount.toLocaleString()} (all-time)`);
|
|
325
373
|
console.log(` Failures: ${s.consecutiveFailures ?? 0} (consecutive)`);
|
|
326
374
|
if (s.pid)
|
|
327
375
|
console.log(` Daemon PID: ${s.pid}`);
|
|
376
|
+
const statusWarnings = getStatusWarnings({
|
|
377
|
+
running: isRunning,
|
|
378
|
+
lastPollAt: s.lastPollAt ?? null,
|
|
379
|
+
lastSyncAt: s.lastSync?.at ?? null,
|
|
380
|
+
lastCaptureAt: s.lastCaptureAt ?? null,
|
|
381
|
+
captureSeenSinceLastSync: Boolean(s.captureSeenSinceLastSync ?? false),
|
|
382
|
+
consecutiveFailures: s.consecutiveFailures ?? 0,
|
|
383
|
+
});
|
|
384
|
+
if (statusWarnings.length > 0) {
|
|
385
|
+
console.log(' Warning: ⚠️ Capture health degraded');
|
|
386
|
+
for (const warning of statusWarnings) {
|
|
387
|
+
console.log(` - ${warning}`);
|
|
388
|
+
}
|
|
389
|
+
console.log(' - If this persists, run: openclaw gateway restart');
|
|
390
|
+
}
|
|
328
391
|
const startedAt = s.startedAt;
|
|
329
392
|
if (startedAt) {
|
|
330
393
|
const uptimeMs = Date.now() - startedAt;
|
|
@@ -424,6 +487,51 @@ exports.default = {
|
|
|
424
487
|
let telemetryHandle = null;
|
|
425
488
|
const startGuard = createStartGuard();
|
|
426
489
|
let onSignalHandler = null;
|
|
490
|
+
let runtimeGeneration = 0;
|
|
491
|
+
const clearRuntimeHandles = () => {
|
|
492
|
+
if (pollHandle) {
|
|
493
|
+
clearTimeout(pollHandle);
|
|
494
|
+
pollHandle = null;
|
|
495
|
+
}
|
|
496
|
+
if (telemetryHandle) {
|
|
497
|
+
clearInterval(telemetryHandle);
|
|
498
|
+
telemetryHandle = null;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
const detachSignalHandlers = () => {
|
|
502
|
+
if (!onSignalHandler)
|
|
503
|
+
return;
|
|
504
|
+
process.off('SIGTERM', onSignalHandler);
|
|
505
|
+
process.off('SIGINT', onSignalHandler);
|
|
506
|
+
onSignalHandler = null;
|
|
507
|
+
};
|
|
508
|
+
const cleanupRuntime = async (opts) => {
|
|
509
|
+
runtimeGeneration++;
|
|
510
|
+
pollFn = null;
|
|
511
|
+
clearRuntimeHandles();
|
|
512
|
+
detachSignalHandlers();
|
|
513
|
+
const markStopped = opts?.markStopped ?? true;
|
|
514
|
+
if (markStopped) {
|
|
515
|
+
state.running = false;
|
|
516
|
+
markStateDirty();
|
|
517
|
+
persistState();
|
|
518
|
+
}
|
|
519
|
+
if (opts?.resetGuard) {
|
|
520
|
+
startGuard.reset();
|
|
521
|
+
}
|
|
522
|
+
if (opts?.flushRedactor) {
|
|
523
|
+
try {
|
|
524
|
+
const { flush: flushRedactor } = await Promise.resolve().then(() => __importStar(require('./src/redactor')));
|
|
525
|
+
flushRedactor();
|
|
526
|
+
}
|
|
527
|
+
catch { }
|
|
528
|
+
}
|
|
529
|
+
};
|
|
530
|
+
if (teardownPreviousRuntime) {
|
|
531
|
+
void teardownPreviousRuntime()
|
|
532
|
+
.catch((err) => log.warn('shield', `Runtime cleanup before re-register failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
533
|
+
}
|
|
534
|
+
teardownPreviousRuntime = () => cleanupRuntime({ markStopped: true, resetGuard: true, flushRedactor: false });
|
|
427
535
|
api.registerService({
|
|
428
536
|
id: 'shield-monitor',
|
|
429
537
|
async start() {
|
|
@@ -432,6 +540,8 @@ exports.default = {
|
|
|
432
540
|
return;
|
|
433
541
|
}
|
|
434
542
|
try {
|
|
543
|
+
await cleanupRuntime({ markStopped: false, resetGuard: false, flushRedactor: false });
|
|
544
|
+
const activeGeneration = ++runtimeGeneration;
|
|
435
545
|
let credentials = (0, config_1.loadCredentials)();
|
|
436
546
|
let validCreds = hasValidCredentials(credentials);
|
|
437
547
|
if (!validCreds && installationKey) {
|
|
@@ -480,7 +590,7 @@ exports.default = {
|
|
|
480
590
|
state.running = true;
|
|
481
591
|
persistState();
|
|
482
592
|
const runTelemetry = async () => {
|
|
483
|
-
if (!state.running)
|
|
593
|
+
if (!state.running || activeGeneration !== runtimeGeneration)
|
|
484
594
|
return;
|
|
485
595
|
const hostSnapshot = config.collectHostMetrics ? generateHostTelemetry() : null;
|
|
486
596
|
const hostMeta = hostSnapshot?.event?.tool_metadata;
|
|
@@ -503,18 +613,24 @@ exports.default = {
|
|
|
503
613
|
},
|
|
504
614
|
};
|
|
505
615
|
const result = await reportInstance(instancePayload, config.credentials);
|
|
616
|
+
if (activeGeneration !== runtimeGeneration)
|
|
617
|
+
return;
|
|
506
618
|
log.info('shield', `Instance report → Platform: success=${result.ok}`);
|
|
507
619
|
};
|
|
508
620
|
const runTelemetrySingleflight = createSingleflightRunner(runTelemetry);
|
|
509
621
|
runTelemetrySingleflight().catch((err) => log.error('shield', `Telemetry error: ${err instanceof Error ? err.message : String(err)}`));
|
|
510
622
|
telemetryHandle = setInterval(() => {
|
|
623
|
+
if (activeGeneration !== runtimeGeneration || !state.running)
|
|
624
|
+
return;
|
|
511
625
|
runTelemetrySingleflight().catch((err) => log.error('shield', `Telemetry error: ${err instanceof Error ? err.message : String(err)}`));
|
|
512
626
|
}, TELEMETRY_INTERVAL_MS);
|
|
513
627
|
const poll = async () => {
|
|
514
|
-
if (!state.running)
|
|
628
|
+
if (!state.running || activeGeneration !== runtimeGeneration)
|
|
515
629
|
return;
|
|
516
630
|
try {
|
|
517
631
|
const entries = await fetchNewEntries(config);
|
|
632
|
+
if (activeGeneration !== runtimeGeneration)
|
|
633
|
+
return;
|
|
518
634
|
if (entries.length === 0) {
|
|
519
635
|
commitCursors(config, []);
|
|
520
636
|
state.consecutiveFailures = 0;
|
|
@@ -523,6 +639,9 @@ exports.default = {
|
|
|
523
639
|
persistState();
|
|
524
640
|
return;
|
|
525
641
|
}
|
|
642
|
+
state.lastCaptureAt = Date.now();
|
|
643
|
+
state.captureSeenSinceLastSync = true;
|
|
644
|
+
markStateDirty();
|
|
526
645
|
let envelopes = transformEntries(entries);
|
|
527
646
|
const { valid: validEvents, quarantined } = validate(envelopes.map(e => e.event));
|
|
528
647
|
if (quarantined > 0) {
|
|
@@ -536,6 +655,8 @@ exports.default = {
|
|
|
536
655
|
envelopes = envelopes.map(e => redactEvent(e));
|
|
537
656
|
}
|
|
538
657
|
const results = await sendEvents(envelopes, config);
|
|
658
|
+
if (activeGeneration !== runtimeGeneration)
|
|
659
|
+
return;
|
|
539
660
|
const needsReg = results.some(r => r.needsRegistration);
|
|
540
661
|
if (needsReg) {
|
|
541
662
|
log.error('shield', 'Instance not registered on platform — Shield deactivated.');
|
|
@@ -551,6 +672,8 @@ exports.default = {
|
|
|
551
672
|
markStateDirty();
|
|
552
673
|
persistState();
|
|
553
674
|
await new Promise(r => setTimeout(r, waitMs));
|
|
675
|
+
if (activeGeneration !== runtimeGeneration)
|
|
676
|
+
return;
|
|
554
677
|
return;
|
|
555
678
|
}
|
|
556
679
|
const accepted = results.reduce((sum, r) => sum + (r.success ? r.eventCount : 0), 0);
|
|
@@ -567,6 +690,7 @@ exports.default = {
|
|
|
567
690
|
}
|
|
568
691
|
const lastSync = { at: Date.now(), eventCount: accepted, eventTypes: syncEventTypes };
|
|
569
692
|
state.lastSync = lastSync;
|
|
693
|
+
state.captureSeenSinceLastSync = false;
|
|
570
694
|
writeAllTimeStats({ eventsProcessed: accepted, lastSync });
|
|
571
695
|
}
|
|
572
696
|
else {
|
|
@@ -578,6 +702,8 @@ exports.default = {
|
|
|
578
702
|
persistState();
|
|
579
703
|
}
|
|
580
704
|
catch (err) {
|
|
705
|
+
if (activeGeneration !== runtimeGeneration)
|
|
706
|
+
return;
|
|
581
707
|
state.consecutiveFailures++;
|
|
582
708
|
markStateDirty();
|
|
583
709
|
log.error('shield', `Poll error: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -587,14 +713,18 @@ exports.default = {
|
|
|
587
713
|
pollFn = runPollSingleflight;
|
|
588
714
|
await runPollSingleflight();
|
|
589
715
|
const schedulePoll = () => {
|
|
590
|
-
if (!state.running)
|
|
716
|
+
if (!state.running || activeGeneration !== runtimeGeneration)
|
|
591
717
|
return;
|
|
592
718
|
const interval = getBackoffInterval(config.pollIntervalMs);
|
|
593
719
|
if (interval !== config.pollIntervalMs) {
|
|
594
720
|
log.warn('shield', `Backing off: next poll in ${Math.round(interval / 1000)}s (${state.consecutiveFailures} consecutive failures)`);
|
|
595
721
|
}
|
|
596
722
|
pollHandle = setTimeout(() => {
|
|
723
|
+
if (activeGeneration !== runtimeGeneration || !state.running)
|
|
724
|
+
return;
|
|
597
725
|
runPollSingleflight().catch((err) => {
|
|
726
|
+
if (activeGeneration !== runtimeGeneration)
|
|
727
|
+
return;
|
|
598
728
|
state.consecutiveFailures++;
|
|
599
729
|
log.error('shield', `Poll error (unhandled): ${err instanceof Error ? err.message : String(err)}`);
|
|
600
730
|
}).finally(() => {
|
|
@@ -606,23 +736,7 @@ exports.default = {
|
|
|
606
736
|
onSignalHandler = async () => {
|
|
607
737
|
if (!state.running)
|
|
608
738
|
return;
|
|
609
|
-
|
|
610
|
-
startGuard.reset();
|
|
611
|
-
markStateDirty();
|
|
612
|
-
persistState();
|
|
613
|
-
if (pollHandle) {
|
|
614
|
-
clearTimeout(pollHandle);
|
|
615
|
-
pollHandle = null;
|
|
616
|
-
}
|
|
617
|
-
if (telemetryHandle) {
|
|
618
|
-
clearInterval(telemetryHandle);
|
|
619
|
-
telemetryHandle = null;
|
|
620
|
-
}
|
|
621
|
-
try {
|
|
622
|
-
const { flush: fr } = await Promise.resolve().then(() => __importStar(require('./src/redactor')));
|
|
623
|
-
fr();
|
|
624
|
-
}
|
|
625
|
-
catch { }
|
|
739
|
+
await cleanupRuntime({ markStopped: true, resetGuard: true, flushRedactor: true });
|
|
626
740
|
log.info('shield', 'Service stopped (signal)');
|
|
627
741
|
};
|
|
628
742
|
process.once('SIGTERM', onSignalHandler);
|
|
@@ -635,31 +749,10 @@ exports.default = {
|
|
|
635
749
|
}
|
|
636
750
|
},
|
|
637
751
|
async stop() {
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
markStateDirty();
|
|
643
|
-
persistState();
|
|
644
|
-
if (pollHandle) {
|
|
645
|
-
clearTimeout(pollHandle);
|
|
646
|
-
pollHandle = null;
|
|
647
|
-
}
|
|
648
|
-
if (telemetryHandle) {
|
|
649
|
-
clearInterval(telemetryHandle);
|
|
650
|
-
telemetryHandle = null;
|
|
651
|
-
}
|
|
652
|
-
if (onSignalHandler) {
|
|
653
|
-
process.off('SIGTERM', onSignalHandler);
|
|
654
|
-
process.off('SIGINT', onSignalHandler);
|
|
655
|
-
onSignalHandler = null;
|
|
656
|
-
}
|
|
657
|
-
try {
|
|
658
|
-
const { flush: flushRedactor } = await Promise.resolve().then(() => __importStar(require('./src/redactor')));
|
|
659
|
-
flushRedactor();
|
|
660
|
-
}
|
|
661
|
-
catch { }
|
|
662
|
-
log.info('shield', 'Service stopped');
|
|
752
|
+
const wasRunning = state.running;
|
|
753
|
+
await cleanupRuntime({ markStopped: true, resetGuard: true, flushRedactor: true });
|
|
754
|
+
if (wasRunning)
|
|
755
|
+
log.info('shield', 'Service stopped');
|
|
663
756
|
},
|
|
664
757
|
});
|
|
665
758
|
api.registerGatewayMethod('shield.status', ({ respond }) => {
|
|
@@ -669,6 +762,8 @@ exports.default = {
|
|
|
669
762
|
activated,
|
|
670
763
|
running: state.running,
|
|
671
764
|
lastPollAt: state.lastPollAt,
|
|
765
|
+
lastCaptureAt: state.lastCaptureAt,
|
|
766
|
+
captureSeenSinceLastSync: state.captureSeenSinceLastSync,
|
|
672
767
|
eventsProcessed: state.eventsProcessed,
|
|
673
768
|
quarantineCount: state.quarantineCount,
|
|
674
769
|
consecutiveFailures: state.consecutiveFailures,
|
package/dist/src/index.js
CHANGED
|
@@ -68,17 +68,20 @@ async function poll() {
|
|
|
68
68
|
if (now - lastTelemetryAt >= TELEMETRY_INTERVAL_MS) {
|
|
69
69
|
const hostSnapshot = config.collectHostMetrics ? (0, transformer_1.generateHostTelemetry)() : null;
|
|
70
70
|
const hostMeta = hostSnapshot?.event?.tool_metadata;
|
|
71
|
+
const agentId = process.env.OPENCLAW_AGENT_ID || 'main';
|
|
71
72
|
const instancePayload = {
|
|
72
73
|
machine: {
|
|
73
74
|
hostname: config.hostname,
|
|
74
75
|
os: process.platform,
|
|
75
76
|
arch: process.arch,
|
|
76
77
|
node_version: process.version,
|
|
78
|
+
public_ip: (0, transformer_1.getCachedPublicIp)() ?? undefined,
|
|
77
79
|
},
|
|
78
80
|
software: {
|
|
79
81
|
plugin_version: version_1.VERSION,
|
|
80
82
|
openclaw_version: (0, transformer_1.resolveOpenClawVersion)(),
|
|
81
|
-
agent_label: (0, transformer_1.resolveAgentLabel)(
|
|
83
|
+
agent_label: (0, transformer_1.resolveAgentLabel)(agentId),
|
|
84
|
+
instance_name: (0, transformer_1.resolveAgentLabel)(agentId) || config.hostname,
|
|
82
85
|
...(hostMeta && {
|
|
83
86
|
gateway_bind: hostMeta['openclaw.gateway_bind'],
|
|
84
87
|
webhook_configured: hostMeta['openclaw.webhook_configured'],
|
package/dist/src/sender.js
CHANGED
|
@@ -163,6 +163,16 @@ async function sendEvents(events, config) {
|
|
|
163
163
|
log.warn('sender', `Batch ${batchNum} attempt ${attempt + 1} — HTTP ${res.status}, retrying...`);
|
|
164
164
|
continue;
|
|
165
165
|
}
|
|
166
|
+
let parsedData = null;
|
|
167
|
+
try {
|
|
168
|
+
parsedData = JSON.parse(data);
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
171
|
+
if (res.status === 402 || (res.status === 429 && parsedData?.error === 'quota_exceeded')) {
|
|
172
|
+
log.warn('sender', `⚠ Event quota exhausted or subscription inactive — events are being dropped. Renew your subscription to resume delivery.`);
|
|
173
|
+
results.push({ success: false, statusCode: res.status, body: data, eventCount: batch.length });
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
166
176
|
const safeBody = sanitizeResponseBodyForLog(data);
|
|
167
177
|
log.error('sender', `Batch ${batchNum} — HTTP ${res.status}: ${safeBody}`);
|
|
168
178
|
consecutiveBatchFailures++;
|
|
@@ -10,6 +10,7 @@ export interface IngestPayload {
|
|
|
10
10
|
}
|
|
11
11
|
export declare function resolveOpenClawVersion(): string;
|
|
12
12
|
export declare function resolveAgentLabel(agentId: string): string;
|
|
13
|
+
export declare function getCachedPublicIp(): string | null;
|
|
13
14
|
export declare function resolveOutboundIp(): Promise<string | null>;
|
|
14
15
|
export declare function transformEntries(entries: RawEntry[]): EnvelopeEvent[];
|
|
15
16
|
export declare function generateHostTelemetry(): EnvelopeEvent | null;
|
package/dist/src/transformer.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.resolveOpenClawVersion = resolveOpenClawVersion;
|
|
37
37
|
exports.resolveAgentLabel = resolveAgentLabel;
|
|
38
|
+
exports.getCachedPublicIp = getCachedPublicIp;
|
|
38
39
|
exports.resolveOutboundIp = resolveOutboundIp;
|
|
39
40
|
exports.transformEntries = transformEntries;
|
|
40
41
|
exports.generateHostTelemetry = generateHostTelemetry;
|
|
@@ -116,6 +117,9 @@ function writeIpCache(ip) {
|
|
|
116
117
|
}
|
|
117
118
|
catch { }
|
|
118
119
|
}
|
|
120
|
+
function getCachedPublicIp() {
|
|
121
|
+
return readIpCache()?.ip ?? (_source?.ip_addresses[0] ?? null);
|
|
122
|
+
}
|
|
119
123
|
function resolveOutboundIp() {
|
|
120
124
|
return new Promise((resolve) => {
|
|
121
125
|
try {
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,60 +1,60 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
"uiHints": {
|
|
40
|
-
"installationKey": {
|
|
41
|
-
"label": "Installation Key",
|
|
42
|
-
"description": "One-time key from the Shield portal (https://uss.upx.com). Required for first-time activation only."
|
|
43
|
-
},
|
|
44
|
-
"enabled": {
|
|
45
|
-
"label": "Enable security monitoring"
|
|
46
|
-
},
|
|
47
|
-
"dryRun": {
|
|
48
|
-
"label": "Dry run (log events locally, do not transmit)"
|
|
49
|
-
},
|
|
50
|
-
"redactionEnabled": {
|
|
51
|
-
"label": "Redact sensitive values before transmitting"
|
|
52
|
-
},
|
|
53
|
-
"pollIntervalMs": {
|
|
54
|
-
"label": "Polling interval (milliseconds)"
|
|
2
|
+
"id": "shield",
|
|
3
|
+
"name": "OpenClaw Shield",
|
|
4
|
+
"description": "Real-time security monitoring — streams enriched, redacted security events to the Shield detection platform.",
|
|
5
|
+
"version": "0.3.16",
|
|
6
|
+
"skills": [
|
|
7
|
+
"./skills"
|
|
8
|
+
],
|
|
9
|
+
"configSchema": {
|
|
10
|
+
"type": "object",
|
|
11
|
+
"additionalProperties": false,
|
|
12
|
+
"properties": {
|
|
13
|
+
"installationKey": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "One-time installation key from the Shield portal. The plugin uses this to register the instance and obtain credentials automatically. Can be removed from config after first activation."
|
|
16
|
+
},
|
|
17
|
+
"enabled": {
|
|
18
|
+
"type": "boolean",
|
|
19
|
+
"default": true
|
|
20
|
+
},
|
|
21
|
+
"dryRun": {
|
|
22
|
+
"type": "boolean",
|
|
23
|
+
"default": false
|
|
24
|
+
},
|
|
25
|
+
"redactionEnabled": {
|
|
26
|
+
"type": "boolean",
|
|
27
|
+
"default": true
|
|
28
|
+
},
|
|
29
|
+
"pollIntervalMs": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"default": 30000
|
|
32
|
+
},
|
|
33
|
+
"collectHostMetrics": {
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"default": false
|
|
36
|
+
}
|
|
37
|
+
}
|
|
55
38
|
},
|
|
56
|
-
"
|
|
57
|
-
|
|
39
|
+
"uiHints": {
|
|
40
|
+
"installationKey": {
|
|
41
|
+
"label": "Installation Key",
|
|
42
|
+
"description": "One-time key from the Shield portal (https://uss.upx.com). Required for first-time activation only."
|
|
43
|
+
},
|
|
44
|
+
"enabled": {
|
|
45
|
+
"label": "Enable security monitoring"
|
|
46
|
+
},
|
|
47
|
+
"dryRun": {
|
|
48
|
+
"label": "Dry run (log events locally, do not transmit)"
|
|
49
|
+
},
|
|
50
|
+
"redactionEnabled": {
|
|
51
|
+
"label": "Redact sensitive values before transmitting"
|
|
52
|
+
},
|
|
53
|
+
"pollIntervalMs": {
|
|
54
|
+
"label": "Polling interval (milliseconds)"
|
|
55
|
+
},
|
|
56
|
+
"collectHostMetrics": {
|
|
57
|
+
"label": "Collect host telemetry metrics"
|
|
58
|
+
}
|
|
58
59
|
}
|
|
59
|
-
}
|
|
60
60
|
}
|
package/package.json
CHANGED
|
@@ -1,69 +1,72 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
"
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
2
|
+
"name": "@upx-us/shield",
|
|
3
|
+
"version": "0.3.16",
|
|
4
|
+
"description": "Security monitoring plugin for OpenClaw agents — streams enriched security events to the Shield detection platform",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"shield-bridge": "dist/src/index.js",
|
|
9
|
+
"shield-setup": "dist/src/setup.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/index.js",
|
|
13
|
+
"dist/index.d.ts",
|
|
14
|
+
"dist/src/**/*.js",
|
|
15
|
+
"dist/src/**/*.d.ts",
|
|
16
|
+
"openclaw.plugin.json",
|
|
17
|
+
"skills/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"prebuild": "npm run clean",
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
25
|
+
"lint": "tsc --noEmit",
|
|
26
|
+
"test": "node --require tsx/cjs --test --test-reporter spec tests/**/*.test.ts tests/*.test.ts",
|
|
27
|
+
"test:watch": "node --require tsx/cjs --test --watch tests/**/*.test.ts tests/*.test.ts",
|
|
28
|
+
"test:parser": "node tests/run-parser.js",
|
|
29
|
+
"test:parser:short": "node tests/run-parser.js --short",
|
|
30
|
+
"test:parser:verbose": "node tests/run-parser.js --verbose",
|
|
31
|
+
"test:parser:help": "node tests/run-parser.js help",
|
|
32
|
+
"dev": "tsx scripts/dev-harness.ts",
|
|
33
|
+
"dev:dry": "tsx scripts/dev-harness.ts --dry-run",
|
|
34
|
+
"generate:schemas": "tsx scripts/generate-schemas.ts",
|
|
35
|
+
"package:check": "node scripts/prepublish-check.js",
|
|
36
|
+
"package:build": "npm run build",
|
|
37
|
+
"package:validate": "npm run build && npm run test && npm run package:check",
|
|
38
|
+
"package:pack": "npm pack",
|
|
39
|
+
"package:publish": "npm run package:validate && npm publish --access public",
|
|
40
|
+
"start": "node dist/src/index.js",
|
|
41
|
+
"setup": "node dist/src/setup.js"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [
|
|
44
|
+
"openclaw",
|
|
45
|
+
"openclaw-plugin",
|
|
46
|
+
"security",
|
|
47
|
+
"monitoring",
|
|
48
|
+
"detection",
|
|
49
|
+
"siem",
|
|
50
|
+
"compliance"
|
|
51
|
+
],
|
|
52
|
+
"author": "UPX Security Services",
|
|
53
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"tag": "latest",
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"engines": {
|
|
59
|
+
"node": ">=20.0.0"
|
|
60
|
+
},
|
|
61
|
+
"openclaw": {
|
|
62
|
+
"extensions": [
|
|
63
|
+
"./dist/index.js"
|
|
64
|
+
]
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@types/node": "^25.2.3",
|
|
68
|
+
"ts-json-schema-generator": "^2.5.0",
|
|
69
|
+
"tsx": "^4.21.0",
|
|
70
|
+
"typescript": "^5.9.3"
|
|
71
|
+
}
|
|
69
72
|
}
|