@astrasyncai/verification-gateway 3.11.0 → 3.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-interface/interface.d.mts +1 -1
- package/dist/adapter-interface/interface.d.ts +1 -1
- package/dist/adapters/express.js +1 -1
- package/dist/adapters/express.js.map +1 -1
- package/dist/adapters/express.mjs +1 -1
- package/dist/adapters/express.mjs.map +1 -1
- package/dist/adapters/mcp.js +1 -1
- package/dist/adapters/mcp.js.map +1 -1
- package/dist/adapters/mcp.mjs +1 -1
- package/dist/adapters/mcp.mjs.map +1 -1
- package/dist/adapters/nextjs.js +1 -1
- package/dist/adapters/nextjs.js.map +1 -1
- package/dist/adapters/nextjs.mjs +1 -1
- package/dist/adapters/nextjs.mjs.map +1 -1
- package/dist/adapters/sdk.js +1 -1
- package/dist/adapters/sdk.js.map +1 -1
- package/dist/adapters/sdk.mjs +1 -1
- package/dist/adapters/sdk.mjs.map +1 -1
- package/dist/bin/astrasync-claude-hook.js +63 -19
- package/dist/bin/astrasync-codex-hook.js +63 -19
- package/dist/bin/astrasync-guard.js +63 -19
- package/dist/bin/astrasync.js +63 -19
- package/dist/browser/background.js +63 -19
- package/dist/browser/background.js.map +1 -1
- package/dist/browser/background.mjs +63 -19
- package/dist/browser/background.mjs.map +1 -1
- package/dist/browser/browser-adapter.d.mts +1 -1
- package/dist/browser/browser-adapter.d.ts +1 -1
- package/dist/claude-code/claude-code-adapter.d.mts +1 -1
- package/dist/claude-code/claude-code-adapter.d.ts +1 -1
- package/dist/cli/index.d.mts +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/codex/index.d.mts +1 -1
- package/dist/codex/index.d.ts +1 -1
- package/dist/codex/index.js +63 -19
- package/dist/codex/index.js.map +1 -1
- package/dist/codex/index.mjs +63 -19
- package/dist/codex/index.mjs.map +1 -1
- package/dist/cursor/cursor-adapter.d.mts +1 -1
- package/dist/cursor/cursor-adapter.d.ts +1 -1
- package/dist/cursor/extension.d.mts +1 -1
- package/dist/cursor/extension.d.ts +1 -1
- package/dist/cursor/extension.js +63 -19
- package/dist/cursor/extension.js.map +1 -1
- package/dist/cursor/extension.mjs +63 -19
- package/dist/cursor/extension.mjs.map +1 -1
- package/dist/gateway/gateway.d.mts +1 -1
- package/dist/gateway/gateway.d.ts +1 -1
- package/dist/gateway/gateway.js +63 -19
- package/dist/gateway/gateway.js.map +1 -1
- package/dist/gateway/gateway.mjs +63 -19
- package/dist/gateway/gateway.mjs.map +1 -1
- package/dist/git-trigger/git-hooks.d.mts +1 -1
- package/dist/git-trigger/git-hooks.d.ts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/local-evaluator/evaluator.d.mts +10 -1
- package/dist/local-evaluator/evaluator.d.ts +10 -1
- package/dist/local-evaluator/evaluator.js +20 -9
- package/dist/local-evaluator/evaluator.js.map +1 -1
- package/dist/local-evaluator/evaluator.mjs +20 -9
- package/dist/local-evaluator/evaluator.mjs.map +1 -1
- package/dist/{types-C23_uaY8.d.ts → types-DhFPiZ3t.d.ts} +6 -1
- package/dist/{types-BV6abBnF.d.mts → types-cQVCMeNV.d.mts} +6 -1
- package/dist/verify.js +1 -1
- package/dist/verify.js.map +1 -1
- package/dist/verify.mjs +1 -1
- package/dist/verify.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LocalPolicy, P as PDLSSContext, V as VerificationDecision, b as LocalPurposeRule } from '../types-
|
|
1
|
+
import { L as LocalPolicy, P as PDLSSContext, V as VerificationDecision, b as LocalPurposeRule } from '../types-cQVCMeNV.mjs';
|
|
2
2
|
import '../types-sy3tq8lp.mjs';
|
|
3
3
|
import '../metadata-capture.mjs';
|
|
4
4
|
|
|
@@ -46,6 +46,15 @@ declare class LocalEvaluator {
|
|
|
46
46
|
* Supports * (matches any characters including / and spaces) and ? (single char).
|
|
47
47
|
* Uses the same approach as the backend's matchGlobPattern.
|
|
48
48
|
*/
|
|
49
|
+
/**
|
|
50
|
+
* Home directory as a forward-slash prefix for `~` pattern expansion,
|
|
51
|
+
* resolved from the environment WITHOUT importing `os` (this evaluator
|
|
52
|
+
* also runs bundled inside the browser extension, where node builtins
|
|
53
|
+
* don't exist — there `~` patterns stay literal, which is correct since
|
|
54
|
+
* browser targets are URLs). Cached per instance.
|
|
55
|
+
*/
|
|
56
|
+
private cachedHomePrefix;
|
|
57
|
+
private homePrefix;
|
|
49
58
|
private matchGlob;
|
|
50
59
|
}
|
|
51
60
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { L as LocalPolicy, P as PDLSSContext, V as VerificationDecision, b as LocalPurposeRule } from '../types-
|
|
1
|
+
import { L as LocalPolicy, P as PDLSSContext, V as VerificationDecision, b as LocalPurposeRule } from '../types-DhFPiZ3t.js';
|
|
2
2
|
import '../types-9tns4wyl.js';
|
|
3
3
|
import '../metadata-capture.js';
|
|
4
4
|
|
|
@@ -46,6 +46,15 @@ declare class LocalEvaluator {
|
|
|
46
46
|
* Supports * (matches any characters including / and spaces) and ? (single char).
|
|
47
47
|
* Uses the same approach as the backend's matchGlobPattern.
|
|
48
48
|
*/
|
|
49
|
+
/**
|
|
50
|
+
* Home directory as a forward-slash prefix for `~` pattern expansion,
|
|
51
|
+
* resolved from the environment WITHOUT importing `os` (this evaluator
|
|
52
|
+
* also runs bundled inside the browser extension, where node builtins
|
|
53
|
+
* don't exist — there `~` patterns stay literal, which is correct since
|
|
54
|
+
* browser targets are URLs). Cached per instance.
|
|
55
|
+
*/
|
|
56
|
+
private cachedHomePrefix;
|
|
57
|
+
private homePrefix;
|
|
49
58
|
private matchGlob;
|
|
50
59
|
}
|
|
51
60
|
/**
|
|
@@ -148,8 +148,9 @@ function extractBacktickSubstitutions(text, collect) {
|
|
|
148
148
|
return kept.join(" ");
|
|
149
149
|
}
|
|
150
150
|
function basename(token) {
|
|
151
|
-
const slash = token.lastIndexOf("/");
|
|
152
|
-
|
|
151
|
+
const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
|
|
152
|
+
const name = slash === -1 ? token : token.slice(slash + 1);
|
|
153
|
+
return name.replace(/\.(exe|cmd|bat|ps1)$/i, "");
|
|
153
154
|
}
|
|
154
155
|
function matchSegment(segment, list) {
|
|
155
156
|
const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
|
|
@@ -282,7 +283,9 @@ var LocalEvaluator = class {
|
|
|
282
283
|
}
|
|
283
284
|
return {
|
|
284
285
|
recommendation: "ALLOW",
|
|
285
|
-
|
|
286
|
+
// "evaluated": dimensions absent from the policy are skipped, not
|
|
287
|
+
// failed — evaluatedDimensions below records which ones ran.
|
|
288
|
+
reason: "All evaluated PDLSS checks passed",
|
|
286
289
|
evaluatedDimensions: {
|
|
287
290
|
purpose: true,
|
|
288
291
|
scope: !!this.policy.scope,
|
|
@@ -381,7 +384,7 @@ var LocalEvaluator = class {
|
|
|
381
384
|
}
|
|
382
385
|
}
|
|
383
386
|
if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
|
|
384
|
-
const targetLower = context.target.toLowerCase();
|
|
387
|
+
const targetLower = context.target.toLowerCase().replace(/\\/g, "/");
|
|
385
388
|
for (const sensitivePath of SENSITIVE_PATHS) {
|
|
386
389
|
if (targetLower.includes(sensitivePath.toLowerCase())) {
|
|
387
390
|
score = Math.max(score, 50);
|
|
@@ -410,12 +413,20 @@ var LocalEvaluator = class {
|
|
|
410
413
|
matchesAnyPattern(value, patterns) {
|
|
411
414
|
return patterns.some((pattern) => this.matchGlob(value, pattern));
|
|
412
415
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
416
|
+
homePrefix() {
|
|
417
|
+
if (this.cachedHomePrefix !== void 0) return this.cachedHomePrefix;
|
|
418
|
+
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
419
|
+
const home = env?.HOME || env?.USERPROFILE;
|
|
420
|
+
this.cachedHomePrefix = home ? home.replace(/\\/g, "/") : null;
|
|
421
|
+
return this.cachedHomePrefix;
|
|
422
|
+
}
|
|
418
423
|
matchGlob(value, pattern) {
|
|
424
|
+
value = value.replace(/\\/g, "/");
|
|
425
|
+
pattern = pattern.replace(/\\/g, "/");
|
|
426
|
+
if (pattern === "~" || pattern.startsWith("~/")) {
|
|
427
|
+
const home = this.homePrefix();
|
|
428
|
+
if (home) pattern = home + pattern.slice(1);
|
|
429
|
+
}
|
|
419
430
|
if (pattern === value) return true;
|
|
420
431
|
const starCount = (pattern.match(/\*/g) ?? []).length;
|
|
421
432
|
if (starCount > 8) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/local-evaluator/evaluator.ts","../../src/gateway/types.ts","../../src/local-evaluator/command-matcher.ts"],"sourcesContent":["/**\n * Local PDLSS Evaluator\n *\n * Evaluates agent actions against a local PDLSS policy.\n * Same logic as the cloud evaluator but runs in-process with no I/O.\n *\n * Evaluation order:\n * 1. Purpose: find matching rule, check if allowed\n * 2. Purpose: check allowed targets (if specified)\n * 3. Purpose: check blocked patterns (if specified)\n * 4. Scope: check global blocked resources/domains\n * 5. Limits: check transaction/rate limits\n * 6. Risk thresholds + approval requirements\n */\n\nimport type {\n LocalPolicy,\n PDLSSContext,\n VerificationDecision,\n LocalPurposeRule,\n} from '../gateway/types';\nimport { SENSITIVE_PATHS } from '../gateway/types';\nimport { matchHighRiskCommands, isLocalhostOnlyNetworkCommand } from './command-matcher';\n\nexport class LocalEvaluator {\n private policy: LocalPolicy;\n private requestCounts: Map<string, { count: number; windowStart: number }> = new Map();\n\n constructor(policy: LocalPolicy) {\n this.policy = policy;\n }\n\n /**\n * Update the policy (e.g. after hot-reload or sync).\n */\n updatePolicy(policy: LocalPolicy): void {\n this.policy = policy;\n }\n\n /**\n * Evaluate an action context against the loaded policy.\n */\n evaluate(context: PDLSSContext): VerificationDecision {\n // 1. Purpose: find matching rule\n const purposeRule = this.policy.purposes.find((p) => p.id === context.purpose);\n if (!purposeRule) {\n return { recommendation: 'DENY', reason: 'Purpose not in policy' };\n }\n if (!purposeRule.allowed) {\n return { recommendation: 'DENY', reason: 'Purpose explicitly blocked' };\n }\n\n // 2. Purpose: check allowed targets\n if (purposeRule.targets && !this.matchesAnyPattern(context.target, purposeRule.targets)) {\n return { recommendation: 'DENY', reason: 'Target not in allowed list' };\n }\n\n // 3. Purpose: check blocked patterns\n if (\n purposeRule.blockedPatterns &&\n this.matchesAnyPattern(context.target, purposeRule.blockedPatterns)\n ) {\n return { recommendation: 'DENY', reason: 'Target matches blocked pattern' };\n }\n\n // 4. Scope: check global blocked resources/domains\n const scopeBlock = this.checkScopeBlock(context);\n if (scopeBlock) {\n return { recommendation: 'DENY', reason: scopeBlock };\n }\n\n // 5. Limits: check transaction and rate limits\n const limitViolation = this.checkLimits(context);\n if (limitViolation) {\n return { recommendation: 'DENY', reason: limitViolation };\n }\n\n // 6. Self-instantiation: check sub-agent spawning rules\n if (context.purpose === 'sub_agent.spawn' && this.policy.selfInstantiation) {\n if (!this.policy.selfInstantiation.allowed) {\n return { recommendation: 'DENY', reason: 'Sub-agent spawning is not allowed' };\n }\n const depth = (context.metadata?.subAgentDepth as number) || 0;\n if (\n this.policy.selfInstantiation.maxDepth !== undefined &&\n depth >= this.policy.selfInstantiation.maxDepth\n ) {\n return {\n recommendation: 'DENY',\n reason: `Sub-agent depth ${depth} exceeds max depth ${this.policy.selfInstantiation.maxDepth}`,\n };\n }\n }\n\n // 7. Risk thresholds + approval requirements\n if (purposeRule.requiresApproval) {\n return { recommendation: 'MANUAL_REVIEW', reason: 'Purpose requires approval' };\n }\n\n const riskDecision = this.checkRiskThresholds(context);\n if (riskDecision) {\n return riskDecision;\n }\n\n return {\n recommendation: 'ALLOW',\n reason: 'All PDLSS checks passed',\n evaluatedDimensions: {\n purpose: true,\n scope: !!this.policy.scope,\n limits: !!this.policy.limits,\n riskThresholds: !!this.policy.riskThresholds,\n },\n };\n }\n\n private checkScopeBlock(context: PDLSSContext): string | null {\n const scope = this.policy.scope;\n if (!scope) return null;\n\n // Check blocked domains against target and network access\n if (scope.blockedDomains) {\n const targetDomain = this.extractDomain(context.target);\n if (this.matchesAnyPattern(targetDomain, scope.blockedDomains)) {\n return `Target blocked by scope: ${context.target}`;\n }\n if (context.networkAccess) {\n for (const domain of context.networkAccess) {\n if (this.matchesAnyPattern(this.extractDomain(domain), scope.blockedDomains)) {\n return `Domain blocked by scope: ${domain}`;\n }\n }\n }\n }\n\n // Check blocked resources against target\n if (scope.blockedResources && this.matchesAnyPattern(context.target, scope.blockedResources)) {\n return `Resource blocked by scope: ${context.target}`;\n }\n\n // Check allowed domains (if specified, target must match)\n if (scope.allowedDomains && context.networkAccess) {\n for (const domain of context.networkAccess) {\n if (!this.matchesAnyPattern(this.extractDomain(domain), scope.allowedDomains)) {\n return `Domain not in allowed list: ${domain}`;\n }\n }\n }\n\n return null;\n }\n\n private checkLimits(context: PDLSSContext): string | null {\n const limits = this.policy.limits;\n if (!limits) return null;\n\n // Transaction amount check\n if (limits.maxTransactionAmount !== undefined && context.transactionValue !== undefined) {\n if (context.transactionValue > limits.maxTransactionAmount) {\n return `Transaction value ${context.transactionValue} exceeds limit ${limits.maxTransactionAmount}`;\n }\n }\n\n // Rate limit check\n if (limits.maxRequestsPerHour !== undefined) {\n const key = context.purpose;\n const now = Date.now();\n const entry = this.requestCounts.get(key);\n const hourMs = 3600000;\n\n if (!entry || now - entry.windowStart > hourMs) {\n this.requestCounts.set(key, { count: 1, windowStart: now });\n } else {\n entry.count++;\n if (entry.count > limits.maxRequestsPerHour) {\n return `Rate limit exceeded: ${entry.count}/${limits.maxRequestsPerHour} requests per hour`;\n }\n }\n }\n\n return null;\n }\n\n private checkRiskThresholds(context: PDLSSContext): VerificationDecision | null {\n if (!this.policy.riskThresholds) return null;\n\n const riskScore = this.calculateRiskScore(context);\n const thresholds = this.policy.riskThresholds;\n\n if (riskScore >= thresholds.autoBlock.min) {\n return { recommendation: 'DENY', reason: `Risk score ${riskScore} exceeds block threshold` };\n }\n\n if (riskScore >= thresholds.requireApproval.min) {\n return {\n recommendation: 'MANUAL_REVIEW',\n reason: `Risk score ${riskScore} requires approval`,\n };\n }\n\n return null;\n }\n\n private calculateRiskScore(context: PDLSSContext): number {\n let score = 0;\n\n // Explicit risk factors take priority (highest severity wins)\n if (context.riskFactors?.length) {\n const severityScores: Record<string, number> = {\n low: 10,\n medium: 40,\n high: 70,\n critical: 90,\n };\n\n for (const factor of context.riskFactors) {\n const factorScore = severityScores[factor.severity] || 0;\n if (factorScore > score) score = factorScore;\n }\n return score;\n }\n\n // Auto-detect risk from high-risk shell commands (command-position match,\n // not substring). curl/wget aimed exclusively at localhost stay below the\n // review threshold — dev-loop probes, not exfiltration surface.\n if (context.purpose === 'shell.exec' && context.target) {\n const matched = matchHighRiskCommands(context.target);\n const localProbeOnly =\n matched.length > 0 &&\n matched.every((cmd) => cmd === 'curl' || cmd === 'wget') &&\n isLocalhostOnlyNetworkCommand(context.target);\n if (matched.length > 0 && !localProbeOnly) {\n score = Math.max(score, 80);\n }\n }\n\n // Auto-detect risk from sensitive file paths (score 50 = review range)\n if (\n (context.purpose === 'file.read' ||\n context.purpose === 'file.write' ||\n context.purpose === 'file.delete') &&\n context.target\n ) {\n const targetLower = context.target.toLowerCase();\n for (const sensitivePath of SENSITIVE_PATHS) {\n if (targetLower.includes(sensitivePath.toLowerCase())) {\n score = Math.max(score, 50);\n break;\n }\n }\n }\n\n return score;\n }\n\n /**\n * Extract domain from a URL-like string (strips protocol and path).\n */\n private extractDomain(value: string): string {\n let domain = value;\n // Strip protocol\n const protoIndex = domain.indexOf('://');\n if (protoIndex !== -1) domain = domain.slice(protoIndex + 3);\n // Strip path\n const slashIndex = domain.indexOf('/');\n if (slashIndex !== -1) domain = domain.slice(0, slashIndex);\n // Strip port\n const colonIndex = domain.indexOf(':');\n if (colonIndex !== -1) domain = domain.slice(0, colonIndex);\n return domain;\n }\n\n /**\n * Check if a value matches any of the given glob patterns.\n */\n private matchesAnyPattern(value: string, patterns: string[]): boolean {\n return patterns.some((pattern) => this.matchGlob(value, pattern));\n }\n\n /**\n * Simple glob pattern matching.\n * Supports * (matches any characters including / and spaces) and ? (single char).\n * Uses the same approach as the backend's matchGlobPattern.\n */\n private matchGlob(value: string, pattern: string): boolean {\n // Exact match\n if (pattern === value) return true;\n\n // Audit F-A1-13: ReDoS guard. Patterns with too many `*` segments\n // produce catastrophic-backtracking regexes (e.g. `a*a*a*a*a*a*a*a*b`\n // against `aaaaaaaa` runs in exponential time). Local policies are\n // operator-controlled so attacker access requires operator-side\n // compromise, but a copy-pasted-from-internet example pattern could\n // still DoS the gateway when matched against request input.\n const starCount = (pattern.match(/\\*/g) ?? []).length;\n if (starCount > 8) {\n return false;\n }\n\n // Convert glob to regex\n const regexStr = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special regex chars (except * and ?)\n .replace(/\\*/g, '.*') // * matches anything\n .replace(/\\?/g, '.'); // ? matches single char\n\n try {\n return new RegExp(`^${regexStr}$`, 'i').test(value);\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Convenience: find a matching purpose rule.\n */\nexport function findPurposeRule(\n policy: LocalPolicy,\n purposeId: string\n): LocalPurposeRule | undefined {\n return policy.purposes.find((p) => p.id === purposeId);\n}\n","/**\n * AstraSync Gateway - Types for gateway modes, local evaluation, and adapter interface.\n */\n\n// ========================================================================\n// Gateway Configuration\n// ========================================================================\n\nexport type GatewayMode = 'online' | 'local' | 'hybrid';\n\n/**\n * Posture controls whether the gateway actively blocks or just monitors.\n * - active: Evaluate and enforce decisions (block/allow/review)\n * - passive: Evaluate and log but never block (telemetry-only mode)\n */\nexport type GatewayPosture = 'active' | 'passive';\n\nexport interface AstraSyncGatewayConfig {\n mode: GatewayMode;\n /** Enforcement posture: 'active' blocks actions, 'passive' logs only (default: 'active') */\n posture?: GatewayPosture;\n /** AstraSync API base URL (required for online/hybrid modes) */\n apiBaseUrl?: string;\n /** API key for authenticating with AstraSync (required for online/hybrid modes) */\n apiKey?: string;\n /**\n * ASTRA- agent id this gateway acts as. Attached to hybrid sync entries and\n * used as the default identity for online verification when the caller\n * passes no per-call astraId.\n */\n agentId?: string;\n /** Path to local PDLSS policy YAML file (required for local/hybrid modes) */\n policyFile?: string;\n /** Inline policy object (alternative to policyFile) */\n policy?: LocalPolicy;\n /** Sync interval in seconds for hybrid mode (default: 3600) */\n syncInterval?: number;\n /** Cache verification results TTL in seconds (default: 300) */\n cacheTtl?: number;\n /** Enable debug logging */\n debug?: boolean;\n /** Enable trace logging to .astrasync/traces/ (default: false) */\n traceEnabled?: boolean;\n /** Trace log directory (default: .astrasync/traces/) */\n tracePath?: string;\n /** Default access level for unverified requests */\n defaultAccessLevel?: import('../types').AccessLevel;\n /** Minimum trust score for standard access (online/hybrid) */\n minTrustScore?: number;\n /** Minimum trust score for full access (online/hybrid) */\n minTrustScoreForFull?: number;\n /** Custom headers to send with API requests */\n customHeaders?: Record<string, string>;\n /** Counterparty URL for analytics */\n counterpartyUrl?: string;\n /** Counterparty type for analytics */\n counterpartyType?: import('../types').CounterpartyType;\n}\n\n// ========================================================================\n// PDLSS Context (Agent-side action context)\n// ========================================================================\n\nexport interface PDLSSContext {\n /** Purpose category (e.g. email.send, shell.exec, file.read) */\n purpose: string;\n /** Specific action within purpose */\n action: string;\n /** Target resource, recipient, or counterparty */\n target: string;\n /** Types of data access (read, write, delete) */\n dataAccess?: string[];\n /** Network domains/IPs being accessed */\n networkAccess?: string[];\n /** Resource type (customer, order, file, directory, process) */\n resourceType?: string;\n /** Risk factors for this action */\n riskFactors?: RiskFactor[];\n /** Transaction value (if financial) */\n transactionValue?: number;\n /** Currency for transaction */\n currency?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface RiskFactor {\n type: 'financial' | 'data_sensitivity' | 'privilege_escalation' | 'network_scope' | 'destructive';\n severity: 'low' | 'medium' | 'high' | 'critical';\n detail: string;\n}\n\n// ========================================================================\n// Verification Decision\n// ========================================================================\n\nexport interface VerificationDecision {\n recommendation: 'ALLOW' | 'DENY' | 'MANUAL_REVIEW';\n reason: string;\n trustScore?: number;\n tokenGuidance?: import('../types').TokenGuidance;\n sessionId?: string;\n /** PDLSS dimensions that were evaluated */\n evaluatedDimensions?: {\n purpose: boolean;\n scope: boolean;\n limits: boolean;\n riskThresholds: boolean;\n };\n}\n\n// ========================================================================\n// Local Policy Types (YAML format)\n// ========================================================================\n\nexport interface LocalPolicy {\n version: string;\n name: string;\n description?: string;\n purposes: LocalPurposeRule[];\n scope?: LocalScope;\n limits?: LocalLimits;\n riskThresholds?: LocalRiskThresholds;\n selfInstantiation?: LocalSelfInstantiation;\n}\n\nexport interface LocalPurposeRule {\n id: string;\n allowed: boolean;\n targets?: string[];\n blockedPatterns?: string[];\n requiresApproval?: boolean;\n}\n\nexport interface LocalScope {\n allowedDomains?: string[];\n blockedDomains?: string[];\n blockedResources?: string[];\n}\n\nexport interface LocalLimits {\n maxTransactionAmount?: number;\n maxRequestsPerHour?: number;\n currency?: string;\n}\n\nexport interface LocalRiskThresholds {\n autoAllow: { min: number; max: number };\n requireApproval: { min: number; max: number };\n autoBlock: { min: number; max: number };\n}\n\nexport interface LocalSelfInstantiation {\n /** Whether sub-agent spawning is allowed */\n allowed: boolean;\n /** Maximum depth of sub-agent chain */\n maxDepth?: number;\n}\n\n// ========================================================================\n// Risk Scoring Defaults (cherry-picked from trust-harness-core)\n// ========================================================================\n\n/** Base risk scores per action category */\nexport const BASE_RISK_SCORES: Record<string, number> = {\n 'file.read': 10,\n 'file.write': 40,\n 'file.delete': 70,\n 'shell.exec': 50,\n 'network.fetch': 60,\n 'network.request': 60,\n 'email.send': 45,\n 'email.read': 15,\n 'calendar.create': 20,\n 'calendar.modify': 30,\n 'database.query': 25,\n 'database.write': 55,\n 'payment.execute': 80,\n 'sub_agent.spawn': 65,\n 'code.execute': 45,\n};\n\n/** Shell commands that significantly increase risk score */\nexport const HIGH_RISK_COMMANDS = [\n 'rm',\n 'rmdir',\n 'dd',\n 'mkfs',\n 'chmod',\n 'chown',\n 'sudo',\n 'su',\n 'curl',\n 'wget',\n 'nc',\n 'netcat',\n 'ssh',\n 'scp',\n 'rsync',\n 'git push',\n 'npm publish',\n 'docker',\n 'kubectl',\n 'gh pr create',\n 'gh pr merge',\n];\n\n/** File paths that indicate sensitive data access */\nexport const SENSITIVE_PATHS = [\n '.ssh',\n '.aws',\n '.gnupg',\n '.env',\n 'credentials',\n 'secrets',\n 'password',\n '.git/config',\n '/etc',\n '/var',\n '/root',\n 'id_rsa',\n '.npmrc',\n '.pypirc',\n];\n\n// ========================================================================\n// Trace Event Types\n// ========================================================================\n\nexport interface TraceEvent {\n id: string;\n timestamp: Date;\n /**\n * 'skipped' records a tool call the guard saw but did NOT govern\n * (unmapped tool, MCP with governMcp off, skipTools). Added 10 Jul 2026\n * after an ungoverned-PowerShell gap hid behind silent skips — coverage\n * holes must be visible in passive observation.\n */\n type: 'evaluation' | 'decision' | 'error' | 'mode_switch' | 'skipped';\n context?: PDLSSContext;\n decision?: VerificationDecision;\n metadata?: Record<string, unknown>;\n}\n\n// ========================================================================\n// Adapter Interface Types\n// ========================================================================\n\nexport interface AdapterConfig {\n /** The gateway instance (handles mode routing) */\n gateway: unknown; // Typed as AstraSyncGateway at usage site to avoid circular deps\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface AgentAction {\n /** Raw action data from the platform */\n raw: unknown;\n /** Platform identifier (e.g. 'openclaw-cli', 'cursor', 'browser') */\n platform: string;\n /** Timestamp of the action */\n timestamp: Date;\n}\n\nexport interface InterceptResult {\n /** Whether the action was intercepted */\n intercepted: boolean;\n /** Extracted PDLSS context (if intercepted) */\n context?: PDLSSContext;\n /** Reason for not intercepting (if not intercepted) */\n skipReason?: string;\n}\n\n// ========================================================================\n// Sync Queue Types (Hybrid mode)\n// ========================================================================\n\nexport interface SyncQueueEntry {\n id: string;\n context: PDLSSContext;\n decision: VerificationDecision;\n timestamp: Date;\n retryCount: number;\n status: 'pending' | 'synced' | 'failed';\n}\n","/**\n * Command-position matching for high-risk shell commands.\n *\n * Replaces the substring-based HIGH_RISK_COMMANDS check that flagged any\n * command merely *containing* a high-risk name (` su` matched \"superadmin\",\n * quoted text and heredoc bodies triggered). A high-risk entry now matches\n * only when it is the invoked command of some pipeline/list segment.\n *\n * Deliberately NOT a shell parser — pure regex/token splitting inside the\n * hook's latency budget. Known accepted misses: option arguments that survive\n * flag filtering (`git -C /tmp push`), exotic quoting/escapes.\n */\n\nimport { HIGH_RISK_COMMANDS } from '../gateway/types';\n\n/** Wrapper commands that prefix the real command; hop over them and re-check. */\nconst WRAPPERS = new Set([\n 'sudo',\n 'doas',\n 'env',\n 'nohup',\n 'time',\n 'timeout',\n 'nice',\n 'ionice',\n 'stdbuf',\n 'xargs',\n 'command',\n 'exec',\n 'builtin',\n]);\n\n/** Leading environment assignment, e.g. `FOO=bar cmd` (input is lowercased). */\nconst ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;\n\nconst MAX_WRAPPER_HOPS = 4;\nconst MAX_RECURSION_DEPTH = 3;\nconst MAX_COMMAND_LENGTH = 100_000;\n\n/** Hosts considered local for the curl/wget risk downgrade (brackets pre-stripped). */\nconst LOCAL_HOST_RE = /^(localhost|127\\.0\\.0\\.1|::1|[a-z0-9.-]*\\.localhost)$/;\n\ninterface RiskList {\n singleWord: Set<string>;\n /** Sorted longest-first so `gh pr create` wins over a shorter prefix entry. */\n multiWord: string[][];\n}\n\nfunction buildRiskList(entries: readonly string[]): RiskList {\n const singleWord = new Set<string>();\n const multiWord: string[][] = [];\n for (const entry of entries) {\n const words = entry.toLowerCase().trim().split(/\\s+/);\n if (words.length === 1) singleWord.add(words[0]);\n else multiWord.push(words);\n }\n multiWord.sort((a, b) => b.length - a.length);\n return { singleWord, multiWord };\n}\n\n/** Drop heredoc bodies (`<<TAG` … `TAG`) — stdin text, not executed commands. */\nfunction stripHeredocBodies(text: string): string {\n const lines = text.split('\\n');\n const kept: string[] = [];\n let terminator: string | null = null;\n for (const line of lines) {\n if (terminator !== null) {\n if (line.trim() === terminator) terminator = null;\n continue;\n }\n kept.push(line);\n const heredoc = line.match(/<<-?\\s*['\"]?(\\w+)['\"]?/);\n if (heredoc) terminator = heredoc[1];\n }\n return kept.join('\\n');\n}\n\n/**\n * Pull out `$(…)` command substitutions (recursing into their contents via\n * `collect`) and blank their spans. No quote awareness inside the parens —\n * pragmatic depth counting only.\n */\nfunction extractDollarSubstitutions(text: string, collect: (inner: string) => void): string {\n let result = text;\n let start = result.indexOf('$(');\n let iterations = 0;\n while (start !== -1 && iterations < 50) {\n iterations++;\n let depth = 0;\n let end = result.length;\n for (let i = start + 1; i < result.length; i++) {\n if (result[i] === '(') depth++;\n else if (result[i] === ')') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n collect(result.slice(start + 2, end));\n result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;\n start = result.indexOf('$(');\n }\n return result;\n}\n\n/** Pull out backtick substitutions (odd chunks when split on `) and blank them. */\nfunction extractBacktickSubstitutions(text: string, collect: (inner: string) => void): string {\n if (!text.includes('`')) return text;\n const chunks = text.split('`');\n const kept: string[] = [];\n for (let i = 0; i < chunks.length; i++) {\n if (i % 2 === 1) collect(chunks[i]);\n else kept.push(chunks[i]);\n }\n return kept.join(' ');\n}\n\nfunction basename(token: string): string {\n const slash = token.lastIndexOf('/');\n return slash === -1 ? token : token.slice(slash + 1);\n}\n\nfunction matchSegment(segment: string, list: RiskList): string | null {\n const tokens = segment.split(/\\s+/).filter((t) => /[a-z0-9]/.test(t));\n let i = 0;\n let hops = 0;\n while (i < tokens.length) {\n // Skip `export` and leading env assignments to reach the command position.\n while (i < tokens.length && (tokens[i] === 'export' || ASSIGN_RE.test(tokens[i]))) i++;\n if (i >= tokens.length) return null;\n\n const cmd = basename(tokens[i]);\n\n // Multi-word entries match against the leading non-flag tokens.\n const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith('-'))];\n for (const entry of list.multiWord) {\n if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {\n return entry.join(' ');\n }\n }\n if (list.singleWord.has(cmd)) return cmd;\n\n // High-risk check ran first, so `sudo …` matches `sudo` before hopping.\n if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {\n hops++;\n i++;\n while (i < tokens.length && (tokens[i].startsWith('-') || ASSIGN_RE.test(tokens[i]))) i++;\n continue;\n }\n return null;\n }\n return null;\n}\n\nfunction collectMatches(\n command: string,\n list: RiskList,\n matches: Set<string>,\n depth: number\n): void {\n if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;\n\n let text = command.toLowerCase();\n text = stripHeredocBodies(text);\n\n // Substitutions are extracted (and their contents checked) BEFORE quote\n // stripping so `\"$(curl x)\"` is still evaluated in command position.\n const recurse = (inner: string) => collectMatches(inner, list, matches, depth + 1);\n text = extractDollarSubstitutions(text, recurse);\n text = extractBacktickSubstitutions(text, recurse);\n\n // Quoted spans are data, not commands; `#` comments are noise.\n text = text.replace(/'[^']*'/g, ' ').replace(/\"[^\"]*\"/g, ' ');\n text = text.replace(/(^|\\s)#[^\\n]*/g, ' ');\n\n const segments = text.split(/\\|\\||&&|[;|&\\n]|[({]/);\n for (const segment of segments) {\n const matched = matchSegment(segment, list);\n if (matched) matches.add(matched);\n }\n}\n\n/**\n * Returns every high-risk entry invoked in command position anywhere in\n * `command` (deduplicated), or an empty array.\n */\nexport function matchHighRiskCommands(\n command: string,\n highRiskCommands: readonly string[] = HIGH_RISK_COMMANDS\n): string[] {\n if (!command) return [];\n const matches = new Set<string>();\n collectMatches(command, buildRiskList(highRiskCommands), matches, 0);\n return [...matches];\n}\n\n/**\n * Returns the first high-risk entry invoked in command position, or null.\n * Convenience wrapper over {@link matchHighRiskCommands}.\n */\nexport function matchHighRiskCommand(\n command: string,\n highRiskCommands: readonly string[] = HIGH_RISK_COMMANDS\n): string | null {\n return matchHighRiskCommands(command, highRiskCommands)[0] ?? null;\n}\n\n/**\n * True when the command's network destinations are exclusively local\n * (localhost / 127.0.0.1 / ::1 / *.localhost) AND at least one destination is\n * present. Used to skip the risk-80 bump for curl/wget dev-loop probes.\n * Unparseable or absent destinations keep the bump (fail toward review).\n */\nexport function isLocalhostOnlyNetworkCommand(command: string): boolean {\n const text = command.toLowerCase();\n // Candidate destinations: explicit URLs, host:port tokens, or bare local hosts.\n const tokens = text.split(/\\s+/).map((t) => t.replace(/^[\"'`]+|[\"'`,;)]+$/g, ''));\n let localCount = 0;\n for (const token of tokens) {\n let host: string | null = null;\n if (token.includes('://')) {\n let rest = token.slice(token.indexOf('://') + 3);\n const at = rest.indexOf('@');\n if (at !== -1) rest = rest.slice(at + 1);\n host = rest.split(/[/?#]/)[0];\n } else if (/^[a-z0-9.[\\]:-]+:\\d+(\\/.*)?$/.test(token)) {\n host = token.split('/')[0];\n } else if (/^(localhost|127\\.0\\.0\\.1|\\[?::1\\]?)(\\/.*)?$/.test(token)) {\n host = token.split('/')[0];\n }\n if (host === null) continue;\n // Strip a trailing numeric port, then IPv6 brackets ([::1]:3000 → ::1).\n const bare = host.replace(/:\\d+$/, '').replace(/^\\[|\\]$/g, '');\n if (LOCAL_HOST_RE.test(bare)) {\n localCount++;\n } else {\n return false;\n }\n }\n return localCount > 0;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/local-evaluator/evaluator.ts","../../src/gateway/types.ts","../../src/local-evaluator/command-matcher.ts"],"sourcesContent":["/**\n * Local PDLSS Evaluator\n *\n * Evaluates agent actions against a local PDLSS policy.\n * Same logic as the cloud evaluator but runs in-process with no I/O.\n *\n * Evaluation order:\n * 1. Purpose: find matching rule, check if allowed\n * 2. Purpose: check allowed targets (if specified)\n * 3. Purpose: check blocked patterns (if specified)\n * 4. Scope: check global blocked resources/domains\n * 5. Limits: check transaction/rate limits\n * 6. Risk thresholds + approval requirements\n */\n\nimport type {\n LocalPolicy,\n PDLSSContext,\n VerificationDecision,\n LocalPurposeRule,\n} from '../gateway/types';\nimport { SENSITIVE_PATHS } from '../gateway/types';\nimport { matchHighRiskCommands, isLocalhostOnlyNetworkCommand } from './command-matcher';\n\nexport class LocalEvaluator {\n private policy: LocalPolicy;\n private requestCounts: Map<string, { count: number; windowStart: number }> = new Map();\n\n constructor(policy: LocalPolicy) {\n this.policy = policy;\n }\n\n /**\n * Update the policy (e.g. after hot-reload or sync).\n */\n updatePolicy(policy: LocalPolicy): void {\n this.policy = policy;\n }\n\n /**\n * Evaluate an action context against the loaded policy.\n */\n evaluate(context: PDLSSContext): VerificationDecision {\n // 1. Purpose: find matching rule\n const purposeRule = this.policy.purposes.find((p) => p.id === context.purpose);\n if (!purposeRule) {\n return { recommendation: 'DENY', reason: 'Purpose not in policy' };\n }\n if (!purposeRule.allowed) {\n return { recommendation: 'DENY', reason: 'Purpose explicitly blocked' };\n }\n\n // 2. Purpose: check allowed targets\n if (purposeRule.targets && !this.matchesAnyPattern(context.target, purposeRule.targets)) {\n return { recommendation: 'DENY', reason: 'Target not in allowed list' };\n }\n\n // 3. Purpose: check blocked patterns\n if (\n purposeRule.blockedPatterns &&\n this.matchesAnyPattern(context.target, purposeRule.blockedPatterns)\n ) {\n return { recommendation: 'DENY', reason: 'Target matches blocked pattern' };\n }\n\n // 4. Scope: check global blocked resources/domains\n const scopeBlock = this.checkScopeBlock(context);\n if (scopeBlock) {\n return { recommendation: 'DENY', reason: scopeBlock };\n }\n\n // 5. Limits: check transaction and rate limits\n const limitViolation = this.checkLimits(context);\n if (limitViolation) {\n return { recommendation: 'DENY', reason: limitViolation };\n }\n\n // 6. Self-instantiation: check sub-agent spawning rules\n if (context.purpose === 'sub_agent.spawn' && this.policy.selfInstantiation) {\n if (!this.policy.selfInstantiation.allowed) {\n return { recommendation: 'DENY', reason: 'Sub-agent spawning is not allowed' };\n }\n const depth = (context.metadata?.subAgentDepth as number) || 0;\n if (\n this.policy.selfInstantiation.maxDepth !== undefined &&\n depth >= this.policy.selfInstantiation.maxDepth\n ) {\n return {\n recommendation: 'DENY',\n reason: `Sub-agent depth ${depth} exceeds max depth ${this.policy.selfInstantiation.maxDepth}`,\n };\n }\n }\n\n // 7. Risk thresholds + approval requirements\n if (purposeRule.requiresApproval) {\n return { recommendation: 'MANUAL_REVIEW', reason: 'Purpose requires approval' };\n }\n\n const riskDecision = this.checkRiskThresholds(context);\n if (riskDecision) {\n return riskDecision;\n }\n\n return {\n recommendation: 'ALLOW',\n // \"evaluated\": dimensions absent from the policy are skipped, not\n // failed — evaluatedDimensions below records which ones ran.\n reason: 'All evaluated PDLSS checks passed',\n evaluatedDimensions: {\n purpose: true,\n scope: !!this.policy.scope,\n limits: !!this.policy.limits,\n riskThresholds: !!this.policy.riskThresholds,\n },\n };\n }\n\n private checkScopeBlock(context: PDLSSContext): string | null {\n const scope = this.policy.scope;\n if (!scope) return null;\n\n // Check blocked domains against target and network access\n if (scope.blockedDomains) {\n const targetDomain = this.extractDomain(context.target);\n if (this.matchesAnyPattern(targetDomain, scope.blockedDomains)) {\n return `Target blocked by scope: ${context.target}`;\n }\n if (context.networkAccess) {\n for (const domain of context.networkAccess) {\n if (this.matchesAnyPattern(this.extractDomain(domain), scope.blockedDomains)) {\n return `Domain blocked by scope: ${domain}`;\n }\n }\n }\n }\n\n // Check blocked resources against target\n if (scope.blockedResources && this.matchesAnyPattern(context.target, scope.blockedResources)) {\n return `Resource blocked by scope: ${context.target}`;\n }\n\n // Check allowed domains (if specified, target must match)\n if (scope.allowedDomains && context.networkAccess) {\n for (const domain of context.networkAccess) {\n if (!this.matchesAnyPattern(this.extractDomain(domain), scope.allowedDomains)) {\n return `Domain not in allowed list: ${domain}`;\n }\n }\n }\n\n return null;\n }\n\n private checkLimits(context: PDLSSContext): string | null {\n const limits = this.policy.limits;\n if (!limits) return null;\n\n // Transaction amount check\n if (limits.maxTransactionAmount !== undefined && context.transactionValue !== undefined) {\n if (context.transactionValue > limits.maxTransactionAmount) {\n return `Transaction value ${context.transactionValue} exceeds limit ${limits.maxTransactionAmount}`;\n }\n }\n\n // Rate limit check\n if (limits.maxRequestsPerHour !== undefined) {\n const key = context.purpose;\n const now = Date.now();\n const entry = this.requestCounts.get(key);\n const hourMs = 3600000;\n\n if (!entry || now - entry.windowStart > hourMs) {\n this.requestCounts.set(key, { count: 1, windowStart: now });\n } else {\n entry.count++;\n if (entry.count > limits.maxRequestsPerHour) {\n return `Rate limit exceeded: ${entry.count}/${limits.maxRequestsPerHour} requests per hour`;\n }\n }\n }\n\n return null;\n }\n\n private checkRiskThresholds(context: PDLSSContext): VerificationDecision | null {\n if (!this.policy.riskThresholds) return null;\n\n const riskScore = this.calculateRiskScore(context);\n const thresholds = this.policy.riskThresholds;\n\n if (riskScore >= thresholds.autoBlock.min) {\n return { recommendation: 'DENY', reason: `Risk score ${riskScore} exceeds block threshold` };\n }\n\n if (riskScore >= thresholds.requireApproval.min) {\n return {\n recommendation: 'MANUAL_REVIEW',\n reason: `Risk score ${riskScore} requires approval`,\n };\n }\n\n return null;\n }\n\n private calculateRiskScore(context: PDLSSContext): number {\n let score = 0;\n\n // Explicit risk factors take priority (highest severity wins)\n if (context.riskFactors?.length) {\n const severityScores: Record<string, number> = {\n low: 10,\n medium: 40,\n high: 70,\n critical: 90,\n };\n\n for (const factor of context.riskFactors) {\n const factorScore = severityScores[factor.severity] || 0;\n if (factorScore > score) score = factorScore;\n }\n return score;\n }\n\n // Auto-detect risk from high-risk shell commands (command-position match,\n // not substring). curl/wget aimed exclusively at localhost stay below the\n // review threshold — dev-loop probes, not exfiltration surface.\n if (context.purpose === 'shell.exec' && context.target) {\n const matched = matchHighRiskCommands(context.target);\n const localProbeOnly =\n matched.length > 0 &&\n matched.every((cmd) => cmd === 'curl' || cmd === 'wget') &&\n isLocalhostOnlyNetworkCommand(context.target);\n if (matched.length > 0 && !localProbeOnly) {\n score = Math.max(score, 80);\n }\n }\n\n // Auto-detect risk from sensitive file paths (score 50 = review range).\n // Separators normalized so entries containing '/' (e.g. '.git/config')\n // match Windows backslash targets — same class as the matchGlob fix.\n if (\n (context.purpose === 'file.read' ||\n context.purpose === 'file.write' ||\n context.purpose === 'file.delete') &&\n context.target\n ) {\n const targetLower = context.target.toLowerCase().replace(/\\\\/g, '/');\n for (const sensitivePath of SENSITIVE_PATHS) {\n if (targetLower.includes(sensitivePath.toLowerCase())) {\n score = Math.max(score, 50);\n break;\n }\n }\n }\n\n return score;\n }\n\n /**\n * Extract domain from a URL-like string (strips protocol and path).\n */\n private extractDomain(value: string): string {\n let domain = value;\n // Strip protocol\n const protoIndex = domain.indexOf('://');\n if (protoIndex !== -1) domain = domain.slice(protoIndex + 3);\n // Strip path\n const slashIndex = domain.indexOf('/');\n if (slashIndex !== -1) domain = domain.slice(0, slashIndex);\n // Strip port\n const colonIndex = domain.indexOf(':');\n if (colonIndex !== -1) domain = domain.slice(0, colonIndex);\n return domain;\n }\n\n /**\n * Check if a value matches any of the given glob patterns.\n */\n private matchesAnyPattern(value: string, patterns: string[]): boolean {\n return patterns.some((pattern) => this.matchGlob(value, pattern));\n }\n\n /**\n * Simple glob pattern matching.\n * Supports * (matches any characters including / and spaces) and ? (single char).\n * Uses the same approach as the backend's matchGlobPattern.\n */\n /**\n * Home directory as a forward-slash prefix for `~` pattern expansion,\n * resolved from the environment WITHOUT importing `os` (this evaluator\n * also runs bundled inside the browser extension, where node builtins\n * don't exist — there `~` patterns stay literal, which is correct since\n * browser targets are URLs). Cached per instance.\n */\n private cachedHomePrefix: string | null | undefined;\n\n private homePrefix(): string | null {\n if (this.cachedHomePrefix !== undefined) return this.cachedHomePrefix;\n const env = typeof process !== 'undefined' ? process.env : undefined;\n const home = env?.HOME || env?.USERPROFILE;\n this.cachedHomePrefix = home ? home.replace(/\\\\/g, '/') : null;\n return this.cachedHomePrefix;\n }\n\n private matchGlob(value: string, pattern: string): boolean {\n // Two live bypasses found 11 Jul 2026 (Windows desktop verification):\n // 1. Windows targets arrive with backslashes (C:\\Users\\...\\settings.json)\n // and never matched forward-slash glob patterns — EVERY file-path\n // protection was dead on native Windows. Normalize separators on\n // both sides so Windows-authored patterns keep working too.\n // 2. `~/...` patterns were never expanded, so the default policy's\n // credential-path protections (~/.ssh/*, ~/.aws/*) matched nothing\n // on ANY platform. Expand a leading `~` to the home directory.\n value = value.replace(/\\\\/g, '/');\n pattern = pattern.replace(/\\\\/g, '/');\n if (pattern === '~' || pattern.startsWith('~/')) {\n const home = this.homePrefix();\n if (home) pattern = home + pattern.slice(1);\n }\n\n // Exact match\n if (pattern === value) return true;\n\n // Audit F-A1-13: ReDoS guard. Patterns with too many `*` segments\n // produce catastrophic-backtracking regexes (e.g. `a*a*a*a*a*a*a*a*b`\n // against `aaaaaaaa` runs in exponential time). Local policies are\n // operator-controlled so attacker access requires operator-side\n // compromise, but a copy-pasted-from-internet example pattern could\n // still DoS the gateway when matched against request input.\n const starCount = (pattern.match(/\\*/g) ?? []).length;\n if (starCount > 8) {\n return false;\n }\n\n // Convert glob to regex\n const regexStr = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special regex chars (except * and ?)\n .replace(/\\*/g, '.*') // * matches anything\n .replace(/\\?/g, '.'); // ? matches single char\n\n try {\n return new RegExp(`^${regexStr}$`, 'i').test(value);\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Convenience: find a matching purpose rule.\n */\nexport function findPurposeRule(\n policy: LocalPolicy,\n purposeId: string\n): LocalPurposeRule | undefined {\n return policy.purposes.find((p) => p.id === purposeId);\n}\n","/**\n * AstraSync Gateway - Types for gateway modes, local evaluation, and adapter interface.\n */\n\n// ========================================================================\n// Gateway Configuration\n// ========================================================================\n\nexport type GatewayMode = 'online' | 'local' | 'hybrid';\n\n/**\n * Posture controls whether the gateway actively blocks or just monitors.\n * - active: Evaluate and enforce decisions (block/allow/review)\n * - passive: Evaluate and log but never block (telemetry-only mode)\n */\nexport type GatewayPosture = 'active' | 'passive';\n\nexport interface AstraSyncGatewayConfig {\n mode: GatewayMode;\n /** Enforcement posture: 'active' blocks actions, 'passive' logs only (default: 'active') */\n posture?: GatewayPosture;\n /** AstraSync API base URL (required for online/hybrid modes) */\n apiBaseUrl?: string;\n /** API key for authenticating with AstraSync (required for online/hybrid modes) */\n apiKey?: string;\n /**\n * ASTRA- agent id this gateway acts as. Attached to hybrid sync entries and\n * used as the default identity for online verification when the caller\n * passes no per-call astraId.\n */\n agentId?: string;\n /** Path to local PDLSS policy YAML file (required for local/hybrid modes) */\n policyFile?: string;\n /** Inline policy object (alternative to policyFile) */\n policy?: LocalPolicy;\n /** Sync interval in seconds for hybrid mode (default: 3600) */\n syncInterval?: number;\n /** Cache verification results TTL in seconds (default: 300) */\n cacheTtl?: number;\n /** Enable debug logging */\n debug?: boolean;\n /** Enable trace logging to .astrasync/traces/ (default: false) */\n traceEnabled?: boolean;\n /** Trace log directory (default: .astrasync/traces/) */\n tracePath?: string;\n /** Default access level for unverified requests */\n defaultAccessLevel?: import('../types').AccessLevel;\n /** Minimum trust score for standard access (online/hybrid) */\n minTrustScore?: number;\n /** Minimum trust score for full access (online/hybrid) */\n minTrustScoreForFull?: number;\n /** Custom headers to send with API requests */\n customHeaders?: Record<string, string>;\n /** Counterparty URL for analytics */\n counterpartyUrl?: string;\n /** Counterparty type for analytics */\n counterpartyType?: import('../types').CounterpartyType;\n}\n\n// ========================================================================\n// PDLSS Context (Agent-side action context)\n// ========================================================================\n\nexport interface PDLSSContext {\n /** Purpose category (e.g. email.send, shell.exec, file.read) */\n purpose: string;\n /** Specific action within purpose */\n action: string;\n /** Target resource, recipient, or counterparty */\n target: string;\n /** Types of data access (read, write, delete) */\n dataAccess?: string[];\n /** Network domains/IPs being accessed */\n networkAccess?: string[];\n /** Resource type (customer, order, file, directory, process) */\n resourceType?: string;\n /** Risk factors for this action */\n riskFactors?: RiskFactor[];\n /** Transaction value (if financial) */\n transactionValue?: number;\n /** Currency for transaction */\n currency?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface RiskFactor {\n type: 'financial' | 'data_sensitivity' | 'privilege_escalation' | 'network_scope' | 'destructive';\n severity: 'low' | 'medium' | 'high' | 'critical';\n detail: string;\n}\n\n// ========================================================================\n// Verification Decision\n// ========================================================================\n\nexport interface VerificationDecision {\n recommendation: 'ALLOW' | 'DENY' | 'MANUAL_REVIEW';\n reason: string;\n trustScore?: number;\n tokenGuidance?: import('../types').TokenGuidance;\n sessionId?: string;\n /**\n * PDLSS dimensions that were evaluated. `true` = the dimension was\n * configured in the policy and therefore ran; `false` = the dimension is\n * absent from the policy and was skipped — NOT \"failed\" (a failed\n * dimension never produces an ALLOW; this field is only set on ALLOW).\n */\n evaluatedDimensions?: {\n purpose: boolean;\n scope: boolean;\n limits: boolean;\n riskThresholds: boolean;\n };\n}\n\n// ========================================================================\n// Local Policy Types (YAML format)\n// ========================================================================\n\nexport interface LocalPolicy {\n version: string;\n name: string;\n description?: string;\n purposes: LocalPurposeRule[];\n scope?: LocalScope;\n limits?: LocalLimits;\n riskThresholds?: LocalRiskThresholds;\n selfInstantiation?: LocalSelfInstantiation;\n}\n\nexport interface LocalPurposeRule {\n id: string;\n allowed: boolean;\n targets?: string[];\n blockedPatterns?: string[];\n requiresApproval?: boolean;\n}\n\nexport interface LocalScope {\n allowedDomains?: string[];\n blockedDomains?: string[];\n blockedResources?: string[];\n}\n\nexport interface LocalLimits {\n maxTransactionAmount?: number;\n maxRequestsPerHour?: number;\n currency?: string;\n}\n\nexport interface LocalRiskThresholds {\n autoAllow: { min: number; max: number };\n requireApproval: { min: number; max: number };\n autoBlock: { min: number; max: number };\n}\n\nexport interface LocalSelfInstantiation {\n /** Whether sub-agent spawning is allowed */\n allowed: boolean;\n /** Maximum depth of sub-agent chain */\n maxDepth?: number;\n}\n\n// ========================================================================\n// Risk Scoring Defaults (cherry-picked from trust-harness-core)\n// ========================================================================\n\n/** Base risk scores per action category */\nexport const BASE_RISK_SCORES: Record<string, number> = {\n 'file.read': 10,\n 'file.write': 40,\n 'file.delete': 70,\n 'shell.exec': 50,\n 'network.fetch': 60,\n 'network.request': 60,\n 'email.send': 45,\n 'email.read': 15,\n 'calendar.create': 20,\n 'calendar.modify': 30,\n 'database.query': 25,\n 'database.write': 55,\n 'payment.execute': 80,\n 'sub_agent.spawn': 65,\n 'code.execute': 45,\n};\n\n/** Shell commands that significantly increase risk score */\nexport const HIGH_RISK_COMMANDS = [\n 'rm',\n 'rmdir',\n 'dd',\n 'mkfs',\n 'chmod',\n 'chown',\n 'sudo',\n 'su',\n 'curl',\n 'wget',\n 'nc',\n 'netcat',\n 'ssh',\n 'scp',\n 'rsync',\n 'git push',\n 'npm publish',\n 'docker',\n 'kubectl',\n 'gh pr create',\n 'gh pr merge',\n];\n\n/** File paths that indicate sensitive data access */\nexport const SENSITIVE_PATHS = [\n '.ssh',\n '.aws',\n '.gnupg',\n '.env',\n 'credentials',\n 'secrets',\n 'password',\n '.git/config',\n '/etc',\n '/var',\n '/root',\n 'id_rsa',\n '.npmrc',\n '.pypirc',\n];\n\n// ========================================================================\n// Trace Event Types\n// ========================================================================\n\nexport interface TraceEvent {\n id: string;\n timestamp: Date;\n /**\n * 'skipped' records a tool call the guard saw but did NOT govern\n * (unmapped tool, MCP with governMcp off, skipTools). Added 10 Jul 2026\n * after an ungoverned-PowerShell gap hid behind silent skips — coverage\n * holes must be visible in passive observation.\n */\n type: 'evaluation' | 'decision' | 'error' | 'mode_switch' | 'skipped';\n context?: PDLSSContext;\n decision?: VerificationDecision;\n metadata?: Record<string, unknown>;\n}\n\n// ========================================================================\n// Adapter Interface Types\n// ========================================================================\n\nexport interface AdapterConfig {\n /** The gateway instance (handles mode routing) */\n gateway: unknown; // Typed as AstraSyncGateway at usage site to avoid circular deps\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface AgentAction {\n /** Raw action data from the platform */\n raw: unknown;\n /** Platform identifier (e.g. 'openclaw-cli', 'cursor', 'browser') */\n platform: string;\n /** Timestamp of the action */\n timestamp: Date;\n}\n\nexport interface InterceptResult {\n /** Whether the action was intercepted */\n intercepted: boolean;\n /** Extracted PDLSS context (if intercepted) */\n context?: PDLSSContext;\n /** Reason for not intercepting (if not intercepted) */\n skipReason?: string;\n}\n\n// ========================================================================\n// Sync Queue Types (Hybrid mode)\n// ========================================================================\n\nexport interface SyncQueueEntry {\n id: string;\n context: PDLSSContext;\n decision: VerificationDecision;\n timestamp: Date;\n retryCount: number;\n status: 'pending' | 'synced' | 'failed';\n}\n","/**\n * Command-position matching for high-risk shell commands.\n *\n * Replaces the substring-based HIGH_RISK_COMMANDS check that flagged any\n * command merely *containing* a high-risk name (` su` matched \"superadmin\",\n * quoted text and heredoc bodies triggered). A high-risk entry now matches\n * only when it is the invoked command of some pipeline/list segment.\n *\n * Deliberately NOT a shell parser — pure regex/token splitting inside the\n * hook's latency budget. Known accepted misses: option arguments that survive\n * flag filtering (`git -C /tmp push`), exotic quoting/escapes.\n */\n\nimport { HIGH_RISK_COMMANDS } from '../gateway/types';\n\n/** Wrapper commands that prefix the real command; hop over them and re-check. */\nconst WRAPPERS = new Set([\n 'sudo',\n 'doas',\n 'env',\n 'nohup',\n 'time',\n 'timeout',\n 'nice',\n 'ionice',\n 'stdbuf',\n 'xargs',\n 'command',\n 'exec',\n 'builtin',\n]);\n\n/** Leading environment assignment, e.g. `FOO=bar cmd` (input is lowercased). */\nconst ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;\n\nconst MAX_WRAPPER_HOPS = 4;\nconst MAX_RECURSION_DEPTH = 3;\nconst MAX_COMMAND_LENGTH = 100_000;\n\n/** Hosts considered local for the curl/wget risk downgrade (brackets pre-stripped). */\nconst LOCAL_HOST_RE = /^(localhost|127\\.0\\.0\\.1|::1|[a-z0-9.-]*\\.localhost)$/;\n\ninterface RiskList {\n singleWord: Set<string>;\n /** Sorted longest-first so `gh pr create` wins over a shorter prefix entry. */\n multiWord: string[][];\n}\n\nfunction buildRiskList(entries: readonly string[]): RiskList {\n const singleWord = new Set<string>();\n const multiWord: string[][] = [];\n for (const entry of entries) {\n const words = entry.toLowerCase().trim().split(/\\s+/);\n if (words.length === 1) singleWord.add(words[0]);\n else multiWord.push(words);\n }\n multiWord.sort((a, b) => b.length - a.length);\n return { singleWord, multiWord };\n}\n\n/** Drop heredoc bodies (`<<TAG` … `TAG`) — stdin text, not executed commands. */\nfunction stripHeredocBodies(text: string): string {\n const lines = text.split('\\n');\n const kept: string[] = [];\n let terminator: string | null = null;\n for (const line of lines) {\n if (terminator !== null) {\n if (line.trim() === terminator) terminator = null;\n continue;\n }\n kept.push(line);\n const heredoc = line.match(/<<-?\\s*['\"]?(\\w+)['\"]?/);\n if (heredoc) terminator = heredoc[1];\n }\n return kept.join('\\n');\n}\n\n/**\n * Pull out `$(…)` command substitutions (recursing into their contents via\n * `collect`) and blank their spans. No quote awareness inside the parens —\n * pragmatic depth counting only.\n */\nfunction extractDollarSubstitutions(text: string, collect: (inner: string) => void): string {\n let result = text;\n let start = result.indexOf('$(');\n let iterations = 0;\n while (start !== -1 && iterations < 50) {\n iterations++;\n let depth = 0;\n let end = result.length;\n for (let i = start + 1; i < result.length; i++) {\n if (result[i] === '(') depth++;\n else if (result[i] === ')') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n collect(result.slice(start + 2, end));\n result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;\n start = result.indexOf('$(');\n }\n return result;\n}\n\n/** Pull out backtick substitutions (odd chunks when split on `) and blank them. */\nfunction extractBacktickSubstitutions(text: string, collect: (inner: string) => void): string {\n if (!text.includes('`')) return text;\n const chunks = text.split('`');\n const kept: string[] = [];\n for (let i = 0; i < chunks.length; i++) {\n if (i % 2 === 1) collect(chunks[i]);\n else kept.push(chunks[i]);\n }\n return kept.join(' ');\n}\n\n/**\n * Reduce a command token to its matchable name: strip any leading path\n * (both separator styles — PowerShell targets arrive with backslashes) and\n * a trailing Windows executable extension, so `C:\\Tools\\curl.exe` and\n * `curl.exe` both match the `curl` high-risk entry.\n */\nfunction basename(token: string): string {\n const slash = Math.max(token.lastIndexOf('/'), token.lastIndexOf('\\\\'));\n const name = slash === -1 ? token : token.slice(slash + 1);\n return name.replace(/\\.(exe|cmd|bat|ps1)$/i, '');\n}\n\nfunction matchSegment(segment: string, list: RiskList): string | null {\n const tokens = segment.split(/\\s+/).filter((t) => /[a-z0-9]/.test(t));\n let i = 0;\n let hops = 0;\n while (i < tokens.length) {\n // Skip `export` and leading env assignments to reach the command position.\n while (i < tokens.length && (tokens[i] === 'export' || ASSIGN_RE.test(tokens[i]))) i++;\n if (i >= tokens.length) return null;\n\n const cmd = basename(tokens[i]);\n\n // Multi-word entries match against the leading non-flag tokens.\n const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith('-'))];\n for (const entry of list.multiWord) {\n if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {\n return entry.join(' ');\n }\n }\n if (list.singleWord.has(cmd)) return cmd;\n\n // High-risk check ran first, so `sudo …` matches `sudo` before hopping.\n if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {\n hops++;\n i++;\n while (i < tokens.length && (tokens[i].startsWith('-') || ASSIGN_RE.test(tokens[i]))) i++;\n continue;\n }\n return null;\n }\n return null;\n}\n\nfunction collectMatches(\n command: string,\n list: RiskList,\n matches: Set<string>,\n depth: number\n): void {\n if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;\n\n let text = command.toLowerCase();\n text = stripHeredocBodies(text);\n\n // Substitutions are extracted (and their contents checked) BEFORE quote\n // stripping so `\"$(curl x)\"` is still evaluated in command position.\n const recurse = (inner: string) => collectMatches(inner, list, matches, depth + 1);\n text = extractDollarSubstitutions(text, recurse);\n text = extractBacktickSubstitutions(text, recurse);\n\n // Quoted spans are data, not commands; `#` comments are noise.\n text = text.replace(/'[^']*'/g, ' ').replace(/\"[^\"]*\"/g, ' ');\n text = text.replace(/(^|\\s)#[^\\n]*/g, ' ');\n\n const segments = text.split(/\\|\\||&&|[;|&\\n]|[({]/);\n for (const segment of segments) {\n const matched = matchSegment(segment, list);\n if (matched) matches.add(matched);\n }\n}\n\n/**\n * Returns every high-risk entry invoked in command position anywhere in\n * `command` (deduplicated), or an empty array.\n */\nexport function matchHighRiskCommands(\n command: string,\n highRiskCommands: readonly string[] = HIGH_RISK_COMMANDS\n): string[] {\n if (!command) return [];\n const matches = new Set<string>();\n collectMatches(command, buildRiskList(highRiskCommands), matches, 0);\n return [...matches];\n}\n\n/**\n * Returns the first high-risk entry invoked in command position, or null.\n * Convenience wrapper over {@link matchHighRiskCommands}.\n */\nexport function matchHighRiskCommand(\n command: string,\n highRiskCommands: readonly string[] = HIGH_RISK_COMMANDS\n): string | null {\n return matchHighRiskCommands(command, highRiskCommands)[0] ?? null;\n}\n\n/**\n * True when the command's network destinations are exclusively local\n * (localhost / 127.0.0.1 / ::1 / *.localhost) AND at least one destination is\n * present. Used to skip the risk-80 bump for curl/wget dev-loop probes.\n * Unparseable or absent destinations keep the bump (fail toward review).\n */\nexport function isLocalhostOnlyNetworkCommand(command: string): boolean {\n const text = command.toLowerCase();\n // Candidate destinations: explicit URLs, host:port tokens, or bare local hosts.\n const tokens = text.split(/\\s+/).map((t) => t.replace(/^[\"'`]+|[\"'`,;)]+$/g, ''));\n let localCount = 0;\n for (const token of tokens) {\n let host: string | null = null;\n if (token.includes('://')) {\n let rest = token.slice(token.indexOf('://') + 3);\n const at = rest.indexOf('@');\n if (at !== -1) rest = rest.slice(at + 1);\n host = rest.split(/[/?#]/)[0];\n } else if (/^[a-z0-9.[\\]:-]+:\\d+(\\/.*)?$/.test(token)) {\n host = token.split('/')[0];\n } else if (/^(localhost|127\\.0\\.0\\.1|\\[?::1\\]?)(\\/.*)?$/.test(token)) {\n host = token.split('/')[0];\n }\n if (host === null) continue;\n // Strip a trailing numeric port, then IPv6 brackets ([::1]:3000 → ::1).\n const bare = host.replace(/:\\d+$/, '').replace(/^\\[|\\]$/g, '');\n if (LOCAL_HOST_RE.test(bare)) {\n localCount++;\n } else {\n return false;\n }\n }\n return localCount > 0;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -121,8 +121,9 @@ function extractBacktickSubstitutions(text, collect) {
|
|
|
121
121
|
return kept.join(" ");
|
|
122
122
|
}
|
|
123
123
|
function basename(token) {
|
|
124
|
-
const slash = token.lastIndexOf("/");
|
|
125
|
-
|
|
124
|
+
const slash = Math.max(token.lastIndexOf("/"), token.lastIndexOf("\\"));
|
|
125
|
+
const name = slash === -1 ? token : token.slice(slash + 1);
|
|
126
|
+
return name.replace(/\.(exe|cmd|bat|ps1)$/i, "");
|
|
126
127
|
}
|
|
127
128
|
function matchSegment(segment, list) {
|
|
128
129
|
const tokens = segment.split(/\s+/).filter((t) => /[a-z0-9]/.test(t));
|
|
@@ -255,7 +256,9 @@ var LocalEvaluator = class {
|
|
|
255
256
|
}
|
|
256
257
|
return {
|
|
257
258
|
recommendation: "ALLOW",
|
|
258
|
-
|
|
259
|
+
// "evaluated": dimensions absent from the policy are skipped, not
|
|
260
|
+
// failed — evaluatedDimensions below records which ones ran.
|
|
261
|
+
reason: "All evaluated PDLSS checks passed",
|
|
259
262
|
evaluatedDimensions: {
|
|
260
263
|
purpose: true,
|
|
261
264
|
scope: !!this.policy.scope,
|
|
@@ -354,7 +357,7 @@ var LocalEvaluator = class {
|
|
|
354
357
|
}
|
|
355
358
|
}
|
|
356
359
|
if ((context.purpose === "file.read" || context.purpose === "file.write" || context.purpose === "file.delete") && context.target) {
|
|
357
|
-
const targetLower = context.target.toLowerCase();
|
|
360
|
+
const targetLower = context.target.toLowerCase().replace(/\\/g, "/");
|
|
358
361
|
for (const sensitivePath of SENSITIVE_PATHS) {
|
|
359
362
|
if (targetLower.includes(sensitivePath.toLowerCase())) {
|
|
360
363
|
score = Math.max(score, 50);
|
|
@@ -383,12 +386,20 @@ var LocalEvaluator = class {
|
|
|
383
386
|
matchesAnyPattern(value, patterns) {
|
|
384
387
|
return patterns.some((pattern) => this.matchGlob(value, pattern));
|
|
385
388
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
389
|
+
homePrefix() {
|
|
390
|
+
if (this.cachedHomePrefix !== void 0) return this.cachedHomePrefix;
|
|
391
|
+
const env = typeof process !== "undefined" ? process.env : void 0;
|
|
392
|
+
const home = env?.HOME || env?.USERPROFILE;
|
|
393
|
+
this.cachedHomePrefix = home ? home.replace(/\\/g, "/") : null;
|
|
394
|
+
return this.cachedHomePrefix;
|
|
395
|
+
}
|
|
391
396
|
matchGlob(value, pattern) {
|
|
397
|
+
value = value.replace(/\\/g, "/");
|
|
398
|
+
pattern = pattern.replace(/\\/g, "/");
|
|
399
|
+
if (pattern === "~" || pattern.startsWith("~/")) {
|
|
400
|
+
const home = this.homePrefix();
|
|
401
|
+
if (home) pattern = home + pattern.slice(1);
|
|
402
|
+
}
|
|
392
403
|
if (pattern === value) return true;
|
|
393
404
|
const starCount = (pattern.match(/\*/g) ?? []).length;
|
|
394
405
|
if (starCount > 8) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/gateway/types.ts","../../src/local-evaluator/command-matcher.ts","../../src/local-evaluator/evaluator.ts"],"sourcesContent":["/**\n * AstraSync Gateway - Types for gateway modes, local evaluation, and adapter interface.\n */\n\n// ========================================================================\n// Gateway Configuration\n// ========================================================================\n\nexport type GatewayMode = 'online' | 'local' | 'hybrid';\n\n/**\n * Posture controls whether the gateway actively blocks or just monitors.\n * - active: Evaluate and enforce decisions (block/allow/review)\n * - passive: Evaluate and log but never block (telemetry-only mode)\n */\nexport type GatewayPosture = 'active' | 'passive';\n\nexport interface AstraSyncGatewayConfig {\n mode: GatewayMode;\n /** Enforcement posture: 'active' blocks actions, 'passive' logs only (default: 'active') */\n posture?: GatewayPosture;\n /** AstraSync API base URL (required for online/hybrid modes) */\n apiBaseUrl?: string;\n /** API key for authenticating with AstraSync (required for online/hybrid modes) */\n apiKey?: string;\n /**\n * ASTRA- agent id this gateway acts as. Attached to hybrid sync entries and\n * used as the default identity for online verification when the caller\n * passes no per-call astraId.\n */\n agentId?: string;\n /** Path to local PDLSS policy YAML file (required for local/hybrid modes) */\n policyFile?: string;\n /** Inline policy object (alternative to policyFile) */\n policy?: LocalPolicy;\n /** Sync interval in seconds for hybrid mode (default: 3600) */\n syncInterval?: number;\n /** Cache verification results TTL in seconds (default: 300) */\n cacheTtl?: number;\n /** Enable debug logging */\n debug?: boolean;\n /** Enable trace logging to .astrasync/traces/ (default: false) */\n traceEnabled?: boolean;\n /** Trace log directory (default: .astrasync/traces/) */\n tracePath?: string;\n /** Default access level for unverified requests */\n defaultAccessLevel?: import('../types').AccessLevel;\n /** Minimum trust score for standard access (online/hybrid) */\n minTrustScore?: number;\n /** Minimum trust score for full access (online/hybrid) */\n minTrustScoreForFull?: number;\n /** Custom headers to send with API requests */\n customHeaders?: Record<string, string>;\n /** Counterparty URL for analytics */\n counterpartyUrl?: string;\n /** Counterparty type for analytics */\n counterpartyType?: import('../types').CounterpartyType;\n}\n\n// ========================================================================\n// PDLSS Context (Agent-side action context)\n// ========================================================================\n\nexport interface PDLSSContext {\n /** Purpose category (e.g. email.send, shell.exec, file.read) */\n purpose: string;\n /** Specific action within purpose */\n action: string;\n /** Target resource, recipient, or counterparty */\n target: string;\n /** Types of data access (read, write, delete) */\n dataAccess?: string[];\n /** Network domains/IPs being accessed */\n networkAccess?: string[];\n /** Resource type (customer, order, file, directory, process) */\n resourceType?: string;\n /** Risk factors for this action */\n riskFactors?: RiskFactor[];\n /** Transaction value (if financial) */\n transactionValue?: number;\n /** Currency for transaction */\n currency?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface RiskFactor {\n type: 'financial' | 'data_sensitivity' | 'privilege_escalation' | 'network_scope' | 'destructive';\n severity: 'low' | 'medium' | 'high' | 'critical';\n detail: string;\n}\n\n// ========================================================================\n// Verification Decision\n// ========================================================================\n\nexport interface VerificationDecision {\n recommendation: 'ALLOW' | 'DENY' | 'MANUAL_REVIEW';\n reason: string;\n trustScore?: number;\n tokenGuidance?: import('../types').TokenGuidance;\n sessionId?: string;\n /** PDLSS dimensions that were evaluated */\n evaluatedDimensions?: {\n purpose: boolean;\n scope: boolean;\n limits: boolean;\n riskThresholds: boolean;\n };\n}\n\n// ========================================================================\n// Local Policy Types (YAML format)\n// ========================================================================\n\nexport interface LocalPolicy {\n version: string;\n name: string;\n description?: string;\n purposes: LocalPurposeRule[];\n scope?: LocalScope;\n limits?: LocalLimits;\n riskThresholds?: LocalRiskThresholds;\n selfInstantiation?: LocalSelfInstantiation;\n}\n\nexport interface LocalPurposeRule {\n id: string;\n allowed: boolean;\n targets?: string[];\n blockedPatterns?: string[];\n requiresApproval?: boolean;\n}\n\nexport interface LocalScope {\n allowedDomains?: string[];\n blockedDomains?: string[];\n blockedResources?: string[];\n}\n\nexport interface LocalLimits {\n maxTransactionAmount?: number;\n maxRequestsPerHour?: number;\n currency?: string;\n}\n\nexport interface LocalRiskThresholds {\n autoAllow: { min: number; max: number };\n requireApproval: { min: number; max: number };\n autoBlock: { min: number; max: number };\n}\n\nexport interface LocalSelfInstantiation {\n /** Whether sub-agent spawning is allowed */\n allowed: boolean;\n /** Maximum depth of sub-agent chain */\n maxDepth?: number;\n}\n\n// ========================================================================\n// Risk Scoring Defaults (cherry-picked from trust-harness-core)\n// ========================================================================\n\n/** Base risk scores per action category */\nexport const BASE_RISK_SCORES: Record<string, number> = {\n 'file.read': 10,\n 'file.write': 40,\n 'file.delete': 70,\n 'shell.exec': 50,\n 'network.fetch': 60,\n 'network.request': 60,\n 'email.send': 45,\n 'email.read': 15,\n 'calendar.create': 20,\n 'calendar.modify': 30,\n 'database.query': 25,\n 'database.write': 55,\n 'payment.execute': 80,\n 'sub_agent.spawn': 65,\n 'code.execute': 45,\n};\n\n/** Shell commands that significantly increase risk score */\nexport const HIGH_RISK_COMMANDS = [\n 'rm',\n 'rmdir',\n 'dd',\n 'mkfs',\n 'chmod',\n 'chown',\n 'sudo',\n 'su',\n 'curl',\n 'wget',\n 'nc',\n 'netcat',\n 'ssh',\n 'scp',\n 'rsync',\n 'git push',\n 'npm publish',\n 'docker',\n 'kubectl',\n 'gh pr create',\n 'gh pr merge',\n];\n\n/** File paths that indicate sensitive data access */\nexport const SENSITIVE_PATHS = [\n '.ssh',\n '.aws',\n '.gnupg',\n '.env',\n 'credentials',\n 'secrets',\n 'password',\n '.git/config',\n '/etc',\n '/var',\n '/root',\n 'id_rsa',\n '.npmrc',\n '.pypirc',\n];\n\n// ========================================================================\n// Trace Event Types\n// ========================================================================\n\nexport interface TraceEvent {\n id: string;\n timestamp: Date;\n /**\n * 'skipped' records a tool call the guard saw but did NOT govern\n * (unmapped tool, MCP with governMcp off, skipTools). Added 10 Jul 2026\n * after an ungoverned-PowerShell gap hid behind silent skips — coverage\n * holes must be visible in passive observation.\n */\n type: 'evaluation' | 'decision' | 'error' | 'mode_switch' | 'skipped';\n context?: PDLSSContext;\n decision?: VerificationDecision;\n metadata?: Record<string, unknown>;\n}\n\n// ========================================================================\n// Adapter Interface Types\n// ========================================================================\n\nexport interface AdapterConfig {\n /** The gateway instance (handles mode routing) */\n gateway: unknown; // Typed as AstraSyncGateway at usage site to avoid circular deps\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface AgentAction {\n /** Raw action data from the platform */\n raw: unknown;\n /** Platform identifier (e.g. 'openclaw-cli', 'cursor', 'browser') */\n platform: string;\n /** Timestamp of the action */\n timestamp: Date;\n}\n\nexport interface InterceptResult {\n /** Whether the action was intercepted */\n intercepted: boolean;\n /** Extracted PDLSS context (if intercepted) */\n context?: PDLSSContext;\n /** Reason for not intercepting (if not intercepted) */\n skipReason?: string;\n}\n\n// ========================================================================\n// Sync Queue Types (Hybrid mode)\n// ========================================================================\n\nexport interface SyncQueueEntry {\n id: string;\n context: PDLSSContext;\n decision: VerificationDecision;\n timestamp: Date;\n retryCount: number;\n status: 'pending' | 'synced' | 'failed';\n}\n","/**\n * Command-position matching for high-risk shell commands.\n *\n * Replaces the substring-based HIGH_RISK_COMMANDS check that flagged any\n * command merely *containing* a high-risk name (` su` matched \"superadmin\",\n * quoted text and heredoc bodies triggered). A high-risk entry now matches\n * only when it is the invoked command of some pipeline/list segment.\n *\n * Deliberately NOT a shell parser — pure regex/token splitting inside the\n * hook's latency budget. Known accepted misses: option arguments that survive\n * flag filtering (`git -C /tmp push`), exotic quoting/escapes.\n */\n\nimport { HIGH_RISK_COMMANDS } from '../gateway/types';\n\n/** Wrapper commands that prefix the real command; hop over them and re-check. */\nconst WRAPPERS = new Set([\n 'sudo',\n 'doas',\n 'env',\n 'nohup',\n 'time',\n 'timeout',\n 'nice',\n 'ionice',\n 'stdbuf',\n 'xargs',\n 'command',\n 'exec',\n 'builtin',\n]);\n\n/** Leading environment assignment, e.g. `FOO=bar cmd` (input is lowercased). */\nconst ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;\n\nconst MAX_WRAPPER_HOPS = 4;\nconst MAX_RECURSION_DEPTH = 3;\nconst MAX_COMMAND_LENGTH = 100_000;\n\n/** Hosts considered local for the curl/wget risk downgrade (brackets pre-stripped). */\nconst LOCAL_HOST_RE = /^(localhost|127\\.0\\.0\\.1|::1|[a-z0-9.-]*\\.localhost)$/;\n\ninterface RiskList {\n singleWord: Set<string>;\n /** Sorted longest-first so `gh pr create` wins over a shorter prefix entry. */\n multiWord: string[][];\n}\n\nfunction buildRiskList(entries: readonly string[]): RiskList {\n const singleWord = new Set<string>();\n const multiWord: string[][] = [];\n for (const entry of entries) {\n const words = entry.toLowerCase().trim().split(/\\s+/);\n if (words.length === 1) singleWord.add(words[0]);\n else multiWord.push(words);\n }\n multiWord.sort((a, b) => b.length - a.length);\n return { singleWord, multiWord };\n}\n\n/** Drop heredoc bodies (`<<TAG` … `TAG`) — stdin text, not executed commands. */\nfunction stripHeredocBodies(text: string): string {\n const lines = text.split('\\n');\n const kept: string[] = [];\n let terminator: string | null = null;\n for (const line of lines) {\n if (terminator !== null) {\n if (line.trim() === terminator) terminator = null;\n continue;\n }\n kept.push(line);\n const heredoc = line.match(/<<-?\\s*['\"]?(\\w+)['\"]?/);\n if (heredoc) terminator = heredoc[1];\n }\n return kept.join('\\n');\n}\n\n/**\n * Pull out `$(…)` command substitutions (recursing into their contents via\n * `collect`) and blank their spans. No quote awareness inside the parens —\n * pragmatic depth counting only.\n */\nfunction extractDollarSubstitutions(text: string, collect: (inner: string) => void): string {\n let result = text;\n let start = result.indexOf('$(');\n let iterations = 0;\n while (start !== -1 && iterations < 50) {\n iterations++;\n let depth = 0;\n let end = result.length;\n for (let i = start + 1; i < result.length; i++) {\n if (result[i] === '(') depth++;\n else if (result[i] === ')') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n collect(result.slice(start + 2, end));\n result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;\n start = result.indexOf('$(');\n }\n return result;\n}\n\n/** Pull out backtick substitutions (odd chunks when split on `) and blank them. */\nfunction extractBacktickSubstitutions(text: string, collect: (inner: string) => void): string {\n if (!text.includes('`')) return text;\n const chunks = text.split('`');\n const kept: string[] = [];\n for (let i = 0; i < chunks.length; i++) {\n if (i % 2 === 1) collect(chunks[i]);\n else kept.push(chunks[i]);\n }\n return kept.join(' ');\n}\n\nfunction basename(token: string): string {\n const slash = token.lastIndexOf('/');\n return slash === -1 ? token : token.slice(slash + 1);\n}\n\nfunction matchSegment(segment: string, list: RiskList): string | null {\n const tokens = segment.split(/\\s+/).filter((t) => /[a-z0-9]/.test(t));\n let i = 0;\n let hops = 0;\n while (i < tokens.length) {\n // Skip `export` and leading env assignments to reach the command position.\n while (i < tokens.length && (tokens[i] === 'export' || ASSIGN_RE.test(tokens[i]))) i++;\n if (i >= tokens.length) return null;\n\n const cmd = basename(tokens[i]);\n\n // Multi-word entries match against the leading non-flag tokens.\n const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith('-'))];\n for (const entry of list.multiWord) {\n if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {\n return entry.join(' ');\n }\n }\n if (list.singleWord.has(cmd)) return cmd;\n\n // High-risk check ran first, so `sudo …` matches `sudo` before hopping.\n if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {\n hops++;\n i++;\n while (i < tokens.length && (tokens[i].startsWith('-') || ASSIGN_RE.test(tokens[i]))) i++;\n continue;\n }\n return null;\n }\n return null;\n}\n\nfunction collectMatches(\n command: string,\n list: RiskList,\n matches: Set<string>,\n depth: number\n): void {\n if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;\n\n let text = command.toLowerCase();\n text = stripHeredocBodies(text);\n\n // Substitutions are extracted (and their contents checked) BEFORE quote\n // stripping so `\"$(curl x)\"` is still evaluated in command position.\n const recurse = (inner: string) => collectMatches(inner, list, matches, depth + 1);\n text = extractDollarSubstitutions(text, recurse);\n text = extractBacktickSubstitutions(text, recurse);\n\n // Quoted spans are data, not commands; `#` comments are noise.\n text = text.replace(/'[^']*'/g, ' ').replace(/\"[^\"]*\"/g, ' ');\n text = text.replace(/(^|\\s)#[^\\n]*/g, ' ');\n\n const segments = text.split(/\\|\\||&&|[;|&\\n]|[({]/);\n for (const segment of segments) {\n const matched = matchSegment(segment, list);\n if (matched) matches.add(matched);\n }\n}\n\n/**\n * Returns every high-risk entry invoked in command position anywhere in\n * `command` (deduplicated), or an empty array.\n */\nexport function matchHighRiskCommands(\n command: string,\n highRiskCommands: readonly string[] = HIGH_RISK_COMMANDS\n): string[] {\n if (!command) return [];\n const matches = new Set<string>();\n collectMatches(command, buildRiskList(highRiskCommands), matches, 0);\n return [...matches];\n}\n\n/**\n * Returns the first high-risk entry invoked in command position, or null.\n * Convenience wrapper over {@link matchHighRiskCommands}.\n */\nexport function matchHighRiskCommand(\n command: string,\n highRiskCommands: readonly string[] = HIGH_RISK_COMMANDS\n): string | null {\n return matchHighRiskCommands(command, highRiskCommands)[0] ?? null;\n}\n\n/**\n * True when the command's network destinations are exclusively local\n * (localhost / 127.0.0.1 / ::1 / *.localhost) AND at least one destination is\n * present. Used to skip the risk-80 bump for curl/wget dev-loop probes.\n * Unparseable or absent destinations keep the bump (fail toward review).\n */\nexport function isLocalhostOnlyNetworkCommand(command: string): boolean {\n const text = command.toLowerCase();\n // Candidate destinations: explicit URLs, host:port tokens, or bare local hosts.\n const tokens = text.split(/\\s+/).map((t) => t.replace(/^[\"'`]+|[\"'`,;)]+$/g, ''));\n let localCount = 0;\n for (const token of tokens) {\n let host: string | null = null;\n if (token.includes('://')) {\n let rest = token.slice(token.indexOf('://') + 3);\n const at = rest.indexOf('@');\n if (at !== -1) rest = rest.slice(at + 1);\n host = rest.split(/[/?#]/)[0];\n } else if (/^[a-z0-9.[\\]:-]+:\\d+(\\/.*)?$/.test(token)) {\n host = token.split('/')[0];\n } else if (/^(localhost|127\\.0\\.0\\.1|\\[?::1\\]?)(\\/.*)?$/.test(token)) {\n host = token.split('/')[0];\n }\n if (host === null) continue;\n // Strip a trailing numeric port, then IPv6 brackets ([::1]:3000 → ::1).\n const bare = host.replace(/:\\d+$/, '').replace(/^\\[|\\]$/g, '');\n if (LOCAL_HOST_RE.test(bare)) {\n localCount++;\n } else {\n return false;\n }\n }\n return localCount > 0;\n}\n","/**\n * Local PDLSS Evaluator\n *\n * Evaluates agent actions against a local PDLSS policy.\n * Same logic as the cloud evaluator but runs in-process with no I/O.\n *\n * Evaluation order:\n * 1. Purpose: find matching rule, check if allowed\n * 2. Purpose: check allowed targets (if specified)\n * 3. Purpose: check blocked patterns (if specified)\n * 4. Scope: check global blocked resources/domains\n * 5. Limits: check transaction/rate limits\n * 6. Risk thresholds + approval requirements\n */\n\nimport type {\n LocalPolicy,\n PDLSSContext,\n VerificationDecision,\n LocalPurposeRule,\n} from '../gateway/types';\nimport { SENSITIVE_PATHS } from '../gateway/types';\nimport { matchHighRiskCommands, isLocalhostOnlyNetworkCommand } from './command-matcher';\n\nexport class LocalEvaluator {\n private policy: LocalPolicy;\n private requestCounts: Map<string, { count: number; windowStart: number }> = new Map();\n\n constructor(policy: LocalPolicy) {\n this.policy = policy;\n }\n\n /**\n * Update the policy (e.g. after hot-reload or sync).\n */\n updatePolicy(policy: LocalPolicy): void {\n this.policy = policy;\n }\n\n /**\n * Evaluate an action context against the loaded policy.\n */\n evaluate(context: PDLSSContext): VerificationDecision {\n // 1. Purpose: find matching rule\n const purposeRule = this.policy.purposes.find((p) => p.id === context.purpose);\n if (!purposeRule) {\n return { recommendation: 'DENY', reason: 'Purpose not in policy' };\n }\n if (!purposeRule.allowed) {\n return { recommendation: 'DENY', reason: 'Purpose explicitly blocked' };\n }\n\n // 2. Purpose: check allowed targets\n if (purposeRule.targets && !this.matchesAnyPattern(context.target, purposeRule.targets)) {\n return { recommendation: 'DENY', reason: 'Target not in allowed list' };\n }\n\n // 3. Purpose: check blocked patterns\n if (\n purposeRule.blockedPatterns &&\n this.matchesAnyPattern(context.target, purposeRule.blockedPatterns)\n ) {\n return { recommendation: 'DENY', reason: 'Target matches blocked pattern' };\n }\n\n // 4. Scope: check global blocked resources/domains\n const scopeBlock = this.checkScopeBlock(context);\n if (scopeBlock) {\n return { recommendation: 'DENY', reason: scopeBlock };\n }\n\n // 5. Limits: check transaction and rate limits\n const limitViolation = this.checkLimits(context);\n if (limitViolation) {\n return { recommendation: 'DENY', reason: limitViolation };\n }\n\n // 6. Self-instantiation: check sub-agent spawning rules\n if (context.purpose === 'sub_agent.spawn' && this.policy.selfInstantiation) {\n if (!this.policy.selfInstantiation.allowed) {\n return { recommendation: 'DENY', reason: 'Sub-agent spawning is not allowed' };\n }\n const depth = (context.metadata?.subAgentDepth as number) || 0;\n if (\n this.policy.selfInstantiation.maxDepth !== undefined &&\n depth >= this.policy.selfInstantiation.maxDepth\n ) {\n return {\n recommendation: 'DENY',\n reason: `Sub-agent depth ${depth} exceeds max depth ${this.policy.selfInstantiation.maxDepth}`,\n };\n }\n }\n\n // 7. Risk thresholds + approval requirements\n if (purposeRule.requiresApproval) {\n return { recommendation: 'MANUAL_REVIEW', reason: 'Purpose requires approval' };\n }\n\n const riskDecision = this.checkRiskThresholds(context);\n if (riskDecision) {\n return riskDecision;\n }\n\n return {\n recommendation: 'ALLOW',\n reason: 'All PDLSS checks passed',\n evaluatedDimensions: {\n purpose: true,\n scope: !!this.policy.scope,\n limits: !!this.policy.limits,\n riskThresholds: !!this.policy.riskThresholds,\n },\n };\n }\n\n private checkScopeBlock(context: PDLSSContext): string | null {\n const scope = this.policy.scope;\n if (!scope) return null;\n\n // Check blocked domains against target and network access\n if (scope.blockedDomains) {\n const targetDomain = this.extractDomain(context.target);\n if (this.matchesAnyPattern(targetDomain, scope.blockedDomains)) {\n return `Target blocked by scope: ${context.target}`;\n }\n if (context.networkAccess) {\n for (const domain of context.networkAccess) {\n if (this.matchesAnyPattern(this.extractDomain(domain), scope.blockedDomains)) {\n return `Domain blocked by scope: ${domain}`;\n }\n }\n }\n }\n\n // Check blocked resources against target\n if (scope.blockedResources && this.matchesAnyPattern(context.target, scope.blockedResources)) {\n return `Resource blocked by scope: ${context.target}`;\n }\n\n // Check allowed domains (if specified, target must match)\n if (scope.allowedDomains && context.networkAccess) {\n for (const domain of context.networkAccess) {\n if (!this.matchesAnyPattern(this.extractDomain(domain), scope.allowedDomains)) {\n return `Domain not in allowed list: ${domain}`;\n }\n }\n }\n\n return null;\n }\n\n private checkLimits(context: PDLSSContext): string | null {\n const limits = this.policy.limits;\n if (!limits) return null;\n\n // Transaction amount check\n if (limits.maxTransactionAmount !== undefined && context.transactionValue !== undefined) {\n if (context.transactionValue > limits.maxTransactionAmount) {\n return `Transaction value ${context.transactionValue} exceeds limit ${limits.maxTransactionAmount}`;\n }\n }\n\n // Rate limit check\n if (limits.maxRequestsPerHour !== undefined) {\n const key = context.purpose;\n const now = Date.now();\n const entry = this.requestCounts.get(key);\n const hourMs = 3600000;\n\n if (!entry || now - entry.windowStart > hourMs) {\n this.requestCounts.set(key, { count: 1, windowStart: now });\n } else {\n entry.count++;\n if (entry.count > limits.maxRequestsPerHour) {\n return `Rate limit exceeded: ${entry.count}/${limits.maxRequestsPerHour} requests per hour`;\n }\n }\n }\n\n return null;\n }\n\n private checkRiskThresholds(context: PDLSSContext): VerificationDecision | null {\n if (!this.policy.riskThresholds) return null;\n\n const riskScore = this.calculateRiskScore(context);\n const thresholds = this.policy.riskThresholds;\n\n if (riskScore >= thresholds.autoBlock.min) {\n return { recommendation: 'DENY', reason: `Risk score ${riskScore} exceeds block threshold` };\n }\n\n if (riskScore >= thresholds.requireApproval.min) {\n return {\n recommendation: 'MANUAL_REVIEW',\n reason: `Risk score ${riskScore} requires approval`,\n };\n }\n\n return null;\n }\n\n private calculateRiskScore(context: PDLSSContext): number {\n let score = 0;\n\n // Explicit risk factors take priority (highest severity wins)\n if (context.riskFactors?.length) {\n const severityScores: Record<string, number> = {\n low: 10,\n medium: 40,\n high: 70,\n critical: 90,\n };\n\n for (const factor of context.riskFactors) {\n const factorScore = severityScores[factor.severity] || 0;\n if (factorScore > score) score = factorScore;\n }\n return score;\n }\n\n // Auto-detect risk from high-risk shell commands (command-position match,\n // not substring). curl/wget aimed exclusively at localhost stay below the\n // review threshold — dev-loop probes, not exfiltration surface.\n if (context.purpose === 'shell.exec' && context.target) {\n const matched = matchHighRiskCommands(context.target);\n const localProbeOnly =\n matched.length > 0 &&\n matched.every((cmd) => cmd === 'curl' || cmd === 'wget') &&\n isLocalhostOnlyNetworkCommand(context.target);\n if (matched.length > 0 && !localProbeOnly) {\n score = Math.max(score, 80);\n }\n }\n\n // Auto-detect risk from sensitive file paths (score 50 = review range)\n if (\n (context.purpose === 'file.read' ||\n context.purpose === 'file.write' ||\n context.purpose === 'file.delete') &&\n context.target\n ) {\n const targetLower = context.target.toLowerCase();\n for (const sensitivePath of SENSITIVE_PATHS) {\n if (targetLower.includes(sensitivePath.toLowerCase())) {\n score = Math.max(score, 50);\n break;\n }\n }\n }\n\n return score;\n }\n\n /**\n * Extract domain from a URL-like string (strips protocol and path).\n */\n private extractDomain(value: string): string {\n let domain = value;\n // Strip protocol\n const protoIndex = domain.indexOf('://');\n if (protoIndex !== -1) domain = domain.slice(protoIndex + 3);\n // Strip path\n const slashIndex = domain.indexOf('/');\n if (slashIndex !== -1) domain = domain.slice(0, slashIndex);\n // Strip port\n const colonIndex = domain.indexOf(':');\n if (colonIndex !== -1) domain = domain.slice(0, colonIndex);\n return domain;\n }\n\n /**\n * Check if a value matches any of the given glob patterns.\n */\n private matchesAnyPattern(value: string, patterns: string[]): boolean {\n return patterns.some((pattern) => this.matchGlob(value, pattern));\n }\n\n /**\n * Simple glob pattern matching.\n * Supports * (matches any characters including / and spaces) and ? (single char).\n * Uses the same approach as the backend's matchGlobPattern.\n */\n private matchGlob(value: string, pattern: string): boolean {\n // Exact match\n if (pattern === value) return true;\n\n // Audit F-A1-13: ReDoS guard. Patterns with too many `*` segments\n // produce catastrophic-backtracking regexes (e.g. `a*a*a*a*a*a*a*a*b`\n // against `aaaaaaaa` runs in exponential time). Local policies are\n // operator-controlled so attacker access requires operator-side\n // compromise, but a copy-pasted-from-internet example pattern could\n // still DoS the gateway when matched against request input.\n const starCount = (pattern.match(/\\*/g) ?? []).length;\n if (starCount > 8) {\n return false;\n }\n\n // Convert glob to regex\n const regexStr = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special regex chars (except * and ?)\n .replace(/\\*/g, '.*') // * matches anything\n .replace(/\\?/g, '.'); // ? matches single char\n\n try {\n return new RegExp(`^${regexStr}$`, 'i').test(value);\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Convenience: find a matching purpose rule.\n */\nexport function findPurposeRule(\n policy: LocalPolicy,\n purposeId: string\n): LocalPurposeRule | undefined {\n return policy.purposes.find((p) => p.id === purposeId);\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/gateway/types.ts","../../src/local-evaluator/command-matcher.ts","../../src/local-evaluator/evaluator.ts"],"sourcesContent":["/**\n * AstraSync Gateway - Types for gateway modes, local evaluation, and adapter interface.\n */\n\n// ========================================================================\n// Gateway Configuration\n// ========================================================================\n\nexport type GatewayMode = 'online' | 'local' | 'hybrid';\n\n/**\n * Posture controls whether the gateway actively blocks or just monitors.\n * - active: Evaluate and enforce decisions (block/allow/review)\n * - passive: Evaluate and log but never block (telemetry-only mode)\n */\nexport type GatewayPosture = 'active' | 'passive';\n\nexport interface AstraSyncGatewayConfig {\n mode: GatewayMode;\n /** Enforcement posture: 'active' blocks actions, 'passive' logs only (default: 'active') */\n posture?: GatewayPosture;\n /** AstraSync API base URL (required for online/hybrid modes) */\n apiBaseUrl?: string;\n /** API key for authenticating with AstraSync (required for online/hybrid modes) */\n apiKey?: string;\n /**\n * ASTRA- agent id this gateway acts as. Attached to hybrid sync entries and\n * used as the default identity for online verification when the caller\n * passes no per-call astraId.\n */\n agentId?: string;\n /** Path to local PDLSS policy YAML file (required for local/hybrid modes) */\n policyFile?: string;\n /** Inline policy object (alternative to policyFile) */\n policy?: LocalPolicy;\n /** Sync interval in seconds for hybrid mode (default: 3600) */\n syncInterval?: number;\n /** Cache verification results TTL in seconds (default: 300) */\n cacheTtl?: number;\n /** Enable debug logging */\n debug?: boolean;\n /** Enable trace logging to .astrasync/traces/ (default: false) */\n traceEnabled?: boolean;\n /** Trace log directory (default: .astrasync/traces/) */\n tracePath?: string;\n /** Default access level for unverified requests */\n defaultAccessLevel?: import('../types').AccessLevel;\n /** Minimum trust score for standard access (online/hybrid) */\n minTrustScore?: number;\n /** Minimum trust score for full access (online/hybrid) */\n minTrustScoreForFull?: number;\n /** Custom headers to send with API requests */\n customHeaders?: Record<string, string>;\n /** Counterparty URL for analytics */\n counterpartyUrl?: string;\n /** Counterparty type for analytics */\n counterpartyType?: import('../types').CounterpartyType;\n}\n\n// ========================================================================\n// PDLSS Context (Agent-side action context)\n// ========================================================================\n\nexport interface PDLSSContext {\n /** Purpose category (e.g. email.send, shell.exec, file.read) */\n purpose: string;\n /** Specific action within purpose */\n action: string;\n /** Target resource, recipient, or counterparty */\n target: string;\n /** Types of data access (read, write, delete) */\n dataAccess?: string[];\n /** Network domains/IPs being accessed */\n networkAccess?: string[];\n /** Resource type (customer, order, file, directory, process) */\n resourceType?: string;\n /** Risk factors for this action */\n riskFactors?: RiskFactor[];\n /** Transaction value (if financial) */\n transactionValue?: number;\n /** Currency for transaction */\n currency?: string;\n /** Additional metadata */\n metadata?: Record<string, unknown>;\n}\n\nexport interface RiskFactor {\n type: 'financial' | 'data_sensitivity' | 'privilege_escalation' | 'network_scope' | 'destructive';\n severity: 'low' | 'medium' | 'high' | 'critical';\n detail: string;\n}\n\n// ========================================================================\n// Verification Decision\n// ========================================================================\n\nexport interface VerificationDecision {\n recommendation: 'ALLOW' | 'DENY' | 'MANUAL_REVIEW';\n reason: string;\n trustScore?: number;\n tokenGuidance?: import('../types').TokenGuidance;\n sessionId?: string;\n /**\n * PDLSS dimensions that were evaluated. `true` = the dimension was\n * configured in the policy and therefore ran; `false` = the dimension is\n * absent from the policy and was skipped — NOT \"failed\" (a failed\n * dimension never produces an ALLOW; this field is only set on ALLOW).\n */\n evaluatedDimensions?: {\n purpose: boolean;\n scope: boolean;\n limits: boolean;\n riskThresholds: boolean;\n };\n}\n\n// ========================================================================\n// Local Policy Types (YAML format)\n// ========================================================================\n\nexport interface LocalPolicy {\n version: string;\n name: string;\n description?: string;\n purposes: LocalPurposeRule[];\n scope?: LocalScope;\n limits?: LocalLimits;\n riskThresholds?: LocalRiskThresholds;\n selfInstantiation?: LocalSelfInstantiation;\n}\n\nexport interface LocalPurposeRule {\n id: string;\n allowed: boolean;\n targets?: string[];\n blockedPatterns?: string[];\n requiresApproval?: boolean;\n}\n\nexport interface LocalScope {\n allowedDomains?: string[];\n blockedDomains?: string[];\n blockedResources?: string[];\n}\n\nexport interface LocalLimits {\n maxTransactionAmount?: number;\n maxRequestsPerHour?: number;\n currency?: string;\n}\n\nexport interface LocalRiskThresholds {\n autoAllow: { min: number; max: number };\n requireApproval: { min: number; max: number };\n autoBlock: { min: number; max: number };\n}\n\nexport interface LocalSelfInstantiation {\n /** Whether sub-agent spawning is allowed */\n allowed: boolean;\n /** Maximum depth of sub-agent chain */\n maxDepth?: number;\n}\n\n// ========================================================================\n// Risk Scoring Defaults (cherry-picked from trust-harness-core)\n// ========================================================================\n\n/** Base risk scores per action category */\nexport const BASE_RISK_SCORES: Record<string, number> = {\n 'file.read': 10,\n 'file.write': 40,\n 'file.delete': 70,\n 'shell.exec': 50,\n 'network.fetch': 60,\n 'network.request': 60,\n 'email.send': 45,\n 'email.read': 15,\n 'calendar.create': 20,\n 'calendar.modify': 30,\n 'database.query': 25,\n 'database.write': 55,\n 'payment.execute': 80,\n 'sub_agent.spawn': 65,\n 'code.execute': 45,\n};\n\n/** Shell commands that significantly increase risk score */\nexport const HIGH_RISK_COMMANDS = [\n 'rm',\n 'rmdir',\n 'dd',\n 'mkfs',\n 'chmod',\n 'chown',\n 'sudo',\n 'su',\n 'curl',\n 'wget',\n 'nc',\n 'netcat',\n 'ssh',\n 'scp',\n 'rsync',\n 'git push',\n 'npm publish',\n 'docker',\n 'kubectl',\n 'gh pr create',\n 'gh pr merge',\n];\n\n/** File paths that indicate sensitive data access */\nexport const SENSITIVE_PATHS = [\n '.ssh',\n '.aws',\n '.gnupg',\n '.env',\n 'credentials',\n 'secrets',\n 'password',\n '.git/config',\n '/etc',\n '/var',\n '/root',\n 'id_rsa',\n '.npmrc',\n '.pypirc',\n];\n\n// ========================================================================\n// Trace Event Types\n// ========================================================================\n\nexport interface TraceEvent {\n id: string;\n timestamp: Date;\n /**\n * 'skipped' records a tool call the guard saw but did NOT govern\n * (unmapped tool, MCP with governMcp off, skipTools). Added 10 Jul 2026\n * after an ungoverned-PowerShell gap hid behind silent skips — coverage\n * holes must be visible in passive observation.\n */\n type: 'evaluation' | 'decision' | 'error' | 'mode_switch' | 'skipped';\n context?: PDLSSContext;\n decision?: VerificationDecision;\n metadata?: Record<string, unknown>;\n}\n\n// ========================================================================\n// Adapter Interface Types\n// ========================================================================\n\nexport interface AdapterConfig {\n /** The gateway instance (handles mode routing) */\n gateway: unknown; // Typed as AstraSyncGateway at usage site to avoid circular deps\n /** Platform-specific configuration */\n adapterOptions: Record<string, unknown>;\n}\n\nexport interface AgentAction {\n /** Raw action data from the platform */\n raw: unknown;\n /** Platform identifier (e.g. 'openclaw-cli', 'cursor', 'browser') */\n platform: string;\n /** Timestamp of the action */\n timestamp: Date;\n}\n\nexport interface InterceptResult {\n /** Whether the action was intercepted */\n intercepted: boolean;\n /** Extracted PDLSS context (if intercepted) */\n context?: PDLSSContext;\n /** Reason for not intercepting (if not intercepted) */\n skipReason?: string;\n}\n\n// ========================================================================\n// Sync Queue Types (Hybrid mode)\n// ========================================================================\n\nexport interface SyncQueueEntry {\n id: string;\n context: PDLSSContext;\n decision: VerificationDecision;\n timestamp: Date;\n retryCount: number;\n status: 'pending' | 'synced' | 'failed';\n}\n","/**\n * Command-position matching for high-risk shell commands.\n *\n * Replaces the substring-based HIGH_RISK_COMMANDS check that flagged any\n * command merely *containing* a high-risk name (` su` matched \"superadmin\",\n * quoted text and heredoc bodies triggered). A high-risk entry now matches\n * only when it is the invoked command of some pipeline/list segment.\n *\n * Deliberately NOT a shell parser — pure regex/token splitting inside the\n * hook's latency budget. Known accepted misses: option arguments that survive\n * flag filtering (`git -C /tmp push`), exotic quoting/escapes.\n */\n\nimport { HIGH_RISK_COMMANDS } from '../gateway/types';\n\n/** Wrapper commands that prefix the real command; hop over them and re-check. */\nconst WRAPPERS = new Set([\n 'sudo',\n 'doas',\n 'env',\n 'nohup',\n 'time',\n 'timeout',\n 'nice',\n 'ionice',\n 'stdbuf',\n 'xargs',\n 'command',\n 'exec',\n 'builtin',\n]);\n\n/** Leading environment assignment, e.g. `FOO=bar cmd` (input is lowercased). */\nconst ASSIGN_RE = /^[a-z_][a-z0-9_]*=/;\n\nconst MAX_WRAPPER_HOPS = 4;\nconst MAX_RECURSION_DEPTH = 3;\nconst MAX_COMMAND_LENGTH = 100_000;\n\n/** Hosts considered local for the curl/wget risk downgrade (brackets pre-stripped). */\nconst LOCAL_HOST_RE = /^(localhost|127\\.0\\.0\\.1|::1|[a-z0-9.-]*\\.localhost)$/;\n\ninterface RiskList {\n singleWord: Set<string>;\n /** Sorted longest-first so `gh pr create` wins over a shorter prefix entry. */\n multiWord: string[][];\n}\n\nfunction buildRiskList(entries: readonly string[]): RiskList {\n const singleWord = new Set<string>();\n const multiWord: string[][] = [];\n for (const entry of entries) {\n const words = entry.toLowerCase().trim().split(/\\s+/);\n if (words.length === 1) singleWord.add(words[0]);\n else multiWord.push(words);\n }\n multiWord.sort((a, b) => b.length - a.length);\n return { singleWord, multiWord };\n}\n\n/** Drop heredoc bodies (`<<TAG` … `TAG`) — stdin text, not executed commands. */\nfunction stripHeredocBodies(text: string): string {\n const lines = text.split('\\n');\n const kept: string[] = [];\n let terminator: string | null = null;\n for (const line of lines) {\n if (terminator !== null) {\n if (line.trim() === terminator) terminator = null;\n continue;\n }\n kept.push(line);\n const heredoc = line.match(/<<-?\\s*['\"]?(\\w+)['\"]?/);\n if (heredoc) terminator = heredoc[1];\n }\n return kept.join('\\n');\n}\n\n/**\n * Pull out `$(…)` command substitutions (recursing into their contents via\n * `collect`) and blank their spans. No quote awareness inside the parens —\n * pragmatic depth counting only.\n */\nfunction extractDollarSubstitutions(text: string, collect: (inner: string) => void): string {\n let result = text;\n let start = result.indexOf('$(');\n let iterations = 0;\n while (start !== -1 && iterations < 50) {\n iterations++;\n let depth = 0;\n let end = result.length;\n for (let i = start + 1; i < result.length; i++) {\n if (result[i] === '(') depth++;\n else if (result[i] === ')') {\n depth--;\n if (depth === 0) {\n end = i;\n break;\n }\n }\n }\n collect(result.slice(start + 2, end));\n result = `${result.slice(0, start)} ${result.slice(Math.min(end + 1, result.length))}`;\n start = result.indexOf('$(');\n }\n return result;\n}\n\n/** Pull out backtick substitutions (odd chunks when split on `) and blank them. */\nfunction extractBacktickSubstitutions(text: string, collect: (inner: string) => void): string {\n if (!text.includes('`')) return text;\n const chunks = text.split('`');\n const kept: string[] = [];\n for (let i = 0; i < chunks.length; i++) {\n if (i % 2 === 1) collect(chunks[i]);\n else kept.push(chunks[i]);\n }\n return kept.join(' ');\n}\n\n/**\n * Reduce a command token to its matchable name: strip any leading path\n * (both separator styles — PowerShell targets arrive with backslashes) and\n * a trailing Windows executable extension, so `C:\\Tools\\curl.exe` and\n * `curl.exe` both match the `curl` high-risk entry.\n */\nfunction basename(token: string): string {\n const slash = Math.max(token.lastIndexOf('/'), token.lastIndexOf('\\\\'));\n const name = slash === -1 ? token : token.slice(slash + 1);\n return name.replace(/\\.(exe|cmd|bat|ps1)$/i, '');\n}\n\nfunction matchSegment(segment: string, list: RiskList): string | null {\n const tokens = segment.split(/\\s+/).filter((t) => /[a-z0-9]/.test(t));\n let i = 0;\n let hops = 0;\n while (i < tokens.length) {\n // Skip `export` and leading env assignments to reach the command position.\n while (i < tokens.length && (tokens[i] === 'export' || ASSIGN_RE.test(tokens[i]))) i++;\n if (i >= tokens.length) return null;\n\n const cmd = basename(tokens[i]);\n\n // Multi-word entries match against the leading non-flag tokens.\n const significant = [cmd, ...tokens.slice(i + 1).filter((t) => !t.startsWith('-'))];\n for (const entry of list.multiWord) {\n if (entry.length <= significant.length && entry.every((w, j) => significant[j] === w)) {\n return entry.join(' ');\n }\n }\n if (list.singleWord.has(cmd)) return cmd;\n\n // High-risk check ran first, so `sudo …` matches `sudo` before hopping.\n if (WRAPPERS.has(cmd) && hops < MAX_WRAPPER_HOPS) {\n hops++;\n i++;\n while (i < tokens.length && (tokens[i].startsWith('-') || ASSIGN_RE.test(tokens[i]))) i++;\n continue;\n }\n return null;\n }\n return null;\n}\n\nfunction collectMatches(\n command: string,\n list: RiskList,\n matches: Set<string>,\n depth: number\n): void {\n if (depth > MAX_RECURSION_DEPTH || command.length > MAX_COMMAND_LENGTH) return;\n\n let text = command.toLowerCase();\n text = stripHeredocBodies(text);\n\n // Substitutions are extracted (and their contents checked) BEFORE quote\n // stripping so `\"$(curl x)\"` is still evaluated in command position.\n const recurse = (inner: string) => collectMatches(inner, list, matches, depth + 1);\n text = extractDollarSubstitutions(text, recurse);\n text = extractBacktickSubstitutions(text, recurse);\n\n // Quoted spans are data, not commands; `#` comments are noise.\n text = text.replace(/'[^']*'/g, ' ').replace(/\"[^\"]*\"/g, ' ');\n text = text.replace(/(^|\\s)#[^\\n]*/g, ' ');\n\n const segments = text.split(/\\|\\||&&|[;|&\\n]|[({]/);\n for (const segment of segments) {\n const matched = matchSegment(segment, list);\n if (matched) matches.add(matched);\n }\n}\n\n/**\n * Returns every high-risk entry invoked in command position anywhere in\n * `command` (deduplicated), or an empty array.\n */\nexport function matchHighRiskCommands(\n command: string,\n highRiskCommands: readonly string[] = HIGH_RISK_COMMANDS\n): string[] {\n if (!command) return [];\n const matches = new Set<string>();\n collectMatches(command, buildRiskList(highRiskCommands), matches, 0);\n return [...matches];\n}\n\n/**\n * Returns the first high-risk entry invoked in command position, or null.\n * Convenience wrapper over {@link matchHighRiskCommands}.\n */\nexport function matchHighRiskCommand(\n command: string,\n highRiskCommands: readonly string[] = HIGH_RISK_COMMANDS\n): string | null {\n return matchHighRiskCommands(command, highRiskCommands)[0] ?? null;\n}\n\n/**\n * True when the command's network destinations are exclusively local\n * (localhost / 127.0.0.1 / ::1 / *.localhost) AND at least one destination is\n * present. Used to skip the risk-80 bump for curl/wget dev-loop probes.\n * Unparseable or absent destinations keep the bump (fail toward review).\n */\nexport function isLocalhostOnlyNetworkCommand(command: string): boolean {\n const text = command.toLowerCase();\n // Candidate destinations: explicit URLs, host:port tokens, or bare local hosts.\n const tokens = text.split(/\\s+/).map((t) => t.replace(/^[\"'`]+|[\"'`,;)]+$/g, ''));\n let localCount = 0;\n for (const token of tokens) {\n let host: string | null = null;\n if (token.includes('://')) {\n let rest = token.slice(token.indexOf('://') + 3);\n const at = rest.indexOf('@');\n if (at !== -1) rest = rest.slice(at + 1);\n host = rest.split(/[/?#]/)[0];\n } else if (/^[a-z0-9.[\\]:-]+:\\d+(\\/.*)?$/.test(token)) {\n host = token.split('/')[0];\n } else if (/^(localhost|127\\.0\\.0\\.1|\\[?::1\\]?)(\\/.*)?$/.test(token)) {\n host = token.split('/')[0];\n }\n if (host === null) continue;\n // Strip a trailing numeric port, then IPv6 brackets ([::1]:3000 → ::1).\n const bare = host.replace(/:\\d+$/, '').replace(/^\\[|\\]$/g, '');\n if (LOCAL_HOST_RE.test(bare)) {\n localCount++;\n } else {\n return false;\n }\n }\n return localCount > 0;\n}\n","/**\n * Local PDLSS Evaluator\n *\n * Evaluates agent actions against a local PDLSS policy.\n * Same logic as the cloud evaluator but runs in-process with no I/O.\n *\n * Evaluation order:\n * 1. Purpose: find matching rule, check if allowed\n * 2. Purpose: check allowed targets (if specified)\n * 3. Purpose: check blocked patterns (if specified)\n * 4. Scope: check global blocked resources/domains\n * 5. Limits: check transaction/rate limits\n * 6. Risk thresholds + approval requirements\n */\n\nimport type {\n LocalPolicy,\n PDLSSContext,\n VerificationDecision,\n LocalPurposeRule,\n} from '../gateway/types';\nimport { SENSITIVE_PATHS } from '../gateway/types';\nimport { matchHighRiskCommands, isLocalhostOnlyNetworkCommand } from './command-matcher';\n\nexport class LocalEvaluator {\n private policy: LocalPolicy;\n private requestCounts: Map<string, { count: number; windowStart: number }> = new Map();\n\n constructor(policy: LocalPolicy) {\n this.policy = policy;\n }\n\n /**\n * Update the policy (e.g. after hot-reload or sync).\n */\n updatePolicy(policy: LocalPolicy): void {\n this.policy = policy;\n }\n\n /**\n * Evaluate an action context against the loaded policy.\n */\n evaluate(context: PDLSSContext): VerificationDecision {\n // 1. Purpose: find matching rule\n const purposeRule = this.policy.purposes.find((p) => p.id === context.purpose);\n if (!purposeRule) {\n return { recommendation: 'DENY', reason: 'Purpose not in policy' };\n }\n if (!purposeRule.allowed) {\n return { recommendation: 'DENY', reason: 'Purpose explicitly blocked' };\n }\n\n // 2. Purpose: check allowed targets\n if (purposeRule.targets && !this.matchesAnyPattern(context.target, purposeRule.targets)) {\n return { recommendation: 'DENY', reason: 'Target not in allowed list' };\n }\n\n // 3. Purpose: check blocked patterns\n if (\n purposeRule.blockedPatterns &&\n this.matchesAnyPattern(context.target, purposeRule.blockedPatterns)\n ) {\n return { recommendation: 'DENY', reason: 'Target matches blocked pattern' };\n }\n\n // 4. Scope: check global blocked resources/domains\n const scopeBlock = this.checkScopeBlock(context);\n if (scopeBlock) {\n return { recommendation: 'DENY', reason: scopeBlock };\n }\n\n // 5. Limits: check transaction and rate limits\n const limitViolation = this.checkLimits(context);\n if (limitViolation) {\n return { recommendation: 'DENY', reason: limitViolation };\n }\n\n // 6. Self-instantiation: check sub-agent spawning rules\n if (context.purpose === 'sub_agent.spawn' && this.policy.selfInstantiation) {\n if (!this.policy.selfInstantiation.allowed) {\n return { recommendation: 'DENY', reason: 'Sub-agent spawning is not allowed' };\n }\n const depth = (context.metadata?.subAgentDepth as number) || 0;\n if (\n this.policy.selfInstantiation.maxDepth !== undefined &&\n depth >= this.policy.selfInstantiation.maxDepth\n ) {\n return {\n recommendation: 'DENY',\n reason: `Sub-agent depth ${depth} exceeds max depth ${this.policy.selfInstantiation.maxDepth}`,\n };\n }\n }\n\n // 7. Risk thresholds + approval requirements\n if (purposeRule.requiresApproval) {\n return { recommendation: 'MANUAL_REVIEW', reason: 'Purpose requires approval' };\n }\n\n const riskDecision = this.checkRiskThresholds(context);\n if (riskDecision) {\n return riskDecision;\n }\n\n return {\n recommendation: 'ALLOW',\n // \"evaluated\": dimensions absent from the policy are skipped, not\n // failed — evaluatedDimensions below records which ones ran.\n reason: 'All evaluated PDLSS checks passed',\n evaluatedDimensions: {\n purpose: true,\n scope: !!this.policy.scope,\n limits: !!this.policy.limits,\n riskThresholds: !!this.policy.riskThresholds,\n },\n };\n }\n\n private checkScopeBlock(context: PDLSSContext): string | null {\n const scope = this.policy.scope;\n if (!scope) return null;\n\n // Check blocked domains against target and network access\n if (scope.blockedDomains) {\n const targetDomain = this.extractDomain(context.target);\n if (this.matchesAnyPattern(targetDomain, scope.blockedDomains)) {\n return `Target blocked by scope: ${context.target}`;\n }\n if (context.networkAccess) {\n for (const domain of context.networkAccess) {\n if (this.matchesAnyPattern(this.extractDomain(domain), scope.blockedDomains)) {\n return `Domain blocked by scope: ${domain}`;\n }\n }\n }\n }\n\n // Check blocked resources against target\n if (scope.blockedResources && this.matchesAnyPattern(context.target, scope.blockedResources)) {\n return `Resource blocked by scope: ${context.target}`;\n }\n\n // Check allowed domains (if specified, target must match)\n if (scope.allowedDomains && context.networkAccess) {\n for (const domain of context.networkAccess) {\n if (!this.matchesAnyPattern(this.extractDomain(domain), scope.allowedDomains)) {\n return `Domain not in allowed list: ${domain}`;\n }\n }\n }\n\n return null;\n }\n\n private checkLimits(context: PDLSSContext): string | null {\n const limits = this.policy.limits;\n if (!limits) return null;\n\n // Transaction amount check\n if (limits.maxTransactionAmount !== undefined && context.transactionValue !== undefined) {\n if (context.transactionValue > limits.maxTransactionAmount) {\n return `Transaction value ${context.transactionValue} exceeds limit ${limits.maxTransactionAmount}`;\n }\n }\n\n // Rate limit check\n if (limits.maxRequestsPerHour !== undefined) {\n const key = context.purpose;\n const now = Date.now();\n const entry = this.requestCounts.get(key);\n const hourMs = 3600000;\n\n if (!entry || now - entry.windowStart > hourMs) {\n this.requestCounts.set(key, { count: 1, windowStart: now });\n } else {\n entry.count++;\n if (entry.count > limits.maxRequestsPerHour) {\n return `Rate limit exceeded: ${entry.count}/${limits.maxRequestsPerHour} requests per hour`;\n }\n }\n }\n\n return null;\n }\n\n private checkRiskThresholds(context: PDLSSContext): VerificationDecision | null {\n if (!this.policy.riskThresholds) return null;\n\n const riskScore = this.calculateRiskScore(context);\n const thresholds = this.policy.riskThresholds;\n\n if (riskScore >= thresholds.autoBlock.min) {\n return { recommendation: 'DENY', reason: `Risk score ${riskScore} exceeds block threshold` };\n }\n\n if (riskScore >= thresholds.requireApproval.min) {\n return {\n recommendation: 'MANUAL_REVIEW',\n reason: `Risk score ${riskScore} requires approval`,\n };\n }\n\n return null;\n }\n\n private calculateRiskScore(context: PDLSSContext): number {\n let score = 0;\n\n // Explicit risk factors take priority (highest severity wins)\n if (context.riskFactors?.length) {\n const severityScores: Record<string, number> = {\n low: 10,\n medium: 40,\n high: 70,\n critical: 90,\n };\n\n for (const factor of context.riskFactors) {\n const factorScore = severityScores[factor.severity] || 0;\n if (factorScore > score) score = factorScore;\n }\n return score;\n }\n\n // Auto-detect risk from high-risk shell commands (command-position match,\n // not substring). curl/wget aimed exclusively at localhost stay below the\n // review threshold — dev-loop probes, not exfiltration surface.\n if (context.purpose === 'shell.exec' && context.target) {\n const matched = matchHighRiskCommands(context.target);\n const localProbeOnly =\n matched.length > 0 &&\n matched.every((cmd) => cmd === 'curl' || cmd === 'wget') &&\n isLocalhostOnlyNetworkCommand(context.target);\n if (matched.length > 0 && !localProbeOnly) {\n score = Math.max(score, 80);\n }\n }\n\n // Auto-detect risk from sensitive file paths (score 50 = review range).\n // Separators normalized so entries containing '/' (e.g. '.git/config')\n // match Windows backslash targets — same class as the matchGlob fix.\n if (\n (context.purpose === 'file.read' ||\n context.purpose === 'file.write' ||\n context.purpose === 'file.delete') &&\n context.target\n ) {\n const targetLower = context.target.toLowerCase().replace(/\\\\/g, '/');\n for (const sensitivePath of SENSITIVE_PATHS) {\n if (targetLower.includes(sensitivePath.toLowerCase())) {\n score = Math.max(score, 50);\n break;\n }\n }\n }\n\n return score;\n }\n\n /**\n * Extract domain from a URL-like string (strips protocol and path).\n */\n private extractDomain(value: string): string {\n let domain = value;\n // Strip protocol\n const protoIndex = domain.indexOf('://');\n if (protoIndex !== -1) domain = domain.slice(protoIndex + 3);\n // Strip path\n const slashIndex = domain.indexOf('/');\n if (slashIndex !== -1) domain = domain.slice(0, slashIndex);\n // Strip port\n const colonIndex = domain.indexOf(':');\n if (colonIndex !== -1) domain = domain.slice(0, colonIndex);\n return domain;\n }\n\n /**\n * Check if a value matches any of the given glob patterns.\n */\n private matchesAnyPattern(value: string, patterns: string[]): boolean {\n return patterns.some((pattern) => this.matchGlob(value, pattern));\n }\n\n /**\n * Simple glob pattern matching.\n * Supports * (matches any characters including / and spaces) and ? (single char).\n * Uses the same approach as the backend's matchGlobPattern.\n */\n /**\n * Home directory as a forward-slash prefix for `~` pattern expansion,\n * resolved from the environment WITHOUT importing `os` (this evaluator\n * also runs bundled inside the browser extension, where node builtins\n * don't exist — there `~` patterns stay literal, which is correct since\n * browser targets are URLs). Cached per instance.\n */\n private cachedHomePrefix: string | null | undefined;\n\n private homePrefix(): string | null {\n if (this.cachedHomePrefix !== undefined) return this.cachedHomePrefix;\n const env = typeof process !== 'undefined' ? process.env : undefined;\n const home = env?.HOME || env?.USERPROFILE;\n this.cachedHomePrefix = home ? home.replace(/\\\\/g, '/') : null;\n return this.cachedHomePrefix;\n }\n\n private matchGlob(value: string, pattern: string): boolean {\n // Two live bypasses found 11 Jul 2026 (Windows desktop verification):\n // 1. Windows targets arrive with backslashes (C:\\Users\\...\\settings.json)\n // and never matched forward-slash glob patterns — EVERY file-path\n // protection was dead on native Windows. Normalize separators on\n // both sides so Windows-authored patterns keep working too.\n // 2. `~/...` patterns were never expanded, so the default policy's\n // credential-path protections (~/.ssh/*, ~/.aws/*) matched nothing\n // on ANY platform. Expand a leading `~` to the home directory.\n value = value.replace(/\\\\/g, '/');\n pattern = pattern.replace(/\\\\/g, '/');\n if (pattern === '~' || pattern.startsWith('~/')) {\n const home = this.homePrefix();\n if (home) pattern = home + pattern.slice(1);\n }\n\n // Exact match\n if (pattern === value) return true;\n\n // Audit F-A1-13: ReDoS guard. Patterns with too many `*` segments\n // produce catastrophic-backtracking regexes (e.g. `a*a*a*a*a*a*a*a*b`\n // against `aaaaaaaa` runs in exponential time). Local policies are\n // operator-controlled so attacker access requires operator-side\n // compromise, but a copy-pasted-from-internet example pattern could\n // still DoS the gateway when matched against request input.\n const starCount = (pattern.match(/\\*/g) ?? []).length;\n if (starCount > 8) {\n return false;\n }\n\n // Convert glob to regex\n const regexStr = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special regex chars (except * and ?)\n .replace(/\\*/g, '.*') // * matches anything\n .replace(/\\?/g, '.'); // ? matches single char\n\n try {\n return new RegExp(`^${regexStr}$`, 'i').test(value);\n } catch {\n return false;\n }\n }\n}\n\n/**\n * Convenience: find a matching purpose rule.\n */\nexport function findPurposeRule(\n policy: LocalPolicy,\n purposeId: string\n): LocalPurposeRule | undefined {\n return policy.purposes.find((p) => p.id === purposeId);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -84,7 +84,12 @@ interface VerificationDecision {
|
|
|
84
84
|
trustScore?: number;
|
|
85
85
|
tokenGuidance?: TokenGuidance;
|
|
86
86
|
sessionId?: string;
|
|
87
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* PDLSS dimensions that were evaluated. `true` = the dimension was
|
|
89
|
+
* configured in the policy and therefore ran; `false` = the dimension is
|
|
90
|
+
* absent from the policy and was skipped — NOT "failed" (a failed
|
|
91
|
+
* dimension never produces an ALLOW; this field is only set on ALLOW).
|
|
92
|
+
*/
|
|
88
93
|
evaluatedDimensions?: {
|
|
89
94
|
purpose: boolean;
|
|
90
95
|
scope: boolean;
|
|
@@ -84,7 +84,12 @@ interface VerificationDecision {
|
|
|
84
84
|
trustScore?: number;
|
|
85
85
|
tokenGuidance?: TokenGuidance;
|
|
86
86
|
sessionId?: string;
|
|
87
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* PDLSS dimensions that were evaluated. `true` = the dimension was
|
|
89
|
+
* configured in the policy and therefore ran; `false` = the dimension is
|
|
90
|
+
* absent from the policy and was skipped — NOT "failed" (a failed
|
|
91
|
+
* dimension never produces an ALLOW; this field is only set on ALLOW).
|
|
92
|
+
*/
|
|
88
93
|
evaluatedDimensions?: {
|
|
89
94
|
purpose: boolean;
|
|
90
95
|
scope: boolean;
|