{"record":{"id":"80ab58abef154520","repo":"santifer/career-ops","slug":"symlink-traversal-test-skipped-e-message","errorCode":null,"errorMessage":"symlink traversal test skipped: ${e.message}","messagePattern":"symlink traversal test skipped: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"test-all.mjs","lineNumber":15172,"sourceCode":"  if (vm({ ...base, requiredEnv: ['X_TOKEN'], allowedHosts: ['api.x.com'] }) !== null) pass('a valid keyed manifest is accepted');\n  else fail('valid keyed manifest should be accepted');\n  if (vm({ ...base, entry: '../../scan.mjs' }) === null) pass('entry escaping the plugin directory is rejected (traversal guard)');\n  else fail('entry traversal should be rejected');\n  writeFileSync(join(__manifestTmp, 'outside.mjs'), 'export default {};');\n  writeFileSync(join(__manifestTmp, 'outside.md'), '# outside\\n');\n  mkdirSync(join(__manifestTmp, 'outside-dir'), { recursive: true });\n  try {\n    symlinkSync(join(__manifestTmp, 'outside.mjs'), join(__manifestTmp, 'x', 'linked-entry.mjs'));\n    symlinkSync(join(__manifestTmp, 'outside.md'), join(__manifestTmp, 'x', 'linked-skill.md'));\n    symlinkSync(join(__manifestTmp, 'outside-dir'), join(__manifestTmp, 'x', 'linked-dir'), 'dir');\n    if (vm({ ...base, entry: 'linked-entry.mjs' }) === null) pass('entry symlink escaping the plugin directory is rejected');\n    else fail('entry symlink traversal should be rejected');\n    if (vm({ ...base, skill: 'linked-skill.md' }) === null) pass('skill symlink escaping the plugin directory is rejected');\n    else fail('skill symlink traversal should be rejected');\n    if (vm({ ...base, entry: 'linked-dir/missing-entry.mjs' }) === null) pass('missing entry under an escaping symlink directory is rejected');\n    else fail('missing entry under symlink traversal should be rejected');\n  } catch (e) {\n    warn(`symlink traversal test skipped: ${e.message}`);\n  }\n  if (validateManifest({ ...base, id: 'y' }, '/tmp/x', 'x') === null) pass('manifest id must equal the directory name');\n  else fail('id != dirname should be rejected');\n  if (vm({ ...base, apiVersion: 2 }) === null) pass('unknown apiVersion is rejected (forward-compat gate)');\n  else fail('apiVersion 2 should be rejected');\n  console.warn = __origWarn;\n\n  // Build an isolated tmp project root.\n  __pluginTmp = mkdtempSync(join(tmpdir(), 'co-plugins-'));\n  mkdirSync(join(__pluginTmp, 'plugins'), { recursive: true });\n\n  // (a) BYTE-IDENTICAL no-op when config/plugins.yml is absent — and NO env mutation.\n  const beforeGemini = process.env.GEMINI_API_KEY;\n  const map = new Map([['greenhouse', { id: 'greenhouse', fetch() {} }]]);\n  await mergeProviderPlugins(map, { root: __pluginTmp });\n  if (map.size === 1 && map.get('greenhouse')) pass('mergeProviderPlugins is a no-op when config/plugins.yml is absent');\n  else fail(`merge should be a no-op without plugins.yml (size=${map.size})`);\n  if (process.env.GEMINI_API_KEY === beforeGemini) pass('no .env is read / no env mutation when plugins.yml is absent (byte-identical guarantee)');","sourceCodeStart":15154,"sourceCodeEnd":15190,"githubUrl":"https://github.com/santifer/career-ops/blob/e7abd431fce9348a95261acac9e0c14779c35df8/test-all.mjs#L15154-L15190","documentation":"The plugin-loader test suite validates that symlink-based path traversal (an entry or skill symlink escaping the plugin directory) is rejected. The traversal checks are wrapped in try/catch that swaps in a captured console.warn; any unexpected exception inside the block (e.g. filesystem without symlink support, an unexpected throw from validateEntry) downgrades the whole block to this warning instead of failing the run.","triggerScenarios":"An exception escapes the block that calls the manifest/entry validator with symlink fixtures (skill: 'linked-skill.md', entry under an escaping linked-dir) — e.g. fs.symlinkSync unsupported on the platform (Windows without privileges), or the validator throwing instead of returning null.","commonSituations":"Windows CI without Developer Mode / symlink privilege (EPERM creating symlinks), filesystems that disallow symlinks (FAT, some containers), or a validator regression that throws rather than returning a rejection verdict.","solutions":["On Windows, enable Developer Mode or run the terminal as administrator so symlink creation is permitted, or run the suite under WSL/Linux.","If the validator is throwing, fix it to return null for invalid manifests — the test expects a null verdict, not an exception.","Run on a filesystem that supports symlinks; skip acceptance is fine on exotic filesystems.","Inspect e.message in the warning to distinguish an environment limitation (EPERM) from a code bug (unexpected TypeError)."],"exampleFix":"// before (fixture setup)\nfs.symlinkSync(escapeTarget, linkedPath);\n// after\ntry { fs.symlinkSync(escapeTarget, linkedPath); } catch (e) { if (e.code === 'EPERM') return t.skip('symlinks unavailable'); throw e; }","handlingStrategy":"try-catch","validationCode":"let symlinkOk = true; try { const p = join(tmpdir(), 'st'); fs.symlinkSync('target', p); fs.unlinkSync(p); } catch (e) { symlinkOk = e.code !== 'EPERM'; } if (!symlinkOk) console.warn('symlinks unavailable; traversal tests will skip');","typeGuard":"const canSymlink = () => { try { const p = join(tmpdir(), 'sl-probe'); symlinkSync('x', p); unlinkSync(p); return true; } catch { return false; } };","tryCatchPattern":"try { assertSymlinkTraversalRejected(); } catch (e) { warn(`symlink traversal test skipped: ${e.message}`); }","preventionTips":["On Windows, enable Developer Mode or run elevated so symlinks work; or test under WSL","Validators should return a rejection verdict (null) rather than throwing on invalid input","Probe symlink capability once and skip dependent tests cleanly","Run the suite on a filesystem that supports symlinks"],"tags":["symlink","path-traversal","platform-compat","test-runner"],"backgroundTag":"path-traversal-blocked","analyzedSha":"e7abd431fce9348a95261acac9e0c14779c35df8","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}