clawarmor 3.4.0 → 3.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,6 +52,7 @@ ClawArmor sits at the foundation and orchestrates the layers above it:
52
52
  | `audit` | Score your OpenClaw config (0–100), live gateway probes, plain-English verdict |
53
53
  | `scan` | Scan all installed skill files for malicious code and SKILL.md instructions |
54
54
  | `scan --json` | Machine-readable scan output — pipe to CI, scripts, or dashboards |
55
+ | `scan --report` | Write structured JSON + Markdown reports after scanning (v3.5.1) |
55
56
  | `prescan <skill>` | Pre-scan a skill before installing — blocks on CRITICAL findings |
56
57
  | `skill verify <name>` | Deep-verify a specific installed skill — checks SKILL.md + all referenced scripts |
57
58
  | `fix` | Auto-apply safe fixes (--dry-run to preview, --apply to run) |
@@ -164,6 +165,34 @@ clawarmor harden --auto --report
164
165
 
165
166
  Report structure includes: version, timestamp, OS/OpenClaw info, summary counts (hardened/skipped/already-good), and per-check action details with before/after values.
166
167
 
168
+ **Scan reports** (v3.5.1) — Export a structured report after scanning skills:
169
+
170
+ ```bash
171
+ # Write JSON + Markdown reports (e.g. ~/.openclaw/clawarmor-scan-report-2025-03-08.json + .md)
172
+ clawarmor scan --report
173
+ ```
174
+
175
+ Two files are always written together:
176
+ - `clawarmor-scan-report-YYYY-MM-DD.json` — machine-readable, includes per-skill status, severity, findings, and overall score
177
+ - `clawarmor-scan-report-YYYY-MM-DD.md` — human-readable with executive summary table, findings detail, and remediation steps
178
+
179
+ Example JSON structure:
180
+ ```json
181
+ {
182
+ "version": "3.5.1",
183
+ "timestamp": "2025-03-08T12:00:00.000Z",
184
+ "system": { "hostname": "myhost", "platform": "darwin", "node_version": "v20.0.0", "openclaw_version": "1.2.0" },
185
+ "verdict": "PASS",
186
+ "score": 100,
187
+ "summary": { "total": 12, "passed": 12, "failed": 0, "warnings": 0, "critical_findings": 0, "high_findings": 0 },
188
+ "checks": [
189
+ { "name": "weather", "status": "pass", "severity": "NONE", "detail": "No findings", "type": "user" }
190
+ ]
191
+ }
192
+ ```
193
+
194
+ Terminal output is still shown when `--report` is used — the flag only adds file output on top.
195
+
167
196
  ## Philosophy
168
197
 
169
198
  ClawArmor runs entirely on your machine — no telemetry, no cloud, no accounts.
package/cli.js CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  import { paint } from './lib/output/colors.js';
5
5
 
6
- const VERSION = '3.4.0';
6
+ const VERSION = '3.5.1';
7
7
  const GATEWAY_PORT_DEFAULT = 18789;
8
8
 
9
9
  function isLocalhost(host) {
@@ -144,8 +144,13 @@ if (cmd === 'audit') {
144
144
  }
145
145
 
146
146
  if (cmd === 'scan') {
147
+ const scanReportIdx = args.indexOf('--report');
148
+ const scanFlags = {
149
+ json: flags.json,
150
+ report: scanReportIdx !== -1,
151
+ };
147
152
  const { runScan } = await import('./lib/scan.js');
148
- process.exit(await runScan({ json: flags.json }));
153
+ process.exit(await runScan(scanFlags));
149
154
  }
150
155
 
151
156
  if (cmd === 'verify') {
package/lib/harden.js CHANGED
@@ -25,7 +25,7 @@ const HISTORY_FILE = join(CLAWARMOR_DIR, 'history.json');
25
25
  const CLI_PATH = new URL('../cli.js', import.meta.url).pathname;
26
26
  const SEP = paint.dim('─'.repeat(52));
27
27
 
28
- const VERSION = '3.4.0';
28
+ const VERSION = '3.5.1';
29
29
 
30
30
  // ── Impact levels ─────────────────────────────────────────────────────────────
31
31
  // SAFE: No functionality impact. Pure security improvement.
package/lib/scan.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { existsSync, writeFileSync, mkdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { homedir, hostname, platform } from 'os';
4
+ import { spawnSync } from 'child_process';
1
5
  import { paint, severityColor } from './output/colors.js';
2
6
  import { scanFile } from './scanner/file-scanner.js';
3
7
  import { findInstalledSkills } from './scanner/skill-finder.js';
@@ -5,7 +9,8 @@ import { scanSkillMdFiles } from './scanner/skill-md-scanner.js';
5
9
  import { append as auditLogAppend } from './audit-log.js';
6
10
 
7
11
  const SEP = paint.dim('─'.repeat(52));
8
- const HOME = process.env.HOME || '';
12
+ const HOME = homedir();
13
+ const VERSION = '3.5.1';
9
14
 
10
15
  function short(p) { return p.replace(HOME,'~'); }
11
16
 
@@ -16,8 +21,224 @@ function box(title) {
16
21
  paint.dim('╚'+'═'.repeat(W-2)+'╝')].join('\n');
17
22
  }
18
23
 
24
+ // ── Report support ────────────────────────────────────────────────────────────
25
+
26
+ function getSystemInfo() {
27
+ let ocVersion = 'unknown';
28
+ try {
29
+ const r = spawnSync('openclaw', ['--version'], { encoding: 'utf8', timeout: 5000 });
30
+ if (r.stdout) ocVersion = r.stdout.trim().split('\n')[0] || 'unknown';
31
+ } catch { /* non-fatal */ }
32
+ return {
33
+ hostname: hostname(),
34
+ platform: platform(),
35
+ node_version: process.version,
36
+ openclaw_version: ocVersion,
37
+ };
38
+ }
39
+
40
+ function defaultReportBasePath() {
41
+ const date = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
42
+ return join(HOME, '.openclaw', `clawarmor-scan-report-${date}`);
43
+ }
44
+
45
+ function computeScanScore(findings) {
46
+ let score = 100;
47
+ for (const f of findings) {
48
+ if (f.severity === 'CRITICAL') score -= 25;
49
+ else if (f.severity === 'HIGH') score -= 10;
50
+ else if (f.severity === 'MEDIUM') score -= 3;
51
+ }
52
+ return Math.max(0, score);
53
+ }
54
+
55
+ function buildReportChecks(skills, allJsonFindings) {
56
+ // Build per-skill check entries
57
+ const checks = [];
58
+
59
+ // Skills with no findings
60
+ for (const skill of skills) {
61
+ const skillFindings = allJsonFindings.filter(f => f.skill === skill.name);
62
+ if (!skillFindings.length) {
63
+ checks.push({
64
+ name: skill.name,
65
+ status: 'pass',
66
+ severity: 'NONE',
67
+ detail: 'No findings',
68
+ type: skill.isBuiltin ? 'builtin' : 'user',
69
+ });
70
+ } else {
71
+ const maxSev = ['CRITICAL','HIGH','MEDIUM','LOW','INFO'].find(s =>
72
+ skillFindings.some(f => f.severity === s)
73
+ ) || 'INFO';
74
+ const status = maxSev === 'CRITICAL' ? 'block' : maxSev === 'HIGH' ? 'warn' : 'info';
75
+ checks.push({
76
+ name: skill.name,
77
+ status,
78
+ severity: maxSev,
79
+ detail: `${skillFindings.length} finding(s): ${skillFindings.map(f => f.patternId).join(', ')}`,
80
+ type: skill.isBuiltin ? 'builtin' : 'user',
81
+ findings: skillFindings.map(f => ({
82
+ patternId: f.patternId,
83
+ severity: f.severity,
84
+ message: f.message,
85
+ file: f.file,
86
+ line: f.line,
87
+ })),
88
+ });
89
+ }
90
+ }
91
+ return checks;
92
+ }
93
+
94
+ function writeJsonReport(reportPath, { skills, allJsonFindings, totalCritical, totalHigh }) {
95
+ const sysInfo = getSystemInfo();
96
+ const checks = buildReportChecks(skills, allJsonFindings);
97
+ const score = computeScanScore(allJsonFindings);
98
+ const passed = checks.filter(c => c.status === 'pass').length;
99
+ const failed = checks.filter(c => c.status === 'block').length;
100
+ const warnings = checks.filter(c => c.status === 'warn').length;
101
+
102
+ let verdict = 'PASS';
103
+ if (totalCritical > 0) verdict = 'BLOCK';
104
+ else if (totalHigh > 0) verdict = 'WARN';
105
+
106
+ const report = {
107
+ version: VERSION,
108
+ timestamp: new Date().toISOString(),
109
+ system: sysInfo,
110
+ verdict,
111
+ score,
112
+ summary: {
113
+ total: checks.length,
114
+ passed,
115
+ failed,
116
+ warnings,
117
+ critical_findings: totalCritical,
118
+ high_findings: totalHigh,
119
+ },
120
+ checks,
121
+ };
122
+
123
+ try { mkdirSync(dirname(reportPath), { recursive: true }); } catch {}
124
+ writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf8');
125
+ return report;
126
+ }
127
+
128
+ function writeMarkdownReport(reportPath, { skills, allJsonFindings, totalCritical, totalHigh }) {
129
+ const sysInfo = getSystemInfo();
130
+ const checks = buildReportChecks(skills, allJsonFindings);
131
+ const score = computeScanScore(allJsonFindings);
132
+ const passed = checks.filter(c => c.status === 'pass').length;
133
+ const failed = checks.filter(c => c.status === 'block').length;
134
+ const warnings = checks.filter(c => c.status === 'warn').length;
135
+
136
+ let verdict = 'PASS';
137
+ if (totalCritical > 0) verdict = 'BLOCK';
138
+ else if (totalHigh > 0) verdict = 'WARN';
139
+
140
+ const now = new Date();
141
+ const dateStr = now.toLocaleString('en-US', {
142
+ year: 'numeric', month: '2-digit', day: '2-digit',
143
+ hour: '2-digit', minute: '2-digit', hour12: false
144
+ });
145
+
146
+ let verdictEmoji = verdict === 'BLOCK' ? '🔴' : verdict === 'WARN' ? '🟡' : '🟢';
147
+
148
+ let md = `# ClawArmor Skill Scan Report
149
+ Generated: ${dateStr}
150
+ ClawArmor: v${VERSION} | Hostname: ${sysInfo.hostname} | Platform: ${sysInfo.platform} | Node: ${sysInfo.node_version} | OpenClaw: ${sysInfo.openclaw_version}
151
+
152
+ ## Executive Summary
153
+
154
+ | Metric | Value |
155
+ |--------|-------|
156
+ | Verdict | ${verdictEmoji} **${verdict}** |
157
+ | Score | ${score}/100 |
158
+ | Total Skills | ${skills.length} |
159
+ | Passed (clean) | ✅ ${passed} |
160
+ | Warnings | ⚠️ ${warnings} |
161
+ | Blocked (critical) | ❌ ${failed} |
162
+ | Critical Findings | ${totalCritical} |
163
+ | High Findings | ${totalHigh} |
164
+
165
+ `;
166
+
167
+ if (checks.some(c => c.status !== 'pass')) {
168
+ md += `## Skill Check Results
169
+
170
+ | Skill | Type | Status | Severity | Detail |
171
+ |-------|------|--------|----------|--------|
172
+ `;
173
+ for (const check of checks) {
174
+ const statusEmoji = check.status === 'pass' ? '✅ pass' : check.status === 'block' ? '❌ block' : '⚠️ warn';
175
+ md += `| ${check.name} | ${check.type} | ${statusEmoji} | ${check.severity} | ${check.detail} |\n`;
176
+ }
177
+ } else {
178
+ md += `## Skill Check Results
179
+
180
+ All ${skills.length} skills are clean. No findings detected.
181
+ `;
182
+ }
183
+
184
+ // Detailed findings for flagged skills
185
+ const flaggedChecks = checks.filter(c => c.status !== 'pass' && c.findings && c.findings.length);
186
+ if (flaggedChecks.length) {
187
+ md += `
188
+ ## Findings Detail
189
+
190
+ `;
191
+ for (const check of flaggedChecks) {
192
+ md += `### ${check.name} (${check.type})\n\n`;
193
+ md += `| Pattern ID | Severity | Message | File | Line |\n`;
194
+ md += `|------------|----------|---------|------|------|\n`;
195
+ for (const f of check.findings) {
196
+ md += `| ${f.patternId} | ${f.severity} | ${f.message || '—'} | ${f.file || '—'} | ${f.line ?? '—'} |\n`;
197
+ }
198
+ md += '\n';
199
+ }
200
+ }
201
+
202
+ // Remediation
203
+ md += `## Remediation Steps
204
+
205
+ `;
206
+ if (totalCritical > 0) {
207
+ md += `### 🔴 Critical — Immediate Action Required
208
+
209
+ - Remove or replace any skill with CRITICAL findings immediately
210
+ - Run \`clawarmor prescan <skill-name>\` before reinstalling
211
+ - Check for data exfiltration attempts: look for external HTTP calls, encoded payloads
212
+ - Review git history of the skill if available
213
+
214
+ `;
215
+ }
216
+ if (totalHigh > 0) {
217
+ md += `### 🟡 High — Review Before Next Session
218
+
219
+ - Audit HIGH-severity skills before running your agent
220
+ - Run \`clawarmor skill verify <name>\` for a deeper inspection
221
+ - Consider disabling suspect skills temporarily: check your OpenClaw skill config
222
+
223
+ `;
224
+ }
225
+ md += `### General
226
+
227
+ - Run \`clawarmor scan\` regularly to catch new findings
228
+ - Use \`clawarmor prescan <skill-name>\` before installing any new skill
229
+ - Keep skills updated — malicious patterns are added to ClawArmor signatures continuously
230
+ - Run \`clawarmor audit\` to check your broader OpenClaw configuration
231
+ `;
232
+
233
+ try { mkdirSync(dirname(reportPath), { recursive: true }); } catch {}
234
+ writeFileSync(reportPath, md, 'utf8');
235
+ }
236
+
237
+ // ── Main export ───────────────────────────────────────────────────────────────
238
+
19
239
  export async function runScan(flags = {}) {
20
240
  const jsonMode = flags.json || false;
241
+ const reportMode = flags.report || false;
21
242
 
22
243
  if (!jsonMode) {
23
244
  console.log(''); console.log(box('ClawArmor Skill Scan v0.6')); console.log('');
@@ -33,6 +254,17 @@ export async function runScan(flags = {}) {
33
254
  } else {
34
255
  console.log(` ${paint.dim('No installed skills found.')}`); console.log('');
35
256
  }
257
+
258
+ if (reportMode) {
259
+ const basePath = defaultReportBasePath();
260
+ const jsonPath = basePath + '.json';
261
+ const mdPath = basePath + '.md';
262
+ writeJsonReport(jsonPath, { skills: [], allJsonFindings: [], totalCritical: 0, totalHigh: 0 });
263
+ writeMarkdownReport(mdPath, { skills: [], allJsonFindings: [], totalCritical: 0, totalHigh: 0 });
264
+ const date = new Date().toISOString().slice(0, 10);
265
+ console.log(`\n ${paint.dim('Report saved:')} clawarmor-scan-report-${date}.json + .md`);
266
+ }
267
+
36
268
  return 0;
37
269
  }
38
270
 
@@ -46,7 +278,7 @@ export async function runScan(flags = {}) {
46
278
  let totalCritical = 0, totalHigh = 0;
47
279
  const flagged = [];
48
280
  const auditFindings = []; // accumulated for audit log
49
- const jsonFindings = []; // for --json output
281
+ const jsonFindings = []; // for --json output and --report
50
282
 
51
283
  for (const skill of skills) {
52
284
  if (!jsonMode) process.stdout.write(` ${skill.isBuiltin ? paint.dim('⊙') : paint.cyan('▶')} ${paint.bold(skill.name)}${paint.dim(skill.isBuiltin?' [built-in]':' [user]')}...`);
@@ -68,7 +300,7 @@ export async function runScan(flags = {}) {
68
300
 
69
301
  totalCritical += critical.length; totalHigh += high.length;
70
302
 
71
- // Collect findings for JSON output
303
+ // Collect findings for JSON output and report
72
304
  for (const f of allFindings) {
73
305
  if (['CRITICAL','HIGH','MEDIUM','LOW'].includes(f.severity)) {
74
306
  for (const m of (f.matches || [])) {
@@ -113,13 +345,7 @@ export async function runScan(flags = {}) {
113
345
  // JSON output mode
114
346
  if (jsonMode) {
115
347
  // Compute scan score: start 100, -25 per CRITICAL, -10 per HIGH, -3 per MEDIUM
116
- let scanScore = 100;
117
- for (const f of jsonFindings) {
118
- if (f.severity === 'CRITICAL') scanScore -= 25;
119
- else if (f.severity === 'HIGH') scanScore -= 10;
120
- else if (f.severity === 'MEDIUM') scanScore -= 3;
121
- }
122
- scanScore = Math.max(0, scanScore);
348
+ const scanScore = computeScanScore(jsonFindings);
123
349
 
124
350
  let verdict = 'PASS';
125
351
  if (totalCritical > 0) verdict = 'BLOCK';
@@ -210,5 +436,18 @@ export async function runScan(flags = {}) {
210
436
  skill: null,
211
437
  });
212
438
 
439
+ // ── Write report if requested ──────────────────────────────────────────────
440
+ if (reportMode) {
441
+ const basePath = defaultReportBasePath();
442
+ const jsonPath = basePath + '.json';
443
+ const mdPath = basePath + '.md';
444
+ const reportData = { skills, allJsonFindings: jsonFindings, totalCritical, totalHigh };
445
+ writeJsonReport(jsonPath, reportData);
446
+ writeMarkdownReport(mdPath, reportData);
447
+ const date = new Date().toISOString().slice(0, 10);
448
+ console.log(` ${paint.dim('Report saved:')} clawarmor-scan-report-${date}.json + .md`);
449
+ console.log('');
450
+ }
451
+
213
452
  return totalCritical > 0 ? 1 : 0;
214
453
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clawarmor",
3
- "version": "3.4.0",
4
- "description": "Security armor for OpenClaw agents audit, scan, monitor",
3
+ "version": "3.5.1",
4
+ "description": "Security armor for OpenClaw agents \u2014 audit, scan, monitor",
5
5
  "bin": {
6
6
  "clawarmor": "cli.js"
7
7
  },
@@ -0,0 +1,28 @@
1
+ ## What Was Shipped
2
+
3
+ `clawarmor harden --report` — structured hardening report command
4
+
5
+ ### Changes:
6
+ - `lib/harden.js`: Added full report support — JSON + Markdown writers, per-check tracking,
7
+ system info capture, report summary printer. Existing behavior unchanged when --report not passed.
8
+ - `cli.js`: Added --report, --report-format flag parsing for harden command
9
+ - `package.json`: Version bumped 3.3.0 → 3.4.0
10
+ - `README.md`: Added "Hardening reports (v3.4.0)" section with all usage examples
11
+ - `CHANGELOG.md`: Added [3.4.0] entry
12
+
13
+ ### Commands now supported:
14
+ clawarmor harden --report
15
+ clawarmor harden --report /path/to/report.json
16
+ clawarmor harden --report /path/to/report.md --report-format text
17
+ clawarmor harden --auto --report
18
+
19
+ ## npm Publish Status
20
+
21
+ ✅ SUCCESS — clawarmor@3.4.0 published to npm (tag: latest)
22
+
23
+ ## Issues Encountered
24
+
25
+ None. All three test cases passed:
26
+ 1. `clawarmor harden --report` → wrote valid JSON to default path
27
+ 2. `clawarmor harden --report /tmp/test-report.md --report-format text` → wrote clean Markdown
28
+ 3. `clawarmor harden --auto --report /tmp/test-report.json` → wrote valid JSON
@@ -0,0 +1,130 @@
1
+ # ClawArmor v3.4.0 Sprint Report
2
+ **Sprint:** `harden --report` feature
3
+ **Date:** 2026-03-08
4
+ **Status:** ✅ SHIPPED
5
+
6
+ ---
7
+
8
+ ## Summary
9
+
10
+ Shipped `clawarmor harden --report` — a portable hardening report command that exports a structured summary of every hardening run.
11
+
12
+ ## Done Criteria
13
+
14
+ - [x] `clawarmor harden --report` runs and writes JSON report to `~/.openclaw/clawarmor-harden-report-YYYY-MM-DD.json`
15
+ - [x] `clawarmor harden --report /tmp/test.md --report-format text` writes Markdown report
16
+ - [x] `npm publish` succeeded — v3.4.0 live on npm
17
+ - [x] README updated with new `--report` flag and example output
18
+ - [x] Sprint report written to `~/clawarmor/sprint-output/v340-sprint-report.md`
19
+
20
+ ---
21
+
22
+ ## Changes Shipped
23
+
24
+ ### `lib/harden.js`
25
+ - Added `--report` flag support via `flags.report`, `flags.reportPath`, `flags.reportFormat`
26
+ - Added `getSystemInfo()` — captures OS and OpenClaw version for report metadata
27
+ - Added `defaultReportPath(format)` — auto-generates path `~/.openclaw/clawarmor-harden-report-YYYY-MM-DD.{json|md}`
28
+ - Added `buildReportItems()` — assembles per-check items from applied/skipped/failed tracking
29
+ - Added `writeJsonReport()` — writes structured JSON with version, timestamp, system, summary, items
30
+ - Added `writeMarkdownReport()` — writes human-readable Markdown table with summary, actions, skipped, failed
31
+ - Added `printReportSummary()` — prints inline summary to stdout after writing the report
32
+ - Modified main loop to track `appliedIds`, `skippedIds`, `failedIds`, `applyResults` per fix
33
+ - Added `_reportBefore` and `_reportAfter` fields to each fix for before/after capture
34
+ - Existing behavior **fully preserved** when `--report` not passed
35
+
36
+ ### `cli.js`
37
+ - Added `--report`, `--report-format`, `--report-path` flag parsing for `harden` command
38
+ - `--report` can be bare flag or `--report <path>` (path detected if next arg doesn't start with `--`)
39
+ - Version constant bumped: `3.3.0` → `3.4.0`
40
+
41
+ ### `package.json`
42
+ - Version bumped to `3.4.0`
43
+
44
+ ### `README.md`
45
+ - Updated harden command row to include `--report`
46
+ - Added new "Hardening reports (v3.4.0)" section with all usage examples
47
+
48
+ ### `CHANGELOG.md`
49
+ - Added `[3.4.0]` entry with full feature description and examples
50
+
51
+ ---
52
+
53
+ ## Report Format: JSON
54
+
55
+ ```json
56
+ {
57
+ "version": "3.4.0",
58
+ "timestamp": "2026-03-08T07:11:00.023Z",
59
+ "system": {
60
+ "os": "darwin 24.3.0",
61
+ "openclaw_version": "2026.2.26"
62
+ },
63
+ "summary": {
64
+ "total_checks": 3,
65
+ "hardened": 1,
66
+ "already_good": 0,
67
+ "skipped": 2,
68
+ "failed": 0
69
+ },
70
+ "items": [
71
+ {
72
+ "check": "exec.ask.off",
73
+ "status": "hardened",
74
+ "before": "off",
75
+ "after": "on-miss",
76
+ "action": "Enable exec approval for unrecognized commands"
77
+ },
78
+ {
79
+ "check": "gateway.host.open",
80
+ "status": "skipped",
81
+ "skipped_reason": "Breaking fix — skipped in auto mode (use --auto --force to include)"
82
+ }
83
+ ]
84
+ }
85
+ ```
86
+
87
+ ## Report Format: Markdown
88
+
89
+ ```markdown
90
+ # ClawArmor Hardening Report
91
+ Generated: 03/07/2026, 23:07
92
+ ClawArmor: v3.4.0 | OS: darwin 24.3.0 | OpenClaw: 2026.2.26
93
+
94
+ ## Summary
95
+ - ✅ 0 checks already good
96
+ - 🔧 1 hardened
97
+ - ⚠️ 2 skipped
98
+
99
+ ## Actions Taken
100
+
101
+ | Check | Before | After | Action |
102
+ |-------|--------|-------|--------|
103
+ | exec.ask.off | off | on-miss | Enable exec approval... |
104
+
105
+ ## Skipped
106
+
107
+ - **gateway.host.open**: Breaking fix — skipped in auto mode
108
+ ```
109
+
110
+ ---
111
+
112
+ ## npm Publish
113
+
114
+ ```
115
+ npm notice name: clawarmor
116
+ npm notice version: 3.4.0
117
+ npm notice total files: 67
118
+ + clawarmor@3.4.0
119
+ ```
120
+
121
+ Published with tag: `latest`
122
+ Registry: https://registry.npmjs.org/
123
+
124
+ ---
125
+
126
+ ## Git
127
+
128
+ - Commit: `d856232`
129
+ - Branch: `main`
130
+ - Pushed: `origin/main`