Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
132 lines
5.3 KiB
JavaScript
132 lines
5.3 KiB
JavaScript
'use strict';
|
|
/**
|
|
* Tests for the external-descriptor trust gate (ADR-1239 Phase C-2, #1681).
|
|
* Pins: confined passes; escapes (.. / absolute) rejected fail-closed; missing
|
|
* layout passes; the configHome-equals-root edge; non-string destSubpath skipped.
|
|
*/
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const path = require('node:path');
|
|
const {
|
|
isPathConfined,
|
|
assertDescriptorConfined,
|
|
} = require('../msd-core/bin/lib/external-descriptor-trust.cjs');
|
|
|
|
test('isPathConfined: confined paths are true, escapes are false', () => {
|
|
const root = path.join('/home', 'me', '.msd');
|
|
assert.ok(isPathConfined('skills', root), 'simple subdir is confined');
|
|
assert.ok(isPathConfined('skills/msd-plan.md', root), 'nested subdir is confined');
|
|
assert.ok(isPathConfined('.', root), 'root itself is confined');
|
|
assert.ok(!isPathConfined('../etc/passwd', root), 'parent escape is NOT confined');
|
|
assert.ok(!isPathConfined('../../etc', root), 'multi-level escape is NOT confined');
|
|
assert.ok(!isPathConfined('/etc/passwd', root), 'absolute path outside root is NOT confined');
|
|
assert.ok(!isPathConfined('', root), 'empty target is NOT confined');
|
|
assert.ok(!isPathConfined('skills', ''), 'empty root is NOT confined');
|
|
});
|
|
|
|
test('isPathConfined: win32 injection — confined and escape cases', () => {
|
|
const win32 = path.win32;
|
|
const root = 'C:\\Users\\me\\.msd';
|
|
assert.ok(
|
|
isPathConfined('sub\\file.md', root, { pathImpl: win32 }),
|
|
'win32 target under root is confined',
|
|
);
|
|
assert.ok(
|
|
!isPathConfined('D:\\evil', root, { pathImpl: win32 }),
|
|
'a different drive letter is NOT confined',
|
|
);
|
|
assert.ok(
|
|
!isPathConfined('C:\\Windows\\system32', root, { pathImpl: win32 }),
|
|
'an absolute path on the same drive outside root is NOT confined',
|
|
);
|
|
assert.ok(
|
|
!isPathConfined('..\\..\\evil', root, { pathImpl: win32 }),
|
|
'a backslash traversal is NOT confined',
|
|
);
|
|
assert.ok(
|
|
!isPathConfined('\\\\server\\share\\x', root, { pathImpl: win32 }),
|
|
'a UNC path is NOT confined',
|
|
);
|
|
assert.ok(
|
|
!isPathConfined('../../x', root, { pathImpl: win32 }),
|
|
'a forward-slash traversal is NOT confined (win32 accepts / too)',
|
|
);
|
|
});
|
|
|
|
test('isPathConfined: win32 prefix-boundary — sibling with root as a string prefix is refused', () => {
|
|
const win32 = path.win32;
|
|
const root = 'C:\\Users\\me\\.msd';
|
|
assert.ok(
|
|
!isPathConfined('..\\.msdEVIL', root, { pathImpl: win32 }),
|
|
'C:\\Users\\me\\.msdEVIL must be refused despite sharing the "C:\\Users\\me\\.msd" string prefix',
|
|
);
|
|
});
|
|
|
|
test('isPathConfined: posix prefix-boundary — sibling with root as a string prefix is refused', () => {
|
|
const posix = path.posix;
|
|
const root = '/home/me/.msd';
|
|
assert.ok(
|
|
!isPathConfined('../.msdEVIL', root, { pathImpl: posix }),
|
|
'/home/me/.msdEVIL must be refused despite sharing the "/home/me/.msd" string prefix',
|
|
);
|
|
});
|
|
|
|
test('assertDescriptorConfined: a benign descriptor (all destSubpaths under configHome) passes', () => {
|
|
const desc = {
|
|
id: 'community-host',
|
|
runtime: { artifactLayout: {
|
|
global: [{ destSubpath: 'skills' }, { destSubpath: 'agents' }],
|
|
local: [{ destSubpath: 'commands' }],
|
|
} },
|
|
};
|
|
assert.doesNotThrow(() => assertDescriptorConfined(desc, '/home/me/.community'));
|
|
});
|
|
|
|
test('assertDescriptorConfined: a global destSubpath escape is rejected fail-closed', () => {
|
|
const desc = {
|
|
id: 'malicious-host',
|
|
runtime: { artifactLayout: { global: [{ destSubpath: '../../../etc/passwd' }] } },
|
|
};
|
|
assert.throws(
|
|
() => assertDescriptorConfined(desc, '/home/me/.msd'),
|
|
/malicious-host.*unconfined global destSubpath.*fail-closed/,
|
|
'an escaping global destSubpath must be rejected with a fail-closed error naming the descriptor',
|
|
);
|
|
});
|
|
|
|
test('assertDescriptorConfined: a local destSubpath escape is rejected fail-closed', () => {
|
|
const desc = {
|
|
id: 'sneaky-host',
|
|
runtime: { artifactLayout: { local: [{ destSubpath: '../../.ssh/authorized_keys' }] } },
|
|
};
|
|
assert.throws(
|
|
() => assertDescriptorConfined(desc, '/home/me/.msd'),
|
|
/sneaky-host.*unconfined local destSubpath/,
|
|
'an escaping local destSubpath must be rejected',
|
|
);
|
|
});
|
|
|
|
test('assertDescriptorConfined: an absolute destSubpath outside configHome is rejected', () => {
|
|
const desc = {
|
|
id: 'abs-host',
|
|
runtime: { artifactLayout: { global: [{ destSubpath: '/etc/cron.d/evil' }] } },
|
|
};
|
|
assert.throws(() => assertDescriptorConfined(desc, '/home/me/.msd'), /unconfined global destSubpath/);
|
|
});
|
|
|
|
test('assertDescriptorConfined: a descriptor with no artifact layout passes (nothing to confine)', () => {
|
|
assert.doesNotThrow(() => assertDescriptorConfined({ id: 'bare', runtime: {} }, '/home/me/.msd'));
|
|
assert.doesNotThrow(() => assertDescriptorConfined({ id: 'noruntime' }, '/home/me/.msd'));
|
|
assert.doesNotThrow(() => assertDescriptorConfined({}, '/home/me/.msd'));
|
|
assert.doesNotThrow(() => assertDescriptorConfined(null, '/home/me/.msd'));
|
|
});
|
|
|
|
test('assertDescriptorConfined: non-string / empty destSubpath entries are skipped (not flagged)', () => {
|
|
const desc = {
|
|
id: 'mixed',
|
|
runtime: { artifactLayout: { global: [{ destSubpath: 'skills' }, { destSubpath: '' }, { destSubpath: null }, {}, { destSubpath: 'agents' }] } },
|
|
};
|
|
assert.doesNotThrow(() => assertDescriptorConfined(desc, '/home/me/.x'), 'valid entries pass; invalid entries skipped');
|
|
});
|