{"record":{"id":"03b06d266dba79d9","repo":"moeru-ai/airi","slug":"imported-extension-entrypoints-must-be-relative-paths","errorCode":null,"errorMessage":"Imported Extension entrypoints must be relative paths: ${entrypoint}","messagePattern":"Imported Extension entrypoints must be relative paths: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts","lineNumber":239,"sourceCode":"  }\n  catch (error) {\n    if (error instanceof SyntaxError) {\n      throw new Error(`Extension manifest is not valid JSON: ${error.message}`)\n    }\n    throw error\n  }\n\n  const parsedManifest = parseExtensionManifest(rawManifest)\n  if (!parsedManifest.success) {\n    throw new Error(`Extension manifest is invalid: ${formatManifestDiagnostics(parsedManifest.diagnostics)}`)\n  }\n\n  for (const entrypoint of Object.values(parsedManifest.manifest.entrypoints)) {\n    if (!entrypoint) {\n      continue\n    }\n    if (isAbsolute(entrypoint)) {\n      throw new Error(`Imported Extension entrypoints must be relative paths: ${entrypoint}`)\n    }\n    const resolvedEntrypoint = resolve(sourceRealPath, entrypoint)\n    if (!isContainedPath(sourceRealPath, resolvedEntrypoint)) {\n      throw new Error(`Extension entrypoint escapes the package folder: ${entrypoint}`)\n    }\n    await assertRegularFile(resolvedEntrypoint, 'Extension entrypoint')\n    const entrypointRealPath = await realpath(resolvedEntrypoint)\n    if (!isContainedPath(sourceRealPath, entrypointRealPath)) {\n      throw new Error(`Extension entrypoint resolves outside the package folder: ${entrypoint}`)\n    }\n  }\n\n  const fingerprint = createHash('sha256')\n  for (const directory of directories) {\n    fingerprint.update(`directory\\0${directory}\\0`)\n  }\n  for (const file of files) {\n    fingerprint.update(`file\\0${file.relativePath}\\0${file.size}\\0`)","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/moeru-ai/airi/blob/438a067dde47aa0bdb46c2323d1fe293dc805218/apps/stage-tamagotchi/src/main/services/airi/plugins/host/directory-import.ts#L221-L257","documentation":"When importing an unpacked Extension folder, AIRI inspects the manifest and validates every entrypoint listed in it. Entry points must be relative paths inside the package directory so they can be resolved and sandboxed against the imported folder's real path. This error is thrown by `inspectExtensionDirectory` when a manifest entrypoint is an absolute path (e.g. starts with `/` or a drive letter), which would bypass the package containment check.","triggerScenarios":"Call `inspected`/`staged` on a directory whose manifest `entrypoints` values contain an absolute path (e.g. `\"entrypoints\": { \"main\": \"/home/user/extension/main.js\" }` or `\"C:\\\\ext\\\\index.js\"`). The manifest parses fine, but `isAbsolute(entrypoint)` is true for one of the values.","commonSituations":"Authors generating the manifest programmatically and accidentally writing an absolute filesystem path; hand-editing a manifest copied from another machine; build tooling resolving entrypoints to absolute paths before emitting the manifest; copying a manifest from a zip extraction log that recorded full paths.","solutions":["Open the extension's manifest and rewrite each entrypoint as a path relative to the package root (e.g. `./index.js`, `src/main.js`).","If a build tool generates the manifest, configure it to emit relative paths (or strip the package root prefix before writing entrypoints).","Re-select the folder in AIRI after fixing the manifest so inspection re-runs."],"exampleFix":"// before (manifest)\n{ \"entrypoints\": { \"main\": \"/Users/me/Downloads/my-ext/main.js\" } }\n// after\n{ \"entrypoints\": { \"main\": \"./main.js\" } }","handlingStrategy":"validation","validationCode":"import { resolve, isAbsolute } from 'node:path'\nfunction validateManifestEntrypoints(manifest, packageRoot) {\n  for (const [name, entrypoint] of Object.entries(manifest.entrypoints ?? {})) {\n    if (!entrypoint) continue\n    if (isAbsolute(entrypoint))\n      throw new Error(`entrypoint '${name}' must be relative: ${entrypoint}`)\n    if (!resolve(packageRoot, entrypoint).startsWith(packageRoot))\n      throw new Error(`entrypoint '${name}' escapes package root: ${entrypoint}`)\n  }\n}","typeGuard":"const isRelativeEntrypoint = (p) => typeof p === 'string' && p.length > 0 && !isAbsolute(p)","tryCatchPattern":"try {\n  await importExtension(folder)\n} catch (err) {\n  if (String(err.message).includes('must be relative paths')) {\n    showHint('Fix manifest entrypoints to be relative to the package root, then re-select the folder.')\n  } else throw err\n}","preventionTips":["Always write manifest entrypoints as paths relative to the package root.","Lint manifests in CI to reject absolute entrypoint paths before packaging.","Configure generators/builders to emit relative paths, stripping any root prefix."],"tags":["extensions","manifest","path-validation","import"],"backgroundTag":"path-traversal-blocked","analyzedSha":"438a067dde47aa0bdb46c2323d1fe293dc805218","analyzedAt":"2026-09-17T01:14:42.644Z","contentChangedAt":"2026-09-17T01:14:42.644Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}