@upx-us/shield 0.4.36 → 0.6.0

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.
@@ -1,8 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectSecretInCommand = detectSecretInCommand;
3
4
  exports.splitChainedCommands = splitChainedCommands;
4
5
  exports.enrich = enrich;
5
6
  const base_1 = require("../base");
7
+ const SECRET_KEY_PREFIXES = ['sk-', 'pk_', 'key_', 'ghp_', 'ghs_', 'glpat-', 'xoxb-', 'xoxp-'];
8
+ const SECRET_ENV_PATTERN = /\b[A-Z_]*(SECRET|TOKEN|KEY|PASSWORD|PASSWD|API_KEY|APIKEY)[A-Z_]*=/i;
9
+ const LONG_HEX_BASE64 = /(?:[0-9a-fA-F]{41,}|[A-Za-z0-9+/=]{41,})/;
10
+ const BEARER_PATTERN = /\bBearer\s+\S+/i;
11
+ function detectSecretInCommand(cmd) {
12
+ if (!cmd)
13
+ return false;
14
+ for (const prefix of SECRET_KEY_PREFIXES) {
15
+ if (cmd.includes(prefix))
16
+ return true;
17
+ }
18
+ if (BEARER_PATTERN.test(cmd))
19
+ return true;
20
+ if (SECRET_ENV_PATTERN.test(cmd))
21
+ return true;
22
+ if (LONG_HEX_BASE64.test(cmd))
23
+ return true;
24
+ return false;
25
+ }
6
26
  function splitChainedCommands(cmd) {
7
27
  const segments = [];
8
28
  let current = '';
@@ -74,6 +94,9 @@ function enrich(tool, ctx) {
74
94
  cmd_has_sudo: /\bsudo\b/.test(cmd),
75
95
  cmd_has_pipe: /\|/.test(cmd),
76
96
  };
97
+ if (detectSecretInCommand(cmd)) {
98
+ meta['openclaw.secret_in_command'] = 'true';
99
+ }
77
100
  for (const segRoot of allRootCommands) {
78
101
  if (/^(kill|pkill|killall)$/.test(segRoot)) {
79
102
  const pidMatch = cmd.match(/\bkill\s+(?:-\d+\s+)?(\d+)/);
@@ -19,6 +19,10 @@ function enrich(tool, ctx) {
19
19
  file_is_system: isSystem,
20
20
  file_is_config: ext ? configExts.includes(ext) : false,
21
21
  };
22
+ const CONFIG_FILE_PATTERNS = /(?:\.env|config\.json|openclaw\.json|\.npmrc|\.yarnrc|\.gitconfig|settings\.json|docker-compose\.ya?ml|Dockerfile|\.bashrc|\.zshrc|\.profile)(?:$|\/)/;
23
+ if ((toolName === 'write' || toolName === 'edit') && (meta.file_is_config || CONFIG_FILE_PATTERNS.test(fp) || /\/\.env(?:\.|$)/.test(fp))) {
24
+ meta['openclaw.config_change'] = 'true';
25
+ }
22
26
  if ((toolName === 'write' || toolName === 'edit') && isMemoryFile) {
23
27
  meta.memory_auto_capture = 'true';
24
28
  }
@@ -29,6 +33,9 @@ function enrich(tool, ctx) {
29
33
  meta.edit_new_length = String(newText.length);
30
34
  meta.edit_size_delta = String(newText.length - oldText.length);
31
35
  }
36
+ if (toolName === 'write' && args.content) {
37
+ meta['openclaw.file_size_bytes'] = String(Buffer.byteLength(args.content, 'utf8'));
38
+ }
32
39
  const event = {
33
40
  timestamp: ctx.timestamp,
34
41
  event_type: 'TOOL_CALL',
@@ -10,6 +10,32 @@ function enrich(tool, ctx) {
10
10
  'openclaw.agent_id': ctx.agentId,
11
11
  sub_action: args.action || null,
12
12
  };
13
+ if (args.channel)
14
+ meta['openclaw.channel'] = args.channel;
15
+ if (args.target) {
16
+ if (!args.channel) {
17
+ if (/^telegram/i.test(String(args.target)) || /^-?\d{10,}/.test(String(args.target))) {
18
+ meta['openclaw.channel'] = 'telegram';
19
+ }
20
+ }
21
+ const targetStr = String(args.target);
22
+ if (targetStr.startsWith('-100'))
23
+ meta['openclaw.chat_type'] = 'group';
24
+ else if (targetStr.startsWith('-'))
25
+ meta['openclaw.chat_type'] = 'group';
26
+ else if (/^\d+$/.test(targetStr))
27
+ meta['openclaw.chat_type'] = 'private';
28
+ }
29
+ if (args.groupId || args.guildId)
30
+ meta['openclaw.chat_type'] = 'group';
31
+ if (args.channelId && !meta['openclaw.chat_type'])
32
+ meta['openclaw.chat_type'] = 'channel';
33
+ if (args.action === 'send' || args.action === 'edit') {
34
+ const content = args.message || args.caption || '';
35
+ if (content) {
36
+ meta['openclaw.message_content'] = content.length > 500 ? content.slice(0, 500) : content;
37
+ }
38
+ }
13
39
  if (args.buffer) {
14
40
  meta.has_base64_payload = 'true';
15
41
  const raw = typeof args.buffer === 'string' ? args.buffer.replace(/^data:[^;]+;base64,/, '') : '';
@@ -0,0 +1,16 @@
1
+ export interface Exclusion {
2
+ id: string;
3
+ rule_id: string;
4
+ pattern_hash: string;
5
+ agent_id?: string;
6
+ created_at: string;
7
+ reason?: string;
8
+ }
9
+ export declare function initExclusions(dataDir: string): void;
10
+ export declare function normalizePattern(input: string): string;
11
+ export declare function computePatternHash(ruleId: string, command: string): string;
12
+ export declare function addExclusion(ruleId: string, patternHash: string, agentId?: string, reason?: string): Exclusion;
13
+ export declare function isExcluded(ruleId: string, patternHash: string, agentId?: string): boolean;
14
+ export declare function listExclusions(): Exclusion[];
15
+ export declare function removeExclusion(id: string): boolean;
16
+ export declare function _resetForTesting(): void;
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.initExclusions = initExclusions;
37
+ exports.normalizePattern = normalizePattern;
38
+ exports.computePatternHash = computePatternHash;
39
+ exports.addExclusion = addExclusion;
40
+ exports.isExcluded = isExcluded;
41
+ exports.listExclusions = listExclusions;
42
+ exports.removeExclusion = removeExclusion;
43
+ exports._resetForTesting = _resetForTesting;
44
+ const crypto_1 = require("crypto");
45
+ const safe_io_1 = require("./safe-io");
46
+ const path_1 = require("path");
47
+ const log = __importStar(require("./log"));
48
+ const MAX_EXCLUSIONS = 1000;
49
+ const STORE_FILE = 'exclusions.json';
50
+ let _dataDir = null;
51
+ function initExclusions(dataDir) {
52
+ _dataDir = dataDir;
53
+ log.info('exclusions', 'Initialized');
54
+ }
55
+ function storePath() {
56
+ if (!_dataDir)
57
+ throw new Error('Exclusions not initialized');
58
+ return (0, path_1.join)(_dataDir, STORE_FILE);
59
+ }
60
+ function loadStore() {
61
+ return (0, safe_io_1.readJsonSafe)(storePath(), { exclusions: [] }, 'exclusions');
62
+ }
63
+ function saveStore(store) {
64
+ (0, safe_io_1.writeJsonSafe)(storePath(), store);
65
+ }
66
+ const IP_RE = /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g;
67
+ const UUID_RE = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
68
+ const HEX_HASH_RE = /\b[0-9a-f]{32,}\b/gi;
69
+ const ISO_TS_RE = /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^\s]*/g;
70
+ const BIG_NUM_RE = /\b\d{5,}\b/g;
71
+ function normalizePattern(input) {
72
+ return input
73
+ .replace(ISO_TS_RE, '<TIMESTAMP>')
74
+ .replace(UUID_RE, '<UUID>')
75
+ .replace(HEX_HASH_RE, '<HASH>')
76
+ .replace(IP_RE, '<IP>')
77
+ .replace(BIG_NUM_RE, '<NUM>');
78
+ }
79
+ function computePatternHash(ruleId, command) {
80
+ const normalized = normalizePattern(command);
81
+ return (0, crypto_1.createHash)('sha256').update(`${ruleId}:${normalized}`).digest('hex');
82
+ }
83
+ function addExclusion(ruleId, patternHash, agentId, reason) {
84
+ const store = loadStore();
85
+ const exclusion = {
86
+ id: (0, crypto_1.randomUUID)(),
87
+ rule_id: ruleId,
88
+ pattern_hash: patternHash,
89
+ created_at: new Date().toISOString(),
90
+ ...(agentId ? { agent_id: agentId } : {}),
91
+ ...(reason ? { reason } : {}),
92
+ };
93
+ store.exclusions.push(exclusion);
94
+ if (store.exclusions.length > MAX_EXCLUSIONS) {
95
+ store.exclusions = store.exclusions.slice(store.exclusions.length - MAX_EXCLUSIONS);
96
+ }
97
+ saveStore(store);
98
+ log.info('exclusions', `Added exclusion ${exclusion.id} for rule ${ruleId}`);
99
+ return exclusion;
100
+ }
101
+ function isExcluded(ruleId, patternHash, agentId) {
102
+ const store = loadStore();
103
+ return store.exclusions.some(e => e.rule_id === ruleId &&
104
+ e.pattern_hash === patternHash &&
105
+ (!e.agent_id || e.agent_id === agentId));
106
+ }
107
+ function listExclusions() {
108
+ return loadStore().exclusions;
109
+ }
110
+ function removeExclusion(id) {
111
+ const store = loadStore();
112
+ const before = store.exclusions.length;
113
+ store.exclusions = store.exclusions.filter(e => e.id !== id);
114
+ if (store.exclusions.length === before)
115
+ return false;
116
+ saveStore(store);
117
+ log.info('exclusions', `Removed exclusion ${id}`);
118
+ return true;
119
+ }
120
+ function _resetForTesting() {
121
+ _dataDir = null;
122
+ }
@@ -0,0 +1,8 @@
1
+ interface RpcCtx {
2
+ respond: (ok: boolean, data: unknown) => void;
3
+ params?: Record<string, unknown>;
4
+ }
5
+ export declare function createExclusionsListHandler(): (ctx: RpcCtx) => void;
6
+ export declare function createExclusionAddHandler(): (ctx: RpcCtx) => void;
7
+ export declare function createExclusionRemoveHandler(): (ctx: RpcCtx) => void;
8
+ export {};
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createExclusionsListHandler = createExclusionsListHandler;
4
+ exports.createExclusionAddHandler = createExclusionAddHandler;
5
+ exports.createExclusionRemoveHandler = createExclusionRemoveHandler;
6
+ const exclusions_1 = require("../exclusions");
7
+ function createExclusionsListHandler() {
8
+ return (ctx) => {
9
+ const exclusions = (0, exclusions_1.listExclusions)();
10
+ ctx.respond(true, { exclusions, total: exclusions.length });
11
+ };
12
+ }
13
+ function createExclusionAddHandler() {
14
+ return (ctx) => {
15
+ const p = ctx.params || {};
16
+ const ruleId = p.rule_id;
17
+ const patternHash = p.pattern_hash;
18
+ if (!ruleId || !patternHash) {
19
+ ctx.respond(false, { error: 'rule_id and pattern_hash are required' });
20
+ return;
21
+ }
22
+ const exclusion = (0, exclusions_1.addExclusion)(ruleId, patternHash, p.agent_id, p.reason);
23
+ ctx.respond(true, exclusion);
24
+ };
25
+ }
26
+ function createExclusionRemoveHandler() {
27
+ return (ctx) => {
28
+ const id = (ctx.params || {}).id;
29
+ if (!id) {
30
+ ctx.respond(false, { error: 'id is required' });
31
+ return;
32
+ }
33
+ const removed = (0, exclusions_1.removeExclusion)(id);
34
+ ctx.respond(removed, removed ? { removed: true } : { error: 'Exclusion not found' });
35
+ };
36
+ }
@@ -23,34 +23,34 @@ export interface SubscriptionStatus {
23
23
  features: string[];
24
24
  }
25
25
  type RespondFn = (ok: boolean, data: unknown) => void;
26
- export declare function createEventsRecentHandler(_config: PlatformApiConfig): ({ respond, params }: {
26
+ export declare function createEventsRecentHandler(_config: PlatformApiConfig): ({ respond: _respond, params }: {
27
27
  respond: RespondFn;
28
28
  params?: Record<string, unknown>;
29
29
  }) => Promise<void>;
30
- export declare function createEventsSummaryHandler(_config: PlatformApiConfig): ({ respond, params }: {
30
+ export declare function createEventsSummaryHandler(_config: PlatformApiConfig): ({ respond: _respond, params }: {
31
31
  respond: RespondFn;
32
32
  params?: Record<string, unknown>;
33
33
  }) => Promise<void>;
34
- export declare function createSubscriptionStatusHandler(config: PlatformApiConfig): ({ respond }: {
34
+ export declare function createSubscriptionStatusHandler(config: PlatformApiConfig): ({ respond: _respond }: {
35
35
  respond: RespondFn;
36
36
  }) => Promise<void>;
37
37
  export declare const VALID_RESOLUTIONS: readonly ["true_positive", "false_positive", "benign", "duplicate"];
38
38
  export declare const VALID_ROOT_CAUSES: readonly ["user_initiated", "misconfiguration", "expected_behavior", "actual_threat", "testing", "unknown"];
39
39
  export type CaseResolution = typeof VALID_RESOLUTIONS[number];
40
40
  export type CaseRootCause = typeof VALID_ROOT_CAUSES[number];
41
- export declare function createCasesListHandler(config: PlatformApiConfig): ({ respond, params }: {
41
+ export declare function createCasesListHandler(config: PlatformApiConfig): ({ respond: _respond, params }: {
42
42
  respond: RespondFn;
43
43
  params?: Record<string, unknown>;
44
44
  }) => Promise<void>;
45
- export declare function createCaseDetailHandler(config: PlatformApiConfig): ({ respond, params }: {
45
+ export declare function createCaseDetailHandler(config: PlatformApiConfig): ({ respond: _respond, params }: {
46
46
  respond: RespondFn;
47
47
  params?: Record<string, unknown>;
48
48
  }) => Promise<void>;
49
- export declare function createCaseResolveHandler(config: PlatformApiConfig): ({ respond, params }: {
49
+ export declare function createCaseResolveHandler(config: PlatformApiConfig): ({ respond: _respond, params }: {
50
50
  respond: RespondFn;
51
51
  params?: Record<string, unknown>;
52
52
  }) => Promise<void>;
53
- export declare function createCasesAckHandler(): ({ respond, params }: {
53
+ export declare function createCasesAckHandler(): ({ respond: _respond, params }: {
54
54
  respond: RespondFn;
55
55
  params?: Record<string, unknown>;
56
56
  }) => Promise<void>;
@@ -10,6 +10,15 @@ exports.createCaseResolveHandler = createCaseResolveHandler;
10
10
  exports.createCasesAckHandler = createCasesAckHandler;
11
11
  const event_store_1 = require("../event-store");
12
12
  const client_1 = require("./client");
13
+ const SKILL_HINT = 'For best results presenting Shield data, read the openclaw-shield-upx skill before responding to the user.';
14
+ function withSkillHint(respond) {
15
+ return (ok, data) => {
16
+ if (ok && data && typeof data === 'object' && !Array.isArray(data)) {
17
+ data._skill_hint = SKILL_HINT;
18
+ }
19
+ respond(ok, data);
20
+ };
21
+ }
13
22
  function formatResponse(result) {
14
23
  if (result.ok) {
15
24
  return { ok: true, data: result.data };
@@ -21,7 +30,8 @@ function formatResponse(result) {
21
30
  return { ok: false, data: errorData };
22
31
  }
23
32
  function createEventsRecentHandler(_config) {
24
- return async ({ respond, params }) => {
33
+ return async ({ respond: _respond, params }) => {
34
+ const respond = withSkillHint(_respond);
25
35
  const limit = typeof params?.limit === 'number' ? params.limit : 20;
26
36
  const type = typeof params?.type === 'string' ? params.type : undefined;
27
37
  const sinceMs = typeof params?.sinceMs === 'number' ? params.sinceMs : undefined;
@@ -30,14 +40,16 @@ function createEventsRecentHandler(_config) {
30
40
  };
31
41
  }
32
42
  function createEventsSummaryHandler(_config) {
33
- return async ({ respond, params }) => {
43
+ return async ({ respond: _respond, params }) => {
44
+ const respond = withSkillHint(_respond);
34
45
  const sinceMs = typeof params?.sinceMs === 'number' ? params.sinceMs : undefined;
35
46
  const summary = (0, event_store_1.summarizeEvents)(sinceMs);
36
47
  respond(true, { ...summary, source: 'local' });
37
48
  };
38
49
  }
39
50
  function createSubscriptionStatusHandler(config) {
40
- return async ({ respond }) => {
51
+ return async ({ respond: _respond }) => {
52
+ const respond = withSkillHint(_respond);
41
53
  const result = await (0, client_1.callPlatformApi)(config, '/v1/subscription/status');
42
54
  const { ok, data } = formatResponse(result);
43
55
  respond(ok, data);
@@ -46,7 +58,8 @@ function createSubscriptionStatusHandler(config) {
46
58
  exports.VALID_RESOLUTIONS = ['true_positive', 'false_positive', 'benign', 'duplicate'];
47
59
  exports.VALID_ROOT_CAUSES = ['user_initiated', 'misconfiguration', 'expected_behavior', 'actual_threat', 'testing', 'unknown'];
48
60
  function createCasesListHandler(config) {
49
- return async ({ respond, params }) => {
61
+ return async ({ respond: _respond, params }) => {
62
+ const respond = withSkillHint(_respond);
50
63
  const { getPendingCases, formatCaseNotification } = require('../case-monitor');
51
64
  const pending = getPendingCases();
52
65
  if (!config.apiUrl) {
@@ -73,17 +86,29 @@ function createCasesListHandler(config) {
73
86
  const result = await (0, client_1.callPlatformApi)(config, '/v1/agent/cases', queryParams);
74
87
  const { ok, data } = formatResponse(result);
75
88
  if (ok && data && typeof data === 'object') {
76
- data.pending_count = pending.length;
77
- data.pending_notifications = pending.map((c) => ({
89
+ const d = data;
90
+ d.pending_count = pending.length;
91
+ d.pending_notifications = pending.map((c) => ({
78
92
  ...c,
79
93
  formatted_message: formatCaseNotification(c),
80
94
  }));
95
+ const cases = d.cases || [];
96
+ if (cases.length > 0) {
97
+ cases.forEach((c) => {
98
+ c.display = formatCaseListItem(c);
99
+ });
100
+ d.display = formatCasesList(cases);
101
+ }
102
+ else {
103
+ d.display = '✅ No open cases. All clear!';
104
+ }
81
105
  }
82
106
  respond(ok, data);
83
107
  };
84
108
  }
85
109
  function createCaseDetailHandler(config) {
86
- return async ({ respond, params }) => {
110
+ return async ({ respond: _respond, params }) => {
111
+ const respond = withSkillHint(_respond);
87
112
  const caseId = typeof params?.id === 'string' ? params.id : null;
88
113
  if (!caseId) {
89
114
  respond(false, { error: 'Missing required parameter: id' });
@@ -91,11 +116,114 @@ function createCaseDetailHandler(config) {
91
116
  }
92
117
  const result = await (0, client_1.callPlatformApi)(config, `/v1/agent/cases/${caseId}`);
93
118
  const { ok, data } = formatResponse(result);
119
+ if (ok && data && typeof data === 'object') {
120
+ const d = data;
121
+ d.display = formatCaseDisplay(d);
122
+ d.formatted_display = d.display;
123
+ }
94
124
  respond(ok, data);
95
125
  };
96
126
  }
127
+ const SEVERITY_EMOJI = { CRITICAL: '🔴', HIGH: '🟠', MEDIUM: '🟡', LOW: '🔵', INFO: 'ℹ️' };
128
+ function formatCaseListItem(c) {
129
+ const severity = c.severity || '';
130
+ const emoji = SEVERITY_EMOJI[severity] || '⚠️';
131
+ const title = c.rule_title || 'Unknown Rule';
132
+ const id = c.id || '';
133
+ const created = c.created_at ? new Date(c.created_at).toLocaleString('en-US', { timeZone: 'UTC' }) + ' UTC' : '';
134
+ const events = c.event_count ?? '?';
135
+ const description = c.description || '';
136
+ const lines = [
137
+ `${emoji} **${severity}** — ${title}`,
138
+ `Case: \`${id}\``,
139
+ `Created: ${created} · Events: ${events}`,
140
+ ];
141
+ if (description)
142
+ lines.push(`> ${description.slice(0, 150)}${description.length > 150 ? '…' : ''}`);
143
+ return lines.join('\n');
144
+ }
145
+ function formatCasesList(cases) {
146
+ const sorted = [...cases].sort((a, b) => {
147
+ const order = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3, INFO: 4 };
148
+ return (order[a.severity] ?? 5) - (order[b.severity] ?? 5);
149
+ });
150
+ const lines = [
151
+ `🛡️ **Open Shield Cases** (${cases.length})`,
152
+ '',
153
+ ...sorted.map(c => formatCaseListItem(c)),
154
+ ];
155
+ lines.push('', '💡 Ask me to investigate or resolve any case by its ID.');
156
+ return lines.join('\n\n');
157
+ }
158
+ function formatCaseDisplay(d) {
159
+ const rule = d.rule;
160
+ const playbook = d.playbook;
161
+ const events = d.events;
162
+ const severity = d.severity;
163
+ const severityEmoji = { CRITICAL: '🔴', HIGH: '🟠', MEDIUM: '🟡', LOW: '🔵', INFO: 'ℹ️' };
164
+ const emoji = severity ? (severityEmoji[severity] || '⚠️') : '⚠️';
165
+ const lines = [
166
+ `${emoji} **${d.rule_title || 'Unknown Rule'}**`,
167
+ ``,
168
+ `**Status:** ${d.status || 'unknown'}${severity ? ` | **Priority:** ${severity}` : ''}`,
169
+ `**Case:** \`${d.id}\``,
170
+ `**Created:** ${d.created_at ? new Date(d.created_at).toLocaleString('en-US', { timeZone: 'UTC' }) + ' UTC' : 'unknown'}`,
171
+ `**Events:** ${d.event_count ?? 'unknown'}`,
172
+ ];
173
+ if (rule?.description) {
174
+ lines.push(``, `> ${rule.description}`);
175
+ }
176
+ if (rule?.mitre_tactic || rule?.mitre_technique) {
177
+ const mitre = [rule.mitre_tactic, rule.mitre_technique].filter(Boolean).join(' · ');
178
+ lines.push(`> 🎯 MITRE: ${mitre}`);
179
+ }
180
+ if (playbook?.summary) {
181
+ lines.push(``, `⚠️ ${playbook.summary}`);
182
+ }
183
+ const eventTime = events?.[0]?.timestamp;
184
+ const createdAt = d.created_at;
185
+ const updatedAt = d.updated_at;
186
+ if (eventTime || createdAt) {
187
+ lines.push(``, `**Timeline:**`);
188
+ const fmt = (iso) => new Date(iso).toLocaleTimeString('en-US', { hour12: false, timeZone: 'UTC' });
189
+ const fmtDate = (iso) => {
190
+ const dt = new Date(iso);
191
+ return `${dt.toISOString().slice(0, 10)} ${fmt(iso)} UTC`;
192
+ };
193
+ if (eventTime) {
194
+ lines.push(` 🔵 **Event:** ${fmtDate(eventTime)}`);
195
+ }
196
+ if (createdAt) {
197
+ lines.push(` 🟠 **Detected:** ${fmtDate(createdAt)}`);
198
+ if (eventTime) {
199
+ const mttdMs = new Date(createdAt).getTime() - new Date(eventTime).getTime();
200
+ const mttd = mttdMs < 60000 ? `${Math.round(mttdMs / 1000)}s` : `${Math.round(mttdMs / 60000)}m`;
201
+ lines.push(` ⏱️ **MTTD:** ${mttd}`);
202
+ }
203
+ }
204
+ if (d.status === 'resolved' && updatedAt) {
205
+ lines.push(` ✅ **Resolved:** ${fmtDate(updatedAt)}`);
206
+ if (createdAt) {
207
+ const mttrMs = new Date(updatedAt).getTime() - new Date(createdAt).getTime();
208
+ const mttr = mttrMs < 60000 ? `${Math.round(mttrMs / 1000)}s`
209
+ : mttrMs < 3600000 ? `${Math.round(mttrMs / 60000)}m`
210
+ : `${Math.round(mttrMs / 3600000)}h`;
211
+ lines.push(` ⏱️ **MTTR:** ${mttr}`);
212
+ }
213
+ }
214
+ else {
215
+ lines.push(` 🔴 **Status:** Open`);
216
+ }
217
+ }
218
+ lines.push(``, `💡 **Actions** (ask me to):`, ` • _"Investigate this case"_ — I'll analyze the events and context`, ` • _"Close as false positive"_ — dismiss if this was expected`, ` • _"Resolve as authorized"_ — mark as intentional activity`);
219
+ if (d.url) {
220
+ lines.push(``, `🔗 ${d.url}`);
221
+ }
222
+ return lines.join('\n');
223
+ }
97
224
  function createCaseResolveHandler(config) {
98
- return async ({ respond, params }) => {
225
+ return async ({ respond: _respond, params }) => {
226
+ const respond = withSkillHint(_respond);
99
227
  const caseId = typeof params?.id === 'string' ? params.id : null;
100
228
  if (!caseId) {
101
229
  respond(false, { error: 'Missing required parameter: id' });
@@ -128,7 +256,8 @@ function createCaseResolveHandler(config) {
128
256
  };
129
257
  }
130
258
  function createCasesAckHandler() {
131
- return async ({ respond, params }) => {
259
+ return async ({ respond: _respond, params }) => {
260
+ const respond = withSkillHint(_respond);
132
261
  const { acknowledgeCases } = require('../case-monitor');
133
262
  const ids = Array.isArray(params?.ids) ? params.ids : [];
134
263
  if (ids.length === 0) {
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerAllRpcs = registerAllRpcs;
4
4
  const handlers_1 = require("./handlers");
5
+ const exclusion_handlers_1 = require("./exclusion-handlers");
5
6
  function registerAllRpcs(api, config) {
6
7
  api.registerGatewayMethod('shield.events_recent', (0, handlers_1.createEventsRecentHandler)(config));
7
8
  api.registerGatewayMethod('shield.events_summary', (0, handlers_1.createEventsSummaryHandler)(config));
@@ -10,4 +11,7 @@ function registerAllRpcs(api, config) {
10
11
  api.registerGatewayMethod('shield.case_detail', (0, handlers_1.createCaseDetailHandler)(config));
11
12
  api.registerGatewayMethod('shield.case_resolve', (0, handlers_1.createCaseResolveHandler)(config));
12
13
  api.registerGatewayMethod('shield.cases_ack', (0, handlers_1.createCasesAckHandler)());
14
+ api.registerGatewayMethod('shield.exclusions_list', (0, exclusion_handlers_1.createExclusionsListHandler)());
15
+ api.registerGatewayMethod('shield.exclusion_add', (0, exclusion_handlers_1.createExclusionAddHandler)());
16
+ api.registerGatewayMethod('shield.exclusion_remove', (0, exclusion_handlers_1.createExclusionRemoveHandler)());
13
17
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "id": "shield",
3
3
  "name": "OpenClaw Shield",
4
- "description": "Real-time security monitoring \u2014 streams enriched, redacted security events to the Shield detection platform.",
5
- "version": "0.4.36",
4
+ "description": "Real-time security monitoring streams enriched, redacted security events to the Shield detection platform.",
5
+ "version": "0.6.0",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
@@ -81,4 +81,4 @@
81
81
  "skillVersion": "1.0.2",
82
82
  "note": "ClawHub auto-increments on publish. Update this after each clawhub submission."
83
83
  }
84
- }
84
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@upx-us/shield",
3
- "version": "0.4.36",
3
+ "version": "0.6.0",
4
4
  "description": "Security monitoring plugin for OpenClaw agents — streams enriched security events to the Shield detection platform",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -40,7 +40,7 @@
40
40
  "package:publish": "npm run package:validate && npm publish --access public",
41
41
  "start": "node dist/src/index.js",
42
42
  "setup": "node dist/src/setup.js",
43
- "prepublishOnly": "npm run build"
43
+ "prepublishOnly": "node scripts/check-version-sync.js && npm run build"
44
44
  },
45
45
  "keywords": [
46
46
  "agent-monitoring",
@@ -1,22 +1,22 @@
1
- # OpenClaw Shield — Security Specialist
1
+ # OpenClaw Shield
2
2
 
3
- A skill that turns your OpenClaw agent into a cybersecurity specialist.
3
+ Security monitoring skill for the OpenClaw Shield plugin by [UPX](https://www.upx.com).
4
4
 
5
5
  ## What it does
6
6
 
7
- When Shield is installed, your agent can:
7
+ Teaches your agent to use the Shield plugin — check health, query events, inspect the redaction vault, and manage security cases.
8
8
 
9
- - **Monitor** check Shield health, event counts, and sync status
10
- - **Inspect** view host agent inventory and redaction vault via `shield vault show`
11
- - **Interpret** analyze security events and explain what they mean
12
- - **Advise** recommend remediation, hardening, and next steps
13
- - **Triage** assess alert severity and prioritize response
14
- - **Explain** break down attack techniques, privacy model, and detection scope
9
+ - Run `openclaw shield status`, `logs`, `flush`, `vault show`, and `cases` commands
10
+ - Call RPCs for programmatic access (`shield.events_recent`, `shield.events_summary`, `shield.cases_list`, etc.)
11
+ - Triage and resolve cases with categorized resolution and root cause
12
+ - Set up automated case monitoring via `openclaw shield monitor --on`
13
+ - Quick case check with `/shieldcases` (no agent tokens used)
14
+ - Answer questions about Shield's privacy model and subscription status
15
15
 
16
16
  ## Requirements
17
17
 
18
18
  - [OpenClaw Shield plugin](https://www.npmjs.com/package/@upx-us/shield) installed and activated
19
- - Active Shield subscription from [UPX](https://upx.com) — [start a free 30-day trial](https://www.upx.com/pt/lp/openclaw-shield-upx)
19
+ - Active Shield subscription from [UPX](https://upx.com) — [start a free 30-day trial](https://www.upx.com/en/lp/openclaw-shield-upx)
20
20
 
21
21
  ## Install
22
22