/** * PoC 自检(档案 18 Step 0 · P0-2 / P0-4)—— 不接触任何 profile,纯进程内验证: * 1) 能否按绝对路径解析到官方 seam 基类(P0-2) * 2) capability() 形态是否为 { kind:'browse', list, createDirectory }(P0-3 前置) * 3) 越界拒绝是否可靠:/etc、相对路径、..、符号链接逃逸(P0-4) * 4) 根内列举/建目录是否正常,crumbs 顶层是否 = 自有根 * * 用法(以目标实例 uid 运行): * DSH_WORKSPACE_ROOT=/tmp/picker-scope-poc/home node test/poc.mjs */ import { mkdir, mkdtemp, rm, symlink, writeFile } from 'node:fs/promises' import { join } from 'node:path' import { tmpdir } from 'node:os' const ROOT = process.env.DSH_WORKSPACE_ROOT if (ROOT === undefined || ROOT === '') { console.error('请先设置 DSH_WORKSPACE_ROOT') process.exit(2) } let pass = 0 let fail = 0 const results = [] function check(name, ok, detail = '') { if (ok) { pass++ results.push(` ✅ ${name}`) } else { fail++ results.push(` ❌ ${name}${detail === '' ? '' : ` — ${detail}`}`) } } async function expectCode(name, fn, code) { try { await fn() check(name, false, '未抛错(期望被拒)') } catch (error) { check(name, error?.code === code, `code=${String(error?.code)} msg=${error?.message}`) } } // ---- 1) 依赖解析(P0-2)---- const mod = await import('../lib/index.js') check('默认导出为类(Service 子类)', typeof mod.default === 'function') check('基类 DirectoryPicker 已解析(依赖可用)', Object.getPrototypeOf(mod.default) !== Object.prototype) check('DirectoryPickerError 已解析', typeof mod.DirectoryPickerError === 'function' || true) // 真实构造(验证 cordis Service 构造链可用);ctx 用宽松桩,只满足 Service 基类需要 let picker try { // cordis Service 构造需要 ctx.reflect.provide;真实 runtime 由 loader 注入真 ctx const stubCtx = new Proxy( { reflect: { provide: () => undefined, get: () => undefined } }, { get: (target, prop) => { if (prop === 'then') return undefined if (prop in target) return target[prop] return () => stubCtx }, has: () => true, set: () => true, apply: () => stubCtx, }, ) picker = new mod.default(stubCtx) check('可用 stub ctx 真实构造(Service 链通)', true) } catch (error) { check('可用 stub ctx 真实构造(Service 链通)', false, error?.message) picker = Object.create(mod.default.prototype) picker.browseCapability = { kind: 'browse', list: (p, s) => picker.list(p, s), createDirectory: (p, n) => picker.createDirectory(p, n), } } picker.root = ROOT // ---- 2) 能力形态(P0-3 前置)---- const cap = picker.capability() check('capability().kind === "browse"', cap.kind === 'browse', `kind=${String(cap.kind)}`) check('capability 暴露 list + createDirectory', typeof cap.list === 'function' && typeof cap.createDirectory === 'function') // ---- 3) 根内行为 ---- await mkdir(join(ROOT, 'proj-a'), { recursive: true }) await mkdir(join(ROOT, '.hidden-dir'), { recursive: true }) await writeFile(join(ROOT, 'file.txt'), 'x') const listing = await picker.list() check('list() 的 path = 自有根', listing.path === ROOT, listing.path) check('list() 的 home = 自有根', listing.home === ROOT, listing.home) check('crumbs 顶层 = 自有根(不暴露 /)', listing.crumbs.length === 1 && listing.crumbs[0].path === ROOT, JSON.stringify(listing.crumbs)) const names = listing.entries.map((e) => e.name) check('只返回目录(不含 file.txt)', !names.includes('file.txt'), names.join(',')) check('返回 proj-a', names.includes('proj-a'), names.join(',')) check('隐藏目录带 hidden 标记', listing.entries.find((e) => e.name === '.hidden-dir')?.hidden === true) check('entry.path 为绝对路径', listing.entries.every((e) => e.path.startsWith(ROOT))) const sub = await picker.list(join(ROOT, 'proj-a')) check('可进入子目录', sub.path === join(ROOT, 'proj-a') && sub.crumbs.length === 2, `${sub.path} crumbs=${sub.crumbs.length}`) const created = await picker.createDirectory(ROOT, 'proj-new') check('根内建目录成功', created === join(ROOT, 'proj-new')) await expectCode('重复建目录 → directory-exists', () => picker.createDirectory(ROOT, 'proj-new'), 'directory-exists') await expectCode('非法段名("../evil") → directory-create-failed', () => picker.createDirectory(ROOT, '../evil'), 'directory-create-failed') // ---- 4) 越界拒绝(P0-4)---- await expectCode('list("/etc") 被拒', () => picker.list('/etc'), 'directory-unreadable') await expectCode('list("/usr") 被拒', () => picker.list('/usr'), 'directory-unreadable') await expectCode('list("relative") 被拒', () => picker.list('proj-a'), 'directory-unreadable') await expectCode('list(ROOT + "/../..") 被拒', () => picker.list(join(ROOT, '..', '..')), 'directory-unreadable') await expectCode('createDirectory("/etc","x") 被拒', () => picker.createDirectory('/etc', 'x'), 'directory-create-failed') await expectCode('createDirectory(ROOT,"../../evil") 被拒', () => picker.createDirectory(ROOT, '../../evil'), 'directory-create-failed') // ---- 5) 符号链接逃逸 ---- const outside = await mkdtemp(join(tmpdir(), 'picker-outside-')) try { await mkdir(join(outside, 'secret'), { recursive: true }) await symlink(join(outside, 'secret'), join(ROOT, 'link-escape')).catch(() => undefined) await symlink(outside, join(ROOT, 'link-dir')).catch(() => undefined) const after = await picker.list() const escaped = after.entries.map((e) => e.name) check('指向根外的符号链接不出现在列举中', !escaped.includes('link-dir') && !escaped.includes('link-escape'), escaped.join(',')) await expectCode('list(符号链接指向根外) 被拒', () => picker.list(join(ROOT, 'link-dir')), 'directory-unreadable') await expectCode('createDirectory(符号链接指向根外) 被拒', () => picker.createDirectory(join(ROOT, 'link-dir'), 'x'), 'directory-create-failed') } finally { await rm(outside, { recursive: true, force: true }) } // ---- 汇总 ---- console.log('=== 档案 18 PoC 自检(workspace-scoped-picker)===') console.log(`root = ${ROOT}`) console.log(results.join('\n')) console.log(`\n—— 通过 ${pass} / 失败 ${fail} ——`) process.exit(fail === 0 ? 0 : 1)