hackmyagent 0.11.11 → 0.11.13

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.
Files changed (44) hide show
  1. package/README.md +22 -21
  2. package/dist/arp/engine/correlation.d.ts +27 -0
  3. package/dist/arp/engine/correlation.d.ts.map +1 -0
  4. package/dist/arp/engine/correlation.js +95 -0
  5. package/dist/arp/engine/correlation.js.map +1 -0
  6. package/dist/arp/engine/event-engine.d.ts +1 -0
  7. package/dist/arp/engine/event-engine.d.ts.map +1 -1
  8. package/dist/arp/engine/event-engine.js +16 -0
  9. package/dist/arp/engine/event-engine.js.map +1 -1
  10. package/dist/arp/index.d.ts +2 -0
  11. package/dist/arp/index.d.ts.map +1 -1
  12. package/dist/arp/index.js +5 -1
  13. package/dist/arp/index.js.map +1 -1
  14. package/dist/arp/intelligence/anomaly.d.ts +4 -0
  15. package/dist/arp/intelligence/anomaly.d.ts.map +1 -1
  16. package/dist/arp/intelligence/anomaly.js +71 -0
  17. package/dist/arp/intelligence/anomaly.js.map +1 -1
  18. package/dist/arp/intelligence/nanomind-l1.d.ts +72 -0
  19. package/dist/arp/intelligence/nanomind-l1.d.ts.map +1 -0
  20. package/dist/arp/intelligence/nanomind-l1.js +268 -0
  21. package/dist/arp/intelligence/nanomind-l1.js.map +1 -0
  22. package/dist/arp/monitors/network.d.ts +16 -1
  23. package/dist/arp/monitors/network.d.ts.map +1 -1
  24. package/dist/arp/monitors/network.js +55 -1
  25. package/dist/arp/monitors/network.js.map +1 -1
  26. package/dist/arp/proxy/server.d.ts +7 -0
  27. package/dist/arp/proxy/server.d.ts.map +1 -1
  28. package/dist/arp/proxy/server.js +24 -0
  29. package/dist/arp/proxy/server.js.map +1 -1
  30. package/dist/cli.js +30 -8
  31. package/dist/cli.js.map +1 -1
  32. package/dist/hardening/scanner.d.ts +1 -1
  33. package/dist/hardening/scanner.d.ts.map +1 -1
  34. package/dist/hardening/scanner.js +192 -1
  35. package/dist/hardening/scanner.js.map +1 -1
  36. package/dist/index.d.ts +1 -1
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +1 -1
  39. package/dist/index.js.map +1 -1
  40. package/dist/output/asff.d.ts +37 -0
  41. package/dist/output/asff.d.ts.map +1 -0
  42. package/dist/output/asff.js +111 -0
  43. package/dist/output/asff.js.map +1 -0
  44. package/package.json +1 -1
@@ -123,7 +123,7 @@ export declare class HardeningScanner {
123
123
  */
124
124
  private findSkillFiles;
125
125
  /**
126
- * OpenClaw skill security checks (SKILL-001 to SKILL-006)
126
+ * OpenClaw skill security checks (SKILL-001 to SKILL-024)
127
127
  */
128
128
  private checkOpenclawSkills;
129
129
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/hardening/scanner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/hardening/scanner.ts"],"names":[],"mappings":"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"}
@@ -4105,7 +4105,7 @@ dist/
4105
4105
  return skillFiles;
4106
4106
  }
4107
4107
  /**
4108
- * OpenClaw skill security checks (SKILL-001 to SKILL-006)
4108
+ * OpenClaw skill security checks (SKILL-001 to SKILL-024)
4109
4109
  */
4110
4110
  async checkOpenclawSkills(targetDir, autoFix) {
4111
4111
  const findings = [];
@@ -4543,6 +4543,197 @@ dist/
4543
4543
  }
4544
4544
  }
4545
4545
  }
4546
+ // SKILL-020: Missing/invalid frontmatter
4547
+ const fmMatch = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
4548
+ if (!fmMatch) {
4549
+ findings.push({
4550
+ checkId: 'SKILL-020',
4551
+ name: 'Missing YAML Frontmatter',
4552
+ description: 'Skill file lacks required YAML frontmatter for capability declaration',
4553
+ category: 'skill',
4554
+ severity: 'high',
4555
+ passed: false,
4556
+ message: `${relativePath}: Skill file lacks required YAML frontmatter (---). Add frontmatter with name, version, and capabilities fields.`,
4557
+ file: relativePath,
4558
+ fixable: true,
4559
+ fix: 'Add YAML frontmatter block with name, version, and capabilities fields',
4560
+ guidance: 'Skills without frontmatter cannot declare their capabilities, making permission validation impossible. Add a --- delimited YAML block at the top of the file.',
4561
+ });
4562
+ }
4563
+ else {
4564
+ const fmRaw = fmMatch[1];
4565
+ const requiredFields = ['name', 'version', 'capabilities'];
4566
+ const missingFields = requiredFields.filter(f => !new RegExp(`^${f}:`, 'm').test(fmRaw));
4567
+ if (missingFields.length > 0) {
4568
+ findings.push({
4569
+ checkId: 'SKILL-020',
4570
+ name: 'Incomplete Frontmatter',
4571
+ description: 'Skill frontmatter is missing required fields',
4572
+ category: 'skill',
4573
+ severity: 'high',
4574
+ passed: false,
4575
+ message: `${relativePath}: Missing required frontmatter fields: ${missingFields.join(', ')}. These are needed for capability declaration and version tracking.`,
4576
+ file: relativePath,
4577
+ fixable: true,
4578
+ fix: `Add missing fields to frontmatter: ${missingFields.join(', ')}`,
4579
+ guidance: 'Incomplete frontmatter prevents proper capability validation. Every skill should declare name, version, and capabilities.',
4580
+ });
4581
+ }
4582
+ else {
4583
+ findings.push({
4584
+ checkId: 'SKILL-020',
4585
+ name: 'Valid Frontmatter',
4586
+ description: 'Skill file has valid YAML frontmatter with required fields',
4587
+ category: 'skill',
4588
+ severity: 'high',
4589
+ passed: true,
4590
+ message: 'Skill has valid frontmatter with name, version, and capabilities',
4591
+ file: relativePath,
4592
+ fixable: false,
4593
+ fix: 'No action needed',
4594
+ guidance: 'Skill frontmatter is properly configured.',
4595
+ });
4596
+ }
4597
+ }
4598
+ // SKILL-021: Overprivileged permissions (dangerous capability combinations)
4599
+ const dangerousCombos = [
4600
+ {
4601
+ combo: ['filesystem:*', 'network:outbound'],
4602
+ reason: 'filesystem:* + network:outbound enables data exfiltration',
4603
+ },
4604
+ {
4605
+ combo: ['credential:read', 'network:outbound'],
4606
+ reason: 'credential:read + network:outbound enables credential exfiltration',
4607
+ },
4608
+ ];
4609
+ const capPatterns = content.match(/(?:filesystem|network|credential|tool):[a-z*]+/g) || [];
4610
+ const allCaps = [...new Set(capPatterns)];
4611
+ // Also include capabilities from frontmatter if parsed
4612
+ const declaredCapsForPriv = (0, skill_capability_validator_1.parseDeclaredCapabilities)(content);
4613
+ for (const dc of declaredCapsForPriv.capabilities) {
4614
+ if (!allCaps.includes(dc))
4615
+ allCaps.push(dc);
4616
+ }
4617
+ for (const { combo, reason } of dangerousCombos) {
4618
+ const matchCap = (actual, pattern) => {
4619
+ if (actual === pattern)
4620
+ return true;
4621
+ if (pattern.endsWith(':*'))
4622
+ return actual.startsWith(pattern.slice(0, -1));
4623
+ if (actual.endsWith(':*'))
4624
+ return pattern.startsWith(actual.slice(0, -1));
4625
+ return false;
4626
+ };
4627
+ const hasFirst = allCaps.some(c => matchCap(c, combo[0]));
4628
+ const hasSecond = allCaps.some(c => matchCap(c, combo[1]));
4629
+ if (hasFirst && hasSecond) {
4630
+ findings.push({
4631
+ checkId: 'SKILL-021',
4632
+ name: 'Overprivileged Permissions',
4633
+ description: 'Skill has dangerous capability combination that enables exfiltration',
4634
+ category: 'skill',
4635
+ severity: 'high',
4636
+ passed: false,
4637
+ message: `${relativePath}: ${reason}. Restrict filesystem access to specific paths or remove outbound network access.`,
4638
+ file: relativePath,
4639
+ fixable: false,
4640
+ fix: 'Restrict capabilities to minimum required permissions',
4641
+ guidance: 'Dangerous capability combinations can enable data or credential exfiltration. Follow the principle of least privilege.',
4642
+ });
4643
+ }
4644
+ }
4645
+ // SKILL-022: Environment variable exfiltration
4646
+ const envAccessPatterns = [
4647
+ /process\.env/,
4648
+ /os\.environ/,
4649
+ /\$ENV\{/,
4650
+ /System\.getenv/,
4651
+ /printenv/,
4652
+ /\$\(env\)/,
4653
+ /\$\(printenv/,
4654
+ /\$HOME\b/,
4655
+ /\$\{[A-Z_]+\}/,
4656
+ ];
4657
+ const outboundPatterns = [
4658
+ /network:outbound/,
4659
+ /fetch\s*\(/,
4660
+ /https?:\/\//,
4661
+ /XMLHttpRequest/,
4662
+ /\.send\s*\(/,
4663
+ /curl\s/,
4664
+ /wget\s/,
4665
+ ];
4666
+ const hasEnvAccess = envAccessPatterns.some(p => p.test(content));
4667
+ const hasOutbound = outboundPatterns.some(p => p.test(content));
4668
+ if (hasEnvAccess && hasOutbound) {
4669
+ findings.push({
4670
+ checkId: 'SKILL-022',
4671
+ name: 'Environment Variable Exfiltration Risk',
4672
+ description: 'Skill accesses environment variables and has outbound network capability',
4673
+ category: 'skill',
4674
+ severity: 'critical',
4675
+ passed: false,
4676
+ message: `${relativePath}: Skill accesses environment variables AND has outbound network capability. This combination can exfiltrate secrets via network requests.`,
4677
+ file: relativePath,
4678
+ fixable: false,
4679
+ fix: 'Remove outbound network access or environment variable reads',
4680
+ guidance: 'Skills that read environment variables and send data externally can exfiltrate API keys, tokens, and other secrets stored in environment variables.',
4681
+ });
4682
+ }
4683
+ // SKILL-023: Obfuscated code patterns
4684
+ const obfuscationPatterns = [
4685
+ { pattern: /atob\s*\(/, label: 'atob() base64 decode' },
4686
+ { pattern: /Buffer\.from\s*\(/, label: 'Buffer.from() decode' },
4687
+ { pattern: /eval\s*\(/, label: 'eval() dynamic execution' },
4688
+ { pattern: /String\.fromCharCode/, label: 'String.fromCharCode obfuscation' },
4689
+ { pattern: /\\x[0-9a-fA-F]{2}/, label: 'hex-encoded string' },
4690
+ { pattern: /(?:atob|Buffer\.from)\s*\([^)]+\)[\s\S]*?eval\s*\(/, label: 'base64+eval combo' },
4691
+ { pattern: /base64\s+-d/, label: 'shell base64 decode' },
4692
+ { pattern: /eval\s+\$\(/, label: 'shell eval $(...)' },
4693
+ { pattern: /\becho\s+['"][A-Za-z0-9+/=]{20,}['"]\s*\|\s*base64/, label: 'echo+base64 pipe' },
4694
+ { pattern: /new\s+Function\s*\(/, label: 'new Function() dynamic execution' },
4695
+ ];
4696
+ for (const { pattern, label } of obfuscationPatterns) {
4697
+ if (pattern.test(content)) {
4698
+ findings.push({
4699
+ checkId: 'SKILL-023',
4700
+ name: 'Obfuscated Code Pattern',
4701
+ description: 'Skill contains obfuscated code that may hide malicious behavior',
4702
+ category: 'skill',
4703
+ severity: 'high',
4704
+ passed: false,
4705
+ message: `${relativePath}: Detected ${label}. Obfuscated code in skills can hide malicious behavior and should be reviewed.`,
4706
+ file: relativePath,
4707
+ fixable: false,
4708
+ fix: 'Replace obfuscated code with readable equivalent',
4709
+ guidance: 'Obfuscated code (base64 decode, eval, hex-encoded strings) in skills is a strong indicator of hidden malicious behavior. Review and replace with transparent code.',
4710
+ });
4711
+ break; // One finding per file for obfuscation
4712
+ }
4713
+ }
4714
+ // SKILL-024: Unbounded tool chaining
4715
+ const hasToolChain = allCaps.some(c => c.includes('tool:chain'));
4716
+ if (hasToolChain) {
4717
+ const hasFm = !!fmMatch;
4718
+ const fmContent = hasFm ? fmMatch[1] : '';
4719
+ const hasMaxIterations = hasFm && (/maxIterations/i.test(fmContent) ||
4720
+ /iterationLimit/i.test(fmContent));
4721
+ if (!hasMaxIterations) {
4722
+ findings.push({
4723
+ checkId: 'SKILL-024',
4724
+ name: 'Unbounded Tool Chaining',
4725
+ description: 'Skill declares tool:chain capability without iteration limits',
4726
+ category: 'skill',
4727
+ severity: 'medium',
4728
+ passed: false,
4729
+ message: `${relativePath}: Skill declares tool:chain capability without maxIterations or iterationLimit. Unbounded chaining can lead to infinite loops or resource exhaustion.`,
4730
+ file: relativePath,
4731
+ fixable: true,
4732
+ fix: 'Add maxIterations or iterationLimit to skill frontmatter',
4733
+ guidance: 'Tool chaining without iteration limits can cause infinite loops, resource exhaustion, or runaway costs. Set a reasonable maxIterations value in frontmatter.',
4734
+ });
4735
+ }
4736
+ }
4546
4737
  }
4547
4738
  return findings;
4548
4739
  }