@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.

Files changed (159) hide show
  1. package/LICENSE +38 -0
  2. package/README.md +96 -0
  3. package/dist/index.d.ts +43 -0
  4. package/dist/index.js +365 -0
  5. package/dist/src/config.d.ts +43 -0
  6. package/dist/src/config.js +181 -0
  7. package/dist/src/events/base.d.ts +110 -0
  8. package/dist/src/events/base.js +61 -0
  9. package/dist/src/events/browser/enrich.d.ts +3 -0
  10. package/dist/src/events/browser/enrich.js +46 -0
  11. package/dist/src/events/browser/event.d.ts +10 -0
  12. package/dist/src/events/browser/event.js +2 -0
  13. package/dist/src/events/browser/index.d.ts +4 -0
  14. package/dist/src/events/browser/index.js +13 -0
  15. package/dist/src/events/browser/redactions.d.ts +2 -0
  16. package/dist/src/events/browser/redactions.js +4 -0
  17. package/dist/src/events/browser/validations.d.ts +3 -0
  18. package/dist/src/events/browser/validations.js +10 -0
  19. package/dist/src/events/cron/enrich.d.ts +3 -0
  20. package/dist/src/events/cron/enrich.js +44 -0
  21. package/dist/src/events/cron/event.d.ts +5 -0
  22. package/dist/src/events/cron/event.js +2 -0
  23. package/dist/src/events/cron/index.d.ts +4 -0
  24. package/dist/src/events/cron/index.js +13 -0
  25. package/dist/src/events/cron/redactions.d.ts +2 -0
  26. package/dist/src/events/cron/redactions.js +4 -0
  27. package/dist/src/events/cron/validations.d.ts +3 -0
  28. package/dist/src/events/cron/validations.js +4 -0
  29. package/dist/src/events/exec/enrich.d.ts +3 -0
  30. package/dist/src/events/exec/enrich.js +80 -0
  31. package/dist/src/events/exec/event.d.ts +11 -0
  32. package/dist/src/events/exec/event.js +2 -0
  33. package/dist/src/events/exec/index.d.ts +4 -0
  34. package/dist/src/events/exec/index.js +13 -0
  35. package/dist/src/events/exec/redactions.d.ts +3 -0
  36. package/dist/src/events/exec/redactions.js +12 -0
  37. package/dist/src/events/exec/validations.d.ts +3 -0
  38. package/dist/src/events/exec/validations.js +12 -0
  39. package/dist/src/events/file/enrich.d.ts +3 -0
  40. package/dist/src/events/file/enrich.js +63 -0
  41. package/dist/src/events/file/event.d.ts +11 -0
  42. package/dist/src/events/file/event.js +2 -0
  43. package/dist/src/events/file/index.d.ts +4 -0
  44. package/dist/src/events/file/index.js +13 -0
  45. package/dist/src/events/file/redactions.d.ts +2 -0
  46. package/dist/src/events/file/redactions.js +8 -0
  47. package/dist/src/events/file/validations.d.ts +3 -0
  48. package/dist/src/events/file/validations.js +10 -0
  49. package/dist/src/events/gateway/enrich.d.ts +3 -0
  50. package/dist/src/events/gateway/enrich.js +50 -0
  51. package/dist/src/events/gateway/event.d.ts +5 -0
  52. package/dist/src/events/gateway/event.js +2 -0
  53. package/dist/src/events/gateway/index.d.ts +4 -0
  54. package/dist/src/events/gateway/index.js +13 -0
  55. package/dist/src/events/gateway/redactions.d.ts +2 -0
  56. package/dist/src/events/gateway/redactions.js +4 -0
  57. package/dist/src/events/gateway/validations.d.ts +3 -0
  58. package/dist/src/events/gateway/validations.js +4 -0
  59. package/dist/src/events/generic/enrich.d.ts +3 -0
  60. package/dist/src/events/generic/enrich.js +30 -0
  61. package/dist/src/events/generic/event.d.ts +5 -0
  62. package/dist/src/events/generic/event.js +2 -0
  63. package/dist/src/events/generic/index.d.ts +5 -0
  64. package/dist/src/events/generic/index.js +14 -0
  65. package/dist/src/events/generic/redactions.d.ts +2 -0
  66. package/dist/src/events/generic/redactions.js +4 -0
  67. package/dist/src/events/generic/validations.d.ts +3 -0
  68. package/dist/src/events/generic/validations.js +4 -0
  69. package/dist/src/events/host-telemetry/enrich.d.ts +3 -0
  70. package/dist/src/events/host-telemetry/enrich.js +28 -0
  71. package/dist/src/events/host-telemetry/event.d.ts +4 -0
  72. package/dist/src/events/host-telemetry/event.js +2 -0
  73. package/dist/src/events/host-telemetry/index.d.ts +4 -0
  74. package/dist/src/events/host-telemetry/index.js +13 -0
  75. package/dist/src/events/host-telemetry/redactions.d.ts +2 -0
  76. package/dist/src/events/host-telemetry/redactions.js +4 -0
  77. package/dist/src/events/host-telemetry/validations.d.ts +3 -0
  78. package/dist/src/events/host-telemetry/validations.js +4 -0
  79. package/dist/src/events/index.d.ts +40 -0
  80. package/dist/src/events/index.js +39 -0
  81. package/dist/src/events/message/enrich.d.ts +3 -0
  82. package/dist/src/events/message/enrich.js +36 -0
  83. package/dist/src/events/message/event.d.ts +5 -0
  84. package/dist/src/events/message/event.js +2 -0
  85. package/dist/src/events/message/index.d.ts +4 -0
  86. package/dist/src/events/message/index.js +13 -0
  87. package/dist/src/events/message/redactions.d.ts +2 -0
  88. package/dist/src/events/message/redactions.js +4 -0
  89. package/dist/src/events/message/validations.d.ts +3 -0
  90. package/dist/src/events/message/validations.js +7 -0
  91. package/dist/src/events/sessions-spawn/enrich.d.ts +3 -0
  92. package/dist/src/events/sessions-spawn/enrich.js +40 -0
  93. package/dist/src/events/sessions-spawn/event.d.ts +9 -0
  94. package/dist/src/events/sessions-spawn/event.js +2 -0
  95. package/dist/src/events/sessions-spawn/index.d.ts +4 -0
  96. package/dist/src/events/sessions-spawn/index.js +13 -0
  97. package/dist/src/events/sessions-spawn/redactions.d.ts +2 -0
  98. package/dist/src/events/sessions-spawn/redactions.js +4 -0
  99. package/dist/src/events/sessions-spawn/validations.d.ts +3 -0
  100. package/dist/src/events/sessions-spawn/validations.js +4 -0
  101. package/dist/src/events/tool-result/enrich.d.ts +13 -0
  102. package/dist/src/events/tool-result/enrich.js +46 -0
  103. package/dist/src/events/tool-result/event.d.ts +7 -0
  104. package/dist/src/events/tool-result/event.js +2 -0
  105. package/dist/src/events/tool-result/index.d.ts +4 -0
  106. package/dist/src/events/tool-result/index.js +9 -0
  107. package/dist/src/events/tool-result/redactions.d.ts +2 -0
  108. package/dist/src/events/tool-result/redactions.js +7 -0
  109. package/dist/src/events/tool-result/validations.d.ts +3 -0
  110. package/dist/src/events/tool-result/validations.js +9 -0
  111. package/dist/src/events/web/enrich.d.ts +8 -0
  112. package/dist/src/events/web/enrich.js +78 -0
  113. package/dist/src/events/web/event.d.ts +10 -0
  114. package/dist/src/events/web/event.js +2 -0
  115. package/dist/src/events/web/index.d.ts +4 -0
  116. package/dist/src/events/web/index.js +13 -0
  117. package/dist/src/events/web/redactions.d.ts +2 -0
  118. package/dist/src/events/web/redactions.js +6 -0
  119. package/dist/src/events/web/validations.d.ts +3 -0
  120. package/dist/src/events/web/validations.js +10 -0
  121. package/dist/src/fetcher.d.ts +12 -0
  122. package/dist/src/fetcher.js +182 -0
  123. package/dist/src/host-collector.d.ts +1 -0
  124. package/dist/src/host-collector.js +200 -0
  125. package/dist/src/index.d.ts +1 -0
  126. package/dist/src/index.js +210 -0
  127. package/dist/src/log.d.ts +39 -0
  128. package/dist/src/log.js +102 -0
  129. package/dist/src/redactor/base.d.ts +29 -0
  130. package/dist/src/redactor/base.js +9 -0
  131. package/dist/src/redactor/index.d.ts +27 -0
  132. package/dist/src/redactor/index.js +109 -0
  133. package/dist/src/redactor/strategies/command.d.ts +2 -0
  134. package/dist/src/redactor/strategies/command.js +19 -0
  135. package/dist/src/redactor/strategies/hostname.d.ts +2 -0
  136. package/dist/src/redactor/strategies/hostname.js +15 -0
  137. package/dist/src/redactor/strategies/index.d.ts +13 -0
  138. package/dist/src/redactor/strategies/index.js +25 -0
  139. package/dist/src/redactor/strategies/path.d.ts +2 -0
  140. package/dist/src/redactor/strategies/path.js +23 -0
  141. package/dist/src/redactor/strategies/secret-key.d.ts +2 -0
  142. package/dist/src/redactor/strategies/secret-key.js +22 -0
  143. package/dist/src/redactor/strategies/username.d.ts +2 -0
  144. package/dist/src/redactor/strategies/username.js +12 -0
  145. package/dist/src/redactor/vault.d.ts +25 -0
  146. package/dist/src/redactor/vault.js +209 -0
  147. package/dist/src/sender.d.ts +29 -0
  148. package/dist/src/sender.js +186 -0
  149. package/dist/src/setup.d.ts +10 -0
  150. package/dist/src/setup.js +222 -0
  151. package/dist/src/transformer.d.ts +26 -0
  152. package/dist/src/transformer.js +302 -0
  153. package/dist/src/validator.d.ts +17 -0
  154. package/dist/src/validator.js +110 -0
  155. package/dist/src/version.d.ts +1 -0
  156. package/dist/src/version.js +19 -0
  157. package/openclaw.plugin.json +52 -0
  158. package/package.json +64 -0
  159. 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.
@@ -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;