{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/runs/shared/permissions.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,kBAAkB,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAC1D,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;AACjE,MAAM,WAAW,gBAAgB;IAChC,KAAK,CAAC,EAAE,eAAe,CAAC;CACxB;AAED,eAAO,MAAM,qBAAqB,kCAAkC,CAAC;AACrE,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAQ7E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAgBlG;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAuB,GAAG,gBAAgB,GAAG,SAAS,CAOnH;AAED,wBAAgB,sBAAsB,CACrC,YAAY,CAAC,EAAE,gBAAgB,EAC/B,UAAU,CAAC,EAAE,eAAe,GAC1B,eAAe,GAAG,SAAS,CAI7B;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,kBAAkB,CAG3G;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAM5F;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,eAAe,GAAG,SAAS,CAG9F;AAmBD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAc5D;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAIzG","sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nexport type PermissionDecision = \"allow\" | \"ask\" | \"deny\";\nexport type PermissionRules = Record<string, PermissionDecision>;\nexport interface PermissionConfig {\n\trules?: PermissionRules;\n}\n\nexport const PERMISSION_POLICY_ENV = \"PI_SUBAGENT_PERMISSION_POLICY\";\nexport const PERMISSION_AUDIT_PATH_ENV = \"PI_SUBAGENT_PERMISSION_AUDIT_PATH\";\nconst INTERNAL_TOOLS = new Set([\"contact_supervisor\", \"intercom\", \"subagent_wait\", \"structured_output\"]);\nconst DECISIONS = new Set<PermissionDecision>([\"allow\", \"ask\", \"deny\"]);\nconst MAX_POLICY_BYTES = 16 * 1024;\nconst MAX_PREVIEW_BYTES = 2048;\nconst SECRET_KEY = /(?:authorization|cookie|credential|password|secret|token|api[-_]?key)/i;\nconst SECRET_VALUE = /\\b(?:Bearer\\s+\\S+|(?:sk|ghp|github_pat|xox[baprs])[-_A-Za-z0-9]{8,})\\b/gi;\n\nexport function validatePermissionRules(value: unknown, label: string): PermissionRules | undefined {\n\tif (value === undefined) return undefined;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(`${label} must be an object mapping tool names to allow, ask, or deny.`);\n\tconst result: PermissionRules = {};\n\tfor (const [tool, decision] of Object.entries(value)) {\n\t\tif (!tool.trim()) throw new Error(`${label} contains an empty tool name.`);\n\t\tif (tool === \"bash\")\n\t\t\tthrow new Error(`${label}.bash is unsupported; pi-subagents leaves bash policy to pi-guard.`);\n\t\tif (INTERNAL_TOOLS.has(tool))\n\t\t\tthrow new Error(`${label}.${tool} is reserved for child coordination and cannot be gated.`);\n\t\tif (!DECISIONS.has(decision as PermissionDecision))\n\t\t\tthrow new Error(`${label}.${tool} must be allow, ask, or deny.`);\n\t\tresult[tool] = decision as PermissionDecision;\n\t}\n\treturn Object.keys(result).length ? result : undefined;\n}\n\nexport function validatePermissionConfig(value: unknown, label = \"config.permissions\"): PermissionConfig | undefined {\n\tif (value === undefined) return undefined;\n\tif (!value || typeof value !== \"object\" || Array.isArray(value)) throw new Error(`${label} must be an object.`);\n\tconst object = value as Record<string, unknown>;\n\tconst unknown = Object.keys(object).filter((key) => key !== \"rules\");\n\tif (unknown.length) throw new Error(`${label} has unsupported fields: ${unknown.join(\", \")}.`);\n\treturn { rules: validatePermissionRules(object.rules, `${label}.rules`) };\n}\n\nexport function resolvePermissionRules(\n\tglobalConfig?: PermissionConfig,\n\tagentRules?: PermissionRules,\n): PermissionRules | undefined {\n\tconst merged = { ...(globalConfig?.rules ?? {}), ...(agentRules ?? {}) };\n\tfor (const [tool, decision] of Object.entries(merged)) if (decision === \"allow\") delete merged[tool];\n\treturn Object.keys(merged).length ? merged : undefined;\n}\n\nexport function permissionDecision(rules: PermissionRules | undefined, toolName: string): PermissionDecision {\n\tif (toolName === \"bash\" || INTERNAL_TOOLS.has(toolName)) return \"allow\";\n\treturn rules?.[toolName] ?? \"allow\";\n}\n\nexport function encodePermissionRules(rules: PermissionRules | undefined): string | undefined {\n\tif (!rules || Object.keys(rules).length === 0) return undefined;\n\tconst encoded = JSON.stringify(rules);\n\tif (Buffer.byteLength(encoded, \"utf-8\") > MAX_POLICY_BYTES)\n\t\tthrow new Error(\"Resolved permission policy is too large.\");\n\treturn encoded;\n}\n\nexport function decodePermissionRules(encoded: string | undefined): PermissionRules | undefined {\n\tif (!encoded?.trim()) return undefined;\n\treturn validatePermissionRules(JSON.parse(encoded), PERMISSION_POLICY_ENV);\n}\n\nfunction redact(value: unknown, key = \"\", depth = 0): unknown {\n\tif (SECRET_KEY.test(key)) return \"[redacted]\";\n\tif (depth >= 3) return \"[truncated]\";\n\tif (Array.isArray(value)) return value.slice(0, 10).map((item) => redact(item, \"\", depth + 1));\n\tif (value && typeof value === \"object\")\n\t\treturn Object.fromEntries(\n\t\t\tObject.entries(value as Record<string, unknown>)\n\t\t\t\t.slice(0, 20)\n\t\t\t\t.map(([entryKey, entryValue]) => [entryKey, redact(entryValue, entryKey, depth + 1)]),\n\t\t);\n\tif (typeof value === \"string\") {\n\t\tconst redacted = value.replace(SECRET_VALUE, \"[redacted]\");\n\t\treturn redacted.length > 500 ? `${redacted.slice(0, 500)}…` : redacted;\n\t}\n\treturn value;\n}\n\nexport function permissionArgsPreview(input: unknown): string {\n\tconst serialized = JSON.stringify(redact(input));\n\tif (!serialized) return \"{}\";\n\tif (Buffer.byteLength(serialized, \"utf-8\") <= MAX_PREVIEW_BYTES) return serialized;\n\tconst maxContentBytes = MAX_PREVIEW_BYTES - Buffer.byteLength(\"…\", \"utf-8\");\n\tlet preview = \"\";\n\tlet previewBytes = 0;\n\tfor (const character of serialized) {\n\t\tconst characterBytes = Buffer.byteLength(character, \"utf-8\");\n\t\tif (previewBytes + characterBytes > maxContentBytes) break;\n\t\tpreview += character;\n\t\tpreviewBytes += characterBytes;\n\t}\n\treturn `${preview}…`;\n}\n\nexport function appendPermissionAudit(filePath: string | undefined, record: Record<string, unknown>): void {\n\tif (!filePath) return;\n\tfs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });\n\tfs.appendFileSync(filePath, `${JSON.stringify(record)}\\n`, { encoding: \"utf-8\", mode: 0o600 });\n}\n"]}