|
|
@@ -175,6 +175,38 @@ describe('probePackage', () => {
|
|
|
expect(chatty).toMatchObject({ kind: 'plugin', ok: true })
|
|
|
const lingers = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'lingers', timeoutMs: 5_000 })
|
|
|
expect(lingers).toMatchObject({ kind: 'plugin', ok: true })
|
|
|
+ // The probe settled on the child's close: nothing of the killed child is still open.
|
|
|
+ expect(process.getActiveResourcesInfo()).not.toContain('ChildProcess')
|
|
|
+ })
|
|
|
+
|
|
|
+ it('takes only the message that echoes its token, hides credentials from the child, and keeps a stderr tail', async () => {
|
|
|
+ const { profileDir, installAnchor } = stage({
|
|
|
+ // A report forged at import, complete with the token's name: the token is
|
|
|
+ // gone from the environment and `process.send` from `process` by then.
|
|
|
+ 'forges': {
|
|
|
+ main: 'process.send?.({ token: process.env.DSH_PROBE_REPORT ?? "", cordis: null, main: { ok: true, isPlugin: true, configSchema: null }, addable: {} })\n'
|
|
|
+ + 'export const notAPlugin = 1\n',
|
|
|
+ manifest: { dsh: { title: 'Forges' } },
|
|
|
+ },
|
|
|
+ 'peeks': {
|
|
|
+ main: 'throw new Error("env=" + Object.keys(process.env).filter(k => k.startsWith("PROBE_TEST") || k === "DSH_PROBE_REPORT").sort().join(","))\n',
|
|
|
+ },
|
|
|
+ 'floods': { main: 'import { writeSync } from "node:fs"\nwriteSync(2, "x".repeat(200_000))\nwriteSync(2, "tail-marker")\nprocess.exit(3)\n' },
|
|
|
+ })
|
|
|
+ const forged = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'forges' })
|
|
|
+ expect(forged).toMatchObject({ kind: 'library', ok: true })
|
|
|
+ process.env.PROBE_TEST_SECRET = 'hidden'
|
|
|
+ process.env.PROBE_TEST_PLAIN = 'visible'
|
|
|
+ try {
|
|
|
+ const peeked = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'peeks' })
|
|
|
+ expect(peeked.reason).toContain('env=PROBE_TEST_PLAIN')
|
|
|
+ } finally {
|
|
|
+ delete process.env.PROBE_TEST_SECRET
|
|
|
+ delete process.env.PROBE_TEST_PLAIN
|
|
|
+ }
|
|
|
+ const flood = await probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'floods' }).then(() => undefined, (error: unknown) => error as Error)
|
|
|
+ expect(flood?.message).toMatch(/exited with 3 without a report: x+tail-marker$/)
|
|
|
+ expect(flood?.message.length).toBeLessThan(17_000)
|
|
|
})
|
|
|
|
|
|
it('kills a child that never reports, rejects an unrecognized report, and refuses an unresolvable package', async () => {
|
|
|
@@ -182,14 +214,15 @@ describe('probePackage', () => {
|
|
|
// Blocks the child's thread inside the import: an unsettled top-level await would make Node exit instead.
|
|
|
'hangs': { main: 'Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0)\nexport function apply() {}\n' },
|
|
|
'exits': { main: 'process.stderr.write("refusing to report"); process.exit(3)\n' },
|
|
|
- 'spoofs': { main: 'process.send({ nope: true }); process.exit(0)\n' },
|
|
|
+ // `process.send` is gone by the time the package runs; a message it could send would not carry the token anyway.
|
|
|
+ 'spoofs': { main: 'process.send?.({ nope: true }); process.exit(0)\n' },
|
|
|
})
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'hangs', timeoutMs: 300 }))
|
|
|
.rejects.toThrow(/timed out after 300ms/)
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'exits' }))
|
|
|
.rejects.toThrow(/exited with 3 without a report: refusing to report/)
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'spoofs' }))
|
|
|
- .rejects.toThrow(/reported an unrecognized value/)
|
|
|
+ .rejects.toThrow(/exited with 0 without a report/)
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'ghost' }))
|
|
|
.rejects.toThrow(/cannot resolve profile bundle "ghost"/)
|
|
|
await expect(probePackage({ binName: NAME, profileDir, installAnchor, packageName: 'exits', nodeExecutable: '/no/such/node' }))
|
|
|
@@ -248,11 +281,11 @@ describe('probe cache', () => {
|
|
|
describe('parseChildReport', () => {
|
|
|
it('accepts the child\'s message only with every field in place', () => {
|
|
|
const inspection = { ok: true, isPlugin: true, configSchema: null }
|
|
|
- const report: ChildReport = { cordis: null, main: inspection, addable: { 'pkg/x': { ...inspection, ok: false, error: 'boom' } } }
|
|
|
+ const report: ChildReport = { token: 't', cordis: null, main: inspection, addable: { 'pkg/x': { ...inspection, ok: false, error: 'boom' } } }
|
|
|
expect(parseChildReport(report)).toBe(report)
|
|
|
expect(parseChildReport({ ...report, cordis: 'file:///cordis/index.js' })).toBeDefined()
|
|
|
const broken: Record<string, unknown>[] = [
|
|
|
- { cordis: 1 }, { main: undefined }, { main: { ...inspection, ok: 'yes' } }, { main: { ...inspection, isPlugin: 'no' } },
|
|
|
+ { token: undefined }, { token: 1 }, { cordis: 1 }, { main: undefined }, { main: { ...inspection, ok: 'yes' } }, { main: { ...inspection, isPlugin: 'no' } },
|
|
|
{ main: { ok: true, isPlugin: true } }, { main: { ...inspection, error: 1 } }, { addable: [] }, { addable: { 'pkg/x': 1 } },
|
|
|
]
|
|
|
for (const fields of broken) expect(parseChildReport({ ...report, ...fields }), JSON.stringify(fields)).toBeUndefined()
|