{"version":3,"file":"trust.d.ts","sourceRoot":"","sources":["../../../src/core/canvas/trust.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAEhE,gFAAgF;AAChF,qBAAa,gBAAiB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,YAAY,WAAW,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAQ3C;CACD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,yBAAyB,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAElG;AAuBD;;;GAGG;AACH,wBAAgB,oBAAoB,CACnC,SAAS,EAAE,yBAAyB,EACpC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAsB,GAC9B,OAAO,CAET;AAED,uFAAuF;AACvF,MAAM,WAAW,uBAAuB;IACvC,SAAS,EAAE,yBAAyB,CAAC;IACrC,MAAM,EAAE,qBAAqB,CAAC;CAC9B;AAED,4EAA4E;AAC5E,MAAM,WAAW,qBAAqB;IACrC,QAAQ,EAAE,yBAAyB,EAAE,CAAC;IACtC,QAAQ,EAAE,uBAAuB,EAAE,CAAC;CACpC;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CACnC,UAAU,EAAE,yBAAyB,EAAE,EACvC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAsB,GAC9B,qBAAqB,CAUvB","sourcesContent":["/**\n * Trust gating for canvas extensions.\n *\n * Design: `docs/canvas-extensions-design.md` §5. The argument is already written\n * down in `core/extensions/plugins/trust.ts`, for the same reason:\n *\n * > Its skills and commands are text the model reads, which is no worse than\n * > reading the repository itself, but its **hooks and MCP servers are processes**\n * > that start on session load.\n *\n * A canvas extension is a process **that also opens a listening socket**, so it\n * belongs in that record on identical grounds and needs no new mechanism. The gate\n * itself is shared: `isRepositorySupplied` and `shouldWithholdRepositorySupplied`\n * live in `plugins/trust.ts` and are used by the plugin gate too, so this module\n * supplies only the roots that are specific to canvas extensions. The record lives\n * outside the repository, so repository content cannot forge it.\n *\n * One difference from plugins, and it matters. `shouldWithholdExecutables` can\n * withhold a plugin's hooks and MCP servers while still loading its skills,\n * because a plugin has passive capabilities worth having. **A canvas has none.**\n * Its declaration, its actions, and its UI all come from running its code. There\n * is nothing to partially allow, so an untrusted canvas is withheld whole.\n *\n * What stays available is discovery: `discovery.ts` only reads directory entries,\n * so an untrusted canvas can still be listed, named, and offered — the person can\n * see what is on offer and decide to trust the workspace. Listing is not running.\n */\n\nimport * as path from \"node:path\";\nimport { CONFIG_DIR_NAME, getAgentDir } from \"../../config.js\";\nimport { isRepositorySupplied, shouldWithholdRepositorySupplied } from \"../extensions/plugins/trust.js\";\nimport type { DiscoveredCanvasExtension } from \"./discovery.js\";\n\n/** Thrown when something tries to run a canvas the workspace has not earned. */\nexport class CanvasTrustError extends Error {\n\treadonly extensionId: string;\n\n\tconstructor(extensionId: string, cwd: string) {\n\t\tsuper(\n\t\t\t`Canvas extension \"${extensionId}\" came with this repository and \"${cwd}\" is not a trusted workspace. ` +\n\t\t\t\t`Running it would start a process and open a listening socket on your machine. ` +\n\t\t\t\t`Trust the workspace first if that is what you want.`,\n\t\t);\n\t\tthis.name = \"CanvasTrustError\";\n\t\tthis.extensionId = extensionId;\n\t}\n}\n\n/**\n * Whether an extension sits in the working tree, and therefore arrived with the\n * repository as far as anyone but its author can tell.\n *\n * Every project-scope home counts — see {@link canvasProjectScopeRoots}. None of\n * them can distinguish \"I put this here\" from \"this arrived in the clone\", which\n * is exactly why location is not the question being asked; it only decides\n * *whether* to ask about trust.\n *\n * User scope (`~/.copilot/extensions/`) is not project-supplied: it is outside any\n * repository and got there by a deliberate local act.\n */\nexport function isProjectSuppliedCanvas(extension: DiscoveredCanvasExtension, cwd: string): boolean {\n\treturn isRepositorySupplied(extension.dir, canvasProjectScopeRoots(cwd));\n}\n\n/**\n * A canvas extension's project-scope homes — the locations that travel with a\n * clone.\n *\n * The plugin homes belong here for the same reason `loader.ts` counts them for\n * plugins: a plugin installed at project scope, or committed into the working\n * tree, arrives in every collaborator's clone, and a canvas it ships is a\n * process with a listening socket exactly like one dropped in `.github/`. The\n * person who ran the install knows they chose it; nobody who clones the result\n * does, and location cannot tell those two apart.\n */\nfunction canvasProjectScopeRoots(cwd: string): string[] {\n\treturn [\n\t\tpath.join(cwd, \".agents\", \"extensions\"),\n\t\tpath.join(cwd, \".github\", \"extensions\"),\n\t\tpath.join(cwd, \".agents\", \"plugins\"),\n\t\tpath.join(cwd, CONFIG_DIR_NAME, \"plugins\"),\n\t\tpath.join(cwd, \".claude\", \"skills\"),\n\t];\n}\n\n/**\n * Whether an extension must not be forked: it came with the repository and this\n * machine has not trusted the workspace.\n */\nexport function shouldWithholdCanvas(\n\textension: DiscoveredCanvasExtension,\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n): boolean {\n\treturn shouldWithholdRepositorySupplied(extension.dir, cwd, canvasProjectScopeRoots(cwd), agentDir);\n}\n\n/** A withheld extension, with the reason, so a caller can explain rather than hide. */\nexport interface WithheldCanvasExtension {\n\textension: DiscoveredCanvasExtension;\n\treason: \"untrusted-workspace\";\n}\n\n/** Discovered extensions split into what may be forked and what may not. */\nexport interface GatedCanvasExtensions {\n\trunnable: DiscoveredCanvasExtension[];\n\twithheld: WithheldCanvasExtension[];\n}\n\n/**\n * Partition discovered extensions by trust.\n *\n * Callers should present `withheld` rather than dropping it: the point of the gate\n * is that the person can see a repository offers a canvas and choose, not that the\n * offer disappears.\n */\nexport function gateCanvasExtensions(\n\textensions: DiscoveredCanvasExtension[],\n\tcwd: string,\n\tagentDir: string = getAgentDir(),\n): GatedCanvasExtensions {\n\tconst gated: GatedCanvasExtensions = { runnable: [], withheld: [] };\n\tfor (const extension of extensions) {\n\t\tif (shouldWithholdCanvas(extension, cwd, agentDir)) {\n\t\t\tgated.withheld.push({ extension, reason: \"untrusted-workspace\" });\n\t\t} else {\n\t\t\tgated.runnable.push(extension);\n\t\t}\n\t}\n\treturn gated;\n}\n"]}