@upx-us/shield 0.2.12-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of @upx-us/shield might be problematic. Click here for more details.
- package/LICENSE +38 -0
- package/README.md +96 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.js +365 -0
- package/dist/src/config.d.ts +43 -0
- package/dist/src/config.js +181 -0
- package/dist/src/events/base.d.ts +110 -0
- package/dist/src/events/base.js +61 -0
- package/dist/src/events/browser/enrich.d.ts +3 -0
- package/dist/src/events/browser/enrich.js +46 -0
- package/dist/src/events/browser/event.d.ts +10 -0
- package/dist/src/events/browser/event.js +2 -0
- package/dist/src/events/browser/index.d.ts +4 -0
- package/dist/src/events/browser/index.js +13 -0
- package/dist/src/events/browser/redactions.d.ts +2 -0
- package/dist/src/events/browser/redactions.js +4 -0
- package/dist/src/events/browser/validations.d.ts +3 -0
- package/dist/src/events/browser/validations.js +10 -0
- package/dist/src/events/cron/enrich.d.ts +3 -0
- package/dist/src/events/cron/enrich.js +44 -0
- package/dist/src/events/cron/event.d.ts +5 -0
- package/dist/src/events/cron/event.js +2 -0
- package/dist/src/events/cron/index.d.ts +4 -0
- package/dist/src/events/cron/index.js +13 -0
- package/dist/src/events/cron/redactions.d.ts +2 -0
- package/dist/src/events/cron/redactions.js +4 -0
- package/dist/src/events/cron/validations.d.ts +3 -0
- package/dist/src/events/cron/validations.js +4 -0
- package/dist/src/events/exec/enrich.d.ts +3 -0
- package/dist/src/events/exec/enrich.js +80 -0
- package/dist/src/events/exec/event.d.ts +11 -0
- package/dist/src/events/exec/event.js +2 -0
- package/dist/src/events/exec/index.d.ts +4 -0
- package/dist/src/events/exec/index.js +13 -0
- package/dist/src/events/exec/redactions.d.ts +3 -0
- package/dist/src/events/exec/redactions.js +12 -0
- package/dist/src/events/exec/validations.d.ts +3 -0
- package/dist/src/events/exec/validations.js +12 -0
- package/dist/src/events/file/enrich.d.ts +3 -0
- package/dist/src/events/file/enrich.js +63 -0
- package/dist/src/events/file/event.d.ts +11 -0
- package/dist/src/events/file/event.js +2 -0
- package/dist/src/events/file/index.d.ts +4 -0
- package/dist/src/events/file/index.js +13 -0
- package/dist/src/events/file/redactions.d.ts +2 -0
- package/dist/src/events/file/redactions.js +8 -0
- package/dist/src/events/file/validations.d.ts +3 -0
- package/dist/src/events/file/validations.js +10 -0
- package/dist/src/events/gateway/enrich.d.ts +3 -0
- package/dist/src/events/gateway/enrich.js +50 -0
- package/dist/src/events/gateway/event.d.ts +5 -0
- package/dist/src/events/gateway/event.js +2 -0
- package/dist/src/events/gateway/index.d.ts +4 -0
- package/dist/src/events/gateway/index.js +13 -0
- package/dist/src/events/gateway/redactions.d.ts +2 -0
- package/dist/src/events/gateway/redactions.js +4 -0
- package/dist/src/events/gateway/validations.d.ts +3 -0
- package/dist/src/events/gateway/validations.js +4 -0
- package/dist/src/events/generic/enrich.d.ts +3 -0
- package/dist/src/events/generic/enrich.js +30 -0
- package/dist/src/events/generic/event.d.ts +5 -0
- package/dist/src/events/generic/event.js +2 -0
- package/dist/src/events/generic/index.d.ts +5 -0
- package/dist/src/events/generic/index.js +14 -0
- package/dist/src/events/generic/redactions.d.ts +2 -0
- package/dist/src/events/generic/redactions.js +4 -0
- package/dist/src/events/generic/validations.d.ts +3 -0
- package/dist/src/events/generic/validations.js +4 -0
- package/dist/src/events/host-telemetry/enrich.d.ts +3 -0
- package/dist/src/events/host-telemetry/enrich.js +28 -0
- package/dist/src/events/host-telemetry/event.d.ts +4 -0
- package/dist/src/events/host-telemetry/event.js +2 -0
- package/dist/src/events/host-telemetry/index.d.ts +4 -0
- package/dist/src/events/host-telemetry/index.js +13 -0
- package/dist/src/events/host-telemetry/redactions.d.ts +2 -0
- package/dist/src/events/host-telemetry/redactions.js +4 -0
- package/dist/src/events/host-telemetry/validations.d.ts +3 -0
- package/dist/src/events/host-telemetry/validations.js +4 -0
- package/dist/src/events/index.d.ts +40 -0
- package/dist/src/events/index.js +39 -0
- package/dist/src/events/message/enrich.d.ts +3 -0
- package/dist/src/events/message/enrich.js +36 -0
- package/dist/src/events/message/event.d.ts +5 -0
- package/dist/src/events/message/event.js +2 -0
- package/dist/src/events/message/index.d.ts +4 -0
- package/dist/src/events/message/index.js +13 -0
- package/dist/src/events/message/redactions.d.ts +2 -0
- package/dist/src/events/message/redactions.js +4 -0
- package/dist/src/events/message/validations.d.ts +3 -0
- package/dist/src/events/message/validations.js +7 -0
- package/dist/src/events/sessions-spawn/enrich.d.ts +3 -0
- package/dist/src/events/sessions-spawn/enrich.js +40 -0
- package/dist/src/events/sessions-spawn/event.d.ts +9 -0
- package/dist/src/events/sessions-spawn/event.js +2 -0
- package/dist/src/events/sessions-spawn/index.d.ts +4 -0
- package/dist/src/events/sessions-spawn/index.js +13 -0
- package/dist/src/events/sessions-spawn/redactions.d.ts +2 -0
- package/dist/src/events/sessions-spawn/redactions.js +4 -0
- package/dist/src/events/sessions-spawn/validations.d.ts +3 -0
- package/dist/src/events/sessions-spawn/validations.js +4 -0
- package/dist/src/events/tool-result/enrich.d.ts +13 -0
- package/dist/src/events/tool-result/enrich.js +46 -0
- package/dist/src/events/tool-result/event.d.ts +7 -0
- package/dist/src/events/tool-result/event.js +2 -0
- package/dist/src/events/tool-result/index.d.ts +4 -0
- package/dist/src/events/tool-result/index.js +9 -0
- package/dist/src/events/tool-result/redactions.d.ts +2 -0
- package/dist/src/events/tool-result/redactions.js +7 -0
- package/dist/src/events/tool-result/validations.d.ts +3 -0
- package/dist/src/events/tool-result/validations.js +9 -0
- package/dist/src/events/web/enrich.d.ts +8 -0
- package/dist/src/events/web/enrich.js +78 -0
- package/dist/src/events/web/event.d.ts +10 -0
- package/dist/src/events/web/event.js +2 -0
- package/dist/src/events/web/index.d.ts +4 -0
- package/dist/src/events/web/index.js +13 -0
- package/dist/src/events/web/redactions.d.ts +2 -0
- package/dist/src/events/web/redactions.js +6 -0
- package/dist/src/events/web/validations.d.ts +3 -0
- package/dist/src/events/web/validations.js +10 -0
- package/dist/src/fetcher.d.ts +12 -0
- package/dist/src/fetcher.js +182 -0
- package/dist/src/host-collector.d.ts +1 -0
- package/dist/src/host-collector.js +200 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +210 -0
- package/dist/src/log.d.ts +39 -0
- package/dist/src/log.js +102 -0
- package/dist/src/redactor/base.d.ts +29 -0
- package/dist/src/redactor/base.js +9 -0
- package/dist/src/redactor/index.d.ts +27 -0
- package/dist/src/redactor/index.js +109 -0
- package/dist/src/redactor/strategies/command.d.ts +2 -0
- package/dist/src/redactor/strategies/command.js +19 -0
- package/dist/src/redactor/strategies/hostname.d.ts +2 -0
- package/dist/src/redactor/strategies/hostname.js +15 -0
- package/dist/src/redactor/strategies/index.d.ts +13 -0
- package/dist/src/redactor/strategies/index.js +25 -0
- package/dist/src/redactor/strategies/path.d.ts +2 -0
- package/dist/src/redactor/strategies/path.js +23 -0
- package/dist/src/redactor/strategies/secret-key.d.ts +2 -0
- package/dist/src/redactor/strategies/secret-key.js +22 -0
- package/dist/src/redactor/strategies/username.d.ts +2 -0
- package/dist/src/redactor/strategies/username.js +12 -0
- package/dist/src/redactor/vault.d.ts +25 -0
- package/dist/src/redactor/vault.js +209 -0
- package/dist/src/sender.d.ts +29 -0
- package/dist/src/sender.js +186 -0
- package/dist/src/setup.d.ts +10 -0
- package/dist/src/setup.js +222 -0
- package/dist/src/transformer.d.ts +26 -0
- package/dist/src/transformer.js +302 -0
- package/dist/src/validator.d.ts +17 -0
- package/dist/src/validator.js +110 -0
- package/dist/src/version.d.ts +1 -0
- package/dist/src/version.js +19 -0
- package/openclaw.plugin.json +52 -0
- package/package.json +64 -0
- package/skills/shield/SKILL.md +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Copyright (c) 2026 UPX Technologies, Inc. All rights reserved.
|
|
2
|
+
|
|
3
|
+
PROPRIETARY SOFTWARE LICENSE
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are the
|
|
6
|
+
proprietary and confidential property of UPX Technologies, Inc. ("UPX").
|
|
7
|
+
|
|
8
|
+
GRANT OF USE
|
|
9
|
+
Subject to the terms of a valid, active subscription agreement with UPX for
|
|
10
|
+
the OpenClaw Shield service, UPX grants you a limited, non-exclusive,
|
|
11
|
+
non-transferable, non-sublicensable license to install and use the Software
|
|
12
|
+
solely for the purpose of connecting to the OpenClaw Shield platform as part
|
|
13
|
+
of your subscription.
|
|
14
|
+
|
|
15
|
+
RESTRICTIONS
|
|
16
|
+
You may not, and you may not permit others to:
|
|
17
|
+
- Copy, modify, adapt, translate, or create derivative works of the Software
|
|
18
|
+
- Distribute, transfer, sublicense, lease, lend, or rent the Software
|
|
19
|
+
- Reverse engineer, disassemble, or decompile the Software
|
|
20
|
+
- Remove or alter any proprietary notices, labels, or marks on the Software
|
|
21
|
+
- Use the Software without an active OpenClaw Shield subscription
|
|
22
|
+
|
|
23
|
+
TERMINATION
|
|
24
|
+
This license is effective until terminated. It will terminate automatically
|
|
25
|
+
if you breach any term of this agreement or your subscription expires. Upon
|
|
26
|
+
termination, you must destroy all copies of the Software in your possession.
|
|
27
|
+
|
|
28
|
+
DISCLAIMER
|
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. UPX DISCLAIMS
|
|
30
|
+
ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES
|
|
31
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
|
|
32
|
+
|
|
33
|
+
IN NO EVENT SHALL UPX BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL,
|
|
34
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN CONNECTION WITH
|
|
35
|
+
THE SOFTWARE OR ITS USE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
36
|
+
|
|
37
|
+
For licensing inquiries: legal@upx.com
|
|
38
|
+
For support: https://upx.com
|
package/README.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# OpenClaw Shield
|
|
2
|
+
|
|
3
|
+
> **This plugin requires an active OpenClaw Shield subscription provided by UPX.**
|
|
4
|
+
> For more information, visit [upx.com](https://upx.com).
|
|
5
|
+
|
|
6
|
+
Real-time security monitoring for your OpenClaw agents — powered by the UPX Shield detection platform.
|
|
7
|
+
|
|
8
|
+
Shield runs silently alongside your OpenClaw Gateway, captures agent activity, and streams it to the Shield platform where security rules, playbooks, and case management give your team full visibility.
|
|
9
|
+
|
|
10
|
+
📖 **New?** See the [Getting Started guide](https://github.com/UPX-US/openclaw-shield-plugin/blob/main/docs/GETTING_STARTED.md) for a step-by-step walkthrough.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
openclaw plugins install @upx-us/shield@beta
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Restart the Gateway after install. Shield starts automatically.
|
|
19
|
+
|
|
20
|
+
## Activate
|
|
21
|
+
|
|
22
|
+
You'll need an **installation key** from your Shield admin. Run the setup wizard:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx -p @upx-us/shield@beta shield-setup
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
🛡️ OpenClaw Shield Setup
|
|
30
|
+
==========================
|
|
31
|
+
|
|
32
|
+
Installation Key (from Shield portal): ████████████████
|
|
33
|
+
Connecting... ok
|
|
34
|
+
Registering instance... ok
|
|
35
|
+
|
|
36
|
+
✅ Shield activated!
|
|
37
|
+
Restart your OpenClaw Gateway to start monitoring.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
That's it. Your instance is now registered and events will start flowing to the Shield platform.
|
|
41
|
+
|
|
42
|
+
## What data is collected
|
|
43
|
+
|
|
44
|
+
Shield captures **agent activity events** — the things your OpenClaw agent does:
|
|
45
|
+
|
|
46
|
+
| Event type | Examples |
|
|
47
|
+
|---|---|
|
|
48
|
+
| Shell commands | `git status`, `npm install`, `curl` calls |
|
|
49
|
+
| File operations | Read, write, edit — path and action only |
|
|
50
|
+
| Web requests | URLs fetched, search queries, browser actions |
|
|
51
|
+
| Messages sent | Channel, direction — never message content |
|
|
52
|
+
| Sessions spawned | Sub-agent launches |
|
|
53
|
+
|
|
54
|
+
Shield does **not** collect:
|
|
55
|
+
- Message content or conversation history
|
|
56
|
+
- File contents
|
|
57
|
+
- Credentials or secrets (see Redaction below)
|
|
58
|
+
- Anything outside of OpenClaw agent activity
|
|
59
|
+
|
|
60
|
+
## How your data is protected
|
|
61
|
+
|
|
62
|
+
**Redaction** runs locally before any data leaves your machine. The redactor automatically strips:
|
|
63
|
+
|
|
64
|
+
- API keys, tokens, and passwords
|
|
65
|
+
- File paths that look like sensitive locations (`~/.ssh`, credential files)
|
|
66
|
+
- Usernames and hostnames from command output
|
|
67
|
+
- Any string matching known secret patterns
|
|
68
|
+
|
|
69
|
+
You can verify what's being sent at any time by running:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
openclaw shield status
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Transmission** uses HTTPS with TLS 1.2+. Each instance has a unique signing key — your data is tied to your instance only and cannot be replayed or forged.
|
|
76
|
+
|
|
77
|
+
**Credentials** are stored locally at `~/.openclaw/shield/config.env` (mode 0600 — readable only by your user). They are never transmitted.
|
|
78
|
+
|
|
79
|
+
## Check status
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
openclaw shield status
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
Shield v0.2.1-beta (12s ago)
|
|
87
|
+
Running: true
|
|
88
|
+
Last poll: 2026-02-22T22:40:31Z
|
|
89
|
+
Events: 1,204
|
|
90
|
+
Quarantine: 0
|
|
91
|
+
Failures: 0
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Need help?
|
|
95
|
+
|
|
96
|
+
Contact your Shield administrator or reach out to UPX support.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw Shield — Plugin Entry Point
|
|
3
|
+
*
|
|
4
|
+
* This file is the OpenClaw plugin entry point, declared in package.json
|
|
5
|
+
* under `openclaw.extensions`. It registers the Shield plugin with the
|
|
6
|
+
* OpenClaw Gateway and starts the monitoring bridge as a managed service.
|
|
7
|
+
*
|
|
8
|
+
* The monitoring bridge runs as a background service within the Gateway
|
|
9
|
+
* process, polling session files and forwarding enriched security events
|
|
10
|
+
* to the Shield detection platform.
|
|
11
|
+
*
|
|
12
|
+
* Dual-mode design:
|
|
13
|
+
* - Plugin mode: this file, registered via api.registerService()
|
|
14
|
+
* - Standalone mode: src/index.ts, runs directly via `shield-bridge`
|
|
15
|
+
*/
|
|
16
|
+
interface OpenClawPluginAPI {
|
|
17
|
+
config?: Record<string, unknown>;
|
|
18
|
+
logger: {
|
|
19
|
+
info(msg: string): void;
|
|
20
|
+
warn(msg: string): void;
|
|
21
|
+
error(msg: string): void;
|
|
22
|
+
debug(msg: string): void;
|
|
23
|
+
};
|
|
24
|
+
registerService(service: {
|
|
25
|
+
id: string;
|
|
26
|
+
start: () => void | Promise<void>;
|
|
27
|
+
stop: () => void | Promise<void>;
|
|
28
|
+
}): void;
|
|
29
|
+
registerGatewayMethod(method: string, handler: (ctx: {
|
|
30
|
+
respond: (ok: boolean, data: unknown) => void;
|
|
31
|
+
}) => void): void;
|
|
32
|
+
registerCli(setup: (ctx: {
|
|
33
|
+
program: any;
|
|
34
|
+
}) => void, opts?: {
|
|
35
|
+
commands: string[];
|
|
36
|
+
}): void;
|
|
37
|
+
}
|
|
38
|
+
declare const _default: {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
register(api: OpenClawPluginAPI): void;
|
|
42
|
+
};
|
|
43
|
+
export default _default;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenClaw Shield — Plugin Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This file is the OpenClaw plugin entry point, declared in package.json
|
|
6
|
+
* under `openclaw.extensions`. It registers the Shield plugin with the
|
|
7
|
+
* OpenClaw Gateway and starts the monitoring bridge as a managed service.
|
|
8
|
+
*
|
|
9
|
+
* The monitoring bridge runs as a background service within the Gateway
|
|
10
|
+
* process, polling session files and forwarding enriched security events
|
|
11
|
+
* to the Shield detection platform.
|
|
12
|
+
*
|
|
13
|
+
* Dual-mode design:
|
|
14
|
+
* - Plugin mode: this file, registered via api.registerService()
|
|
15
|
+
* - Standalone mode: src/index.ts, runs directly via `shield-bridge`
|
|
16
|
+
*/
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
22
|
+
}
|
|
23
|
+
Object.defineProperty(o, k2, desc);
|
|
24
|
+
}) : (function(o, m, k, k2) {
|
|
25
|
+
if (k2 === undefined) k2 = k;
|
|
26
|
+
o[k2] = m[k];
|
|
27
|
+
}));
|
|
28
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
+
}) : function(o, v) {
|
|
31
|
+
o["default"] = v;
|
|
32
|
+
});
|
|
33
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
+
var ownKeys = function(o) {
|
|
35
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
+
var ar = [];
|
|
37
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
+
return ar;
|
|
39
|
+
};
|
|
40
|
+
return ownKeys(o);
|
|
41
|
+
};
|
|
42
|
+
return function (mod) {
|
|
43
|
+
if (mod && mod.__esModule) return mod;
|
|
44
|
+
var result = {};
|
|
45
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
+
__setModuleDefault(result, mod);
|
|
47
|
+
return result;
|
|
48
|
+
};
|
|
49
|
+
})();
|
|
50
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
+
const config_1 = require("./src/config");
|
|
52
|
+
const log_1 = require("./src/log");
|
|
53
|
+
const log = __importStar(require("./src/log"));
|
|
54
|
+
const version_1 = require("./src/version");
|
|
55
|
+
const fs_1 = require("fs");
|
|
56
|
+
const path_1 = require("path");
|
|
57
|
+
const os_1 = require("os");
|
|
58
|
+
// ─── Persistent status file ──────────────────────────────────────────────────
|
|
59
|
+
// Written by the daemon after each poll; read by the CLI status command.
|
|
60
|
+
const STATUS_FILE = (0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data', 'status.json');
|
|
61
|
+
function persistState(extra = {}) {
|
|
62
|
+
try {
|
|
63
|
+
const dir = (0, path_1.join)((0, os_1.homedir)(), '.openclaw', 'shield', 'data');
|
|
64
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
65
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
66
|
+
(0, fs_1.writeFileSync)(STATUS_FILE, JSON.stringify({
|
|
67
|
+
...state, ...extra,
|
|
68
|
+
version: version_1.VERSION,
|
|
69
|
+
updatedAt: Date.now(),
|
|
70
|
+
pid: process.pid,
|
|
71
|
+
}, null, 2));
|
|
72
|
+
}
|
|
73
|
+
catch { /* non-fatal */ }
|
|
74
|
+
}
|
|
75
|
+
function readPersistedState() {
|
|
76
|
+
try {
|
|
77
|
+
if (!(0, fs_1.existsSync)(STATUS_FILE))
|
|
78
|
+
return null;
|
|
79
|
+
const d = JSON.parse((0, fs_1.readFileSync)(STATUS_FILE, 'utf8'));
|
|
80
|
+
const age = Date.now() - (d.updatedAt || 0);
|
|
81
|
+
if (age > 10 * 60 * 1000)
|
|
82
|
+
return null; // stale if >10min
|
|
83
|
+
return d;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Bridge state — shared between service lifecycle, RPC, and CLI
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
const state = {
|
|
93
|
+
running: false,
|
|
94
|
+
lastPollAt: 0,
|
|
95
|
+
eventsProcessed: 0,
|
|
96
|
+
quarantineCount: 0,
|
|
97
|
+
consecutiveFailures: 0,
|
|
98
|
+
};
|
|
99
|
+
const MAX_BACKOFF_MS = 5 * 60 * 1000;
|
|
100
|
+
const TELEMETRY_INTERVAL_MS = 5 * 60 * 1000;
|
|
101
|
+
function getBackoffInterval(baseMs) {
|
|
102
|
+
if (state.consecutiveFailures === 0)
|
|
103
|
+
return baseMs;
|
|
104
|
+
const backoff = baseMs * Math.pow(2, Math.min(state.consecutiveFailures, 10));
|
|
105
|
+
return Math.min(backoff, MAX_BACKOFF_MS);
|
|
106
|
+
}
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Plugin export
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
exports.default = {
|
|
111
|
+
id: 'shield',
|
|
112
|
+
name: 'OpenClaw Shield',
|
|
113
|
+
register(api) {
|
|
114
|
+
// Wire up the Gateway logger as the log backend
|
|
115
|
+
const gatewayAdapter = {
|
|
116
|
+
debug(tag, msg, data) {
|
|
117
|
+
api.logger.debug(`[${tag}] ${msg}${data !== undefined ? ' ' + JSON.stringify(data) : ''}`);
|
|
118
|
+
},
|
|
119
|
+
info(tag, msg) { api.logger.info(`[${tag}] ${msg}`); },
|
|
120
|
+
warn(tag, msg) { api.logger.warn(`[${tag}] ${msg}`); },
|
|
121
|
+
error(tag, msg, err) {
|
|
122
|
+
api.logger.error(`[${tag}] ${msg}${err ? ' ' + String(err) : ''}`);
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
(0, log_1.setAdapter)(gatewayAdapter);
|
|
126
|
+
const pluginConfig = (api.config ?? {});
|
|
127
|
+
const enabled = pluginConfig.enabled !== false;
|
|
128
|
+
if (!enabled) {
|
|
129
|
+
log.info('shield', 'Monitoring disabled via config (enabled: false)');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Build credentials from plugin config + config.env fallback (no process.env mutation)
|
|
133
|
+
const credentials = (0, config_1.loadCredentialsFromPluginConfig)(pluginConfig);
|
|
134
|
+
if (!credentials.apiUrl || !credentials.hmacSecret) {
|
|
135
|
+
log.warn('shield', 'Not configured. Credentials can come from:');
|
|
136
|
+
log.warn('shield', ' 1. Run setup wizard: npx shield-setup');
|
|
137
|
+
log.warn('shield', ' 2. Set in plugins.entries.shield.config (openclaw.json)');
|
|
138
|
+
log.warn('shield', ' 3. Set env vars: SHIELD_API_URL + SHIELD_HMAC_SECRET');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const config = (0, config_1.loadConfig)({
|
|
142
|
+
credentials,
|
|
143
|
+
dryRun: typeof pluginConfig.dryRun === 'boolean' ? pluginConfig.dryRun : undefined,
|
|
144
|
+
redactionEnabled: typeof pluginConfig.redactionEnabled === 'boolean' ? pluginConfig.redactionEnabled : undefined,
|
|
145
|
+
pollIntervalMs: typeof pluginConfig.pollIntervalMs === 'number' ? pluginConfig.pollIntervalMs : undefined,
|
|
146
|
+
collectHostMetrics: typeof pluginConfig.collectHostMetrics === 'boolean' ? pluginConfig.collectHostMetrics : undefined,
|
|
147
|
+
});
|
|
148
|
+
log.info('shield', `Starting monitoring bridge v${version_1.VERSION} (poll: ${config.pollIntervalMs}ms, dryRun: ${config.dryRun})`);
|
|
149
|
+
// -----------------------------------------------------------------------
|
|
150
|
+
// RPC methods
|
|
151
|
+
// -----------------------------------------------------------------------
|
|
152
|
+
let pollFn = null;
|
|
153
|
+
api.registerGatewayMethod('shield.status', ({ respond }) => {
|
|
154
|
+
respond(true, {
|
|
155
|
+
running: state.running,
|
|
156
|
+
lastPollAt: state.lastPollAt,
|
|
157
|
+
eventsProcessed: state.eventsProcessed,
|
|
158
|
+
quarantineCount: state.quarantineCount,
|
|
159
|
+
consecutiveFailures: state.consecutiveFailures,
|
|
160
|
+
version: version_1.VERSION,
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
api.registerGatewayMethod('shield.flush', ({ respond }) => {
|
|
164
|
+
if (!pollFn) {
|
|
165
|
+
respond(false, { error: 'Bridge not started' });
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
pollFn()
|
|
169
|
+
.then(() => respond(true, { flushed: true }))
|
|
170
|
+
.catch((err) => respond(false, { error: err instanceof Error ? err.message : String(err) }));
|
|
171
|
+
});
|
|
172
|
+
// -----------------------------------------------------------------------
|
|
173
|
+
// CLI commands
|
|
174
|
+
// -----------------------------------------------------------------------
|
|
175
|
+
api.registerCli(({ program }) => {
|
|
176
|
+
const shield = program.command('shield');
|
|
177
|
+
shield.command('status')
|
|
178
|
+
.description('Show Shield monitoring status')
|
|
179
|
+
.action(async () => {
|
|
180
|
+
// Prefer persisted state written by the daemon; fall back to local state
|
|
181
|
+
const s = readPersistedState() ?? state;
|
|
182
|
+
const lastPoll = s.lastPollAt ? new Date(s.lastPollAt).toISOString() : 'never';
|
|
183
|
+
const updatedAt = s.updatedAt ? ` (${Math.round((Date.now() - s.updatedAt) / 1000)}s ago)` : '';
|
|
184
|
+
console.log(`Shield v${s.version ?? version_1.VERSION}${updatedAt}`);
|
|
185
|
+
console.log(` Running: ${s.running}`);
|
|
186
|
+
console.log(` Last poll: ${lastPoll}`);
|
|
187
|
+
console.log(` Events: ${s.eventsProcessed}`);
|
|
188
|
+
console.log(` Quarantine: ${s.quarantineCount}`);
|
|
189
|
+
console.log(` Failures: ${s.consecutiveFailures}`);
|
|
190
|
+
if (s.pid)
|
|
191
|
+
console.log(` Daemon PID: ${s.pid}`);
|
|
192
|
+
});
|
|
193
|
+
shield.command('flush')
|
|
194
|
+
.description('Trigger an immediate poll cycle')
|
|
195
|
+
.action(async () => {
|
|
196
|
+
if (!pollFn) {
|
|
197
|
+
console.error('Bridge not started');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
console.log('Flushing...');
|
|
201
|
+
await pollFn();
|
|
202
|
+
console.log('Done');
|
|
203
|
+
});
|
|
204
|
+
}, { commands: ['shield'] });
|
|
205
|
+
// -----------------------------------------------------------------------
|
|
206
|
+
// Register background service
|
|
207
|
+
// -----------------------------------------------------------------------
|
|
208
|
+
let pollHandle = null;
|
|
209
|
+
let telemetryHandle = null;
|
|
210
|
+
api.registerService({
|
|
211
|
+
id: 'shield-monitor',
|
|
212
|
+
async start() {
|
|
213
|
+
const { fetchNewEntries, commitCursors } = await Promise.resolve().then(() => __importStar(require('./src/fetcher')));
|
|
214
|
+
const { transformEntries, generateHostTelemetry, resolveOpenClawVersion, resolveAgentLabel } = await Promise.resolve().then(() => __importStar(require('./src/transformer')));
|
|
215
|
+
const { sendEvents, reportInstance } = await Promise.resolve().then(() => __importStar(require('./src/sender')));
|
|
216
|
+
const { init: initRedactor, flush: flushRedactor, redactEvent } = await Promise.resolve().then(() => __importStar(require('./src/redactor')));
|
|
217
|
+
const { validate } = await Promise.resolve().then(() => __importStar(require('./src/validator')));
|
|
218
|
+
if (config.redactionEnabled)
|
|
219
|
+
initRedactor();
|
|
220
|
+
state.running = true;
|
|
221
|
+
persistState(); // mark daemon as running immediately
|
|
222
|
+
// -- Telemetry --------------------------------------------------
|
|
223
|
+
const runTelemetry = async () => {
|
|
224
|
+
if (!state.running)
|
|
225
|
+
return;
|
|
226
|
+
if (config.collectHostMetrics) {
|
|
227
|
+
const hostEvent = generateHostTelemetry();
|
|
228
|
+
if (hostEvent) {
|
|
229
|
+
const batch = config.redactionEnabled
|
|
230
|
+
? [redactEvent(hostEvent)]
|
|
231
|
+
: [hostEvent];
|
|
232
|
+
const results = await sendEvents(batch, config);
|
|
233
|
+
const ok = results.every(r => r.success);
|
|
234
|
+
log.info('shield', `Host telemetry → Chronicle: success=${ok}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const instancePayload = {
|
|
238
|
+
machine: {
|
|
239
|
+
hostname: config.hostname,
|
|
240
|
+
os: process.platform,
|
|
241
|
+
arch: process.arch,
|
|
242
|
+
node_version: process.version,
|
|
243
|
+
},
|
|
244
|
+
software: {
|
|
245
|
+
plugin_version: version_1.VERSION,
|
|
246
|
+
openclaw_version: resolveOpenClawVersion(),
|
|
247
|
+
agent_label: resolveAgentLabel('main'),
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const ok = await reportInstance(instancePayload, config.credentials);
|
|
251
|
+
log.info('shield', `Instance report → Platform: success=${ok}`);
|
|
252
|
+
};
|
|
253
|
+
runTelemetry().catch((err) => log.error('shield', `Telemetry error: ${err instanceof Error ? err.message : String(err)}`));
|
|
254
|
+
telemetryHandle = setInterval(() => {
|
|
255
|
+
runTelemetry().catch((err) => log.error('shield', `Telemetry error: ${err instanceof Error ? err.message : String(err)}`));
|
|
256
|
+
}, TELEMETRY_INTERVAL_MS);
|
|
257
|
+
// -- Poll -------------------------------------------------------
|
|
258
|
+
const poll = async () => {
|
|
259
|
+
if (!state.running)
|
|
260
|
+
return;
|
|
261
|
+
try {
|
|
262
|
+
const entries = await fetchNewEntries(config);
|
|
263
|
+
if (entries.length === 0) {
|
|
264
|
+
state.consecutiveFailures = 0;
|
|
265
|
+
state.lastPollAt = Date.now();
|
|
266
|
+
persistState();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
let envelopes = transformEntries(entries);
|
|
270
|
+
const { valid: validEvents, quarantined } = validate(envelopes.map(e => e.event));
|
|
271
|
+
if (quarantined > 0) {
|
|
272
|
+
state.quarantineCount += quarantined;
|
|
273
|
+
log.warn('shield', `${quarantined} events quarantined (see ~/.openclaw/shield/data/quarantine.jsonl)`);
|
|
274
|
+
}
|
|
275
|
+
envelopes = envelopes.filter(e => validEvents.includes(e.event));
|
|
276
|
+
if (config.redactionEnabled) {
|
|
277
|
+
envelopes = envelopes.map(e => redactEvent(e));
|
|
278
|
+
}
|
|
279
|
+
const results = await sendEvents(envelopes, config);
|
|
280
|
+
const accepted = results.reduce((sum, r) => sum + (r.success ? r.eventCount : 0), 0);
|
|
281
|
+
if (accepted > 0) {
|
|
282
|
+
commitCursors(config, entries);
|
|
283
|
+
flushRedactor();
|
|
284
|
+
state.eventsProcessed += accepted;
|
|
285
|
+
state.consecutiveFailures = 0;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
state.consecutiveFailures++;
|
|
289
|
+
}
|
|
290
|
+
state.lastPollAt = Date.now();
|
|
291
|
+
persistState();
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
state.consecutiveFailures++;
|
|
295
|
+
log.error('shield', `Poll error: ${err instanceof Error ? err.message : String(err)}`);
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
pollFn = poll;
|
|
299
|
+
await poll();
|
|
300
|
+
const schedulePoll = () => {
|
|
301
|
+
if (!state.running)
|
|
302
|
+
return;
|
|
303
|
+
const interval = getBackoffInterval(config.pollIntervalMs);
|
|
304
|
+
if (interval !== config.pollIntervalMs) {
|
|
305
|
+
log.warn('shield', `Backing off: next poll in ${Math.round(interval / 1000)}s (${state.consecutiveFailures} consecutive failures)`);
|
|
306
|
+
}
|
|
307
|
+
pollHandle = setTimeout(() => {
|
|
308
|
+
poll().catch((err) => {
|
|
309
|
+
state.consecutiveFailures++;
|
|
310
|
+
log.error('shield', `Poll error (unhandled): ${err instanceof Error ? err.message : String(err)}`);
|
|
311
|
+
}).finally(() => {
|
|
312
|
+
schedulePoll();
|
|
313
|
+
});
|
|
314
|
+
}, interval);
|
|
315
|
+
};
|
|
316
|
+
schedulePoll();
|
|
317
|
+
// ── Graceful shutdown on process signals ──────────────────────────
|
|
318
|
+
// Handles SIGTERM (gateway graceful stop) and SIGINT (Ctrl-C / dev).
|
|
319
|
+
// Uses once() so the handler self-removes after first signal.
|
|
320
|
+
const onSignal = async () => {
|
|
321
|
+
if (!state.running)
|
|
322
|
+
return; // already stopped
|
|
323
|
+
state.running = false;
|
|
324
|
+
persistState();
|
|
325
|
+
if (pollHandle) {
|
|
326
|
+
clearTimeout(pollHandle);
|
|
327
|
+
pollHandle = null;
|
|
328
|
+
}
|
|
329
|
+
if (telemetryHandle) {
|
|
330
|
+
clearInterval(telemetryHandle);
|
|
331
|
+
telemetryHandle = null;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const { flush: fr } = await Promise.resolve().then(() => __importStar(require('./src/redactor')));
|
|
335
|
+
fr();
|
|
336
|
+
}
|
|
337
|
+
catch { }
|
|
338
|
+
log.info('shield', 'Service stopped (signal)');
|
|
339
|
+
};
|
|
340
|
+
process.once('SIGTERM', onSignal);
|
|
341
|
+
process.once('SIGINT', onSignal);
|
|
342
|
+
},
|
|
343
|
+
async stop() {
|
|
344
|
+
if (!state.running)
|
|
345
|
+
return; // already stopped by signal handler
|
|
346
|
+
state.running = false;
|
|
347
|
+
persistState();
|
|
348
|
+
if (pollHandle) {
|
|
349
|
+
clearTimeout(pollHandle);
|
|
350
|
+
pollHandle = null;
|
|
351
|
+
}
|
|
352
|
+
if (telemetryHandle) {
|
|
353
|
+
clearInterval(telemetryHandle);
|
|
354
|
+
telemetryHandle = null;
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const { flush: flushRedactor } = await Promise.resolve().then(() => __importStar(require('./src/redactor')));
|
|
358
|
+
flushRedactor();
|
|
359
|
+
}
|
|
360
|
+
catch { /* ignore if module not loaded */ }
|
|
361
|
+
log.info('shield', 'Service stopped');
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
},
|
|
365
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface ShieldCredentials {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
hmacSecret: string;
|
|
4
|
+
instanceId: string;
|
|
5
|
+
shieldEnv: string;
|
|
6
|
+
}
|
|
7
|
+
export interface Config {
|
|
8
|
+
dryRun: boolean;
|
|
9
|
+
pollIntervalMs: number;
|
|
10
|
+
sessionDirs: string[];
|
|
11
|
+
cursorFile: string;
|
|
12
|
+
hostname: string;
|
|
13
|
+
maxEvents: number;
|
|
14
|
+
collectHostMetrics: boolean;
|
|
15
|
+
redactionEnabled: boolean;
|
|
16
|
+
credentials: ShieldCredentials;
|
|
17
|
+
}
|
|
18
|
+
/** Canonical config location — shared by setup, bridge, and plugin entry. */
|
|
19
|
+
export declare const SHIELD_CONFIG_PATH: string;
|
|
20
|
+
/**
|
|
21
|
+
* Inject config file values into process.env so the standalone bridge
|
|
22
|
+
* (sender, fetcher, etc.) can read credentials via env vars.
|
|
23
|
+
* Values already set in the environment take precedence (allows override).
|
|
24
|
+
*/
|
|
25
|
+
export declare function injectConfigEnv(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Load credentials from config.env file and environment variables.
|
|
28
|
+
* Supports new env var names with backward compat for the old names.
|
|
29
|
+
*/
|
|
30
|
+
export declare function loadCredentials(): ShieldCredentials;
|
|
31
|
+
/**
|
|
32
|
+
* Build credentials from a plugin config object (openclaw.json).
|
|
33
|
+
* Falls back to config.env / env vars for any missing values.
|
|
34
|
+
*/
|
|
35
|
+
export declare function loadCredentialsFromPluginConfig(pluginConfig: Record<string, unknown>): ShieldCredentials;
|
|
36
|
+
export interface ConfigOverrides {
|
|
37
|
+
credentials?: ShieldCredentials;
|
|
38
|
+
dryRun?: boolean;
|
|
39
|
+
redactionEnabled?: boolean;
|
|
40
|
+
pollIntervalMs?: number;
|
|
41
|
+
collectHostMetrics?: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function loadConfig(overrides?: ConfigOverrides): Config;
|