fix: tighten installer migration authoring guards
This commit is contained in:
@@ -144,6 +144,39 @@ test('rejects destructive migration actions without ownership evidence', (t) =>
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects migration actions with absolute or traversal relPaths', (t) => {
|
||||
const configDir = createTempDir('gsd-migration-authoring-relpath-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(configDir, 'gsd-file-manifest.json'),
|
||||
JSON.stringify({ version: '1.50.0', timestamp: '2026-05-11T00:00:00.000Z', mode: 'full', files: {} }),
|
||||
'utf8'
|
||||
);
|
||||
|
||||
for (const relPath of ['/tmp/outside.js', 'hooks/../outside.js', 'hooks/..', '.']) {
|
||||
assert.throws(
|
||||
() => planInstallerMigrations({
|
||||
configDir,
|
||||
migrations: [
|
||||
completeMigrationRecord({
|
||||
plan: () => [
|
||||
{
|
||||
type: 'remove-managed',
|
||||
relPath,
|
||||
reason: 'bad path',
|
||||
ownershipEvidence: 'test fixture manifest-managed hook',
|
||||
},
|
||||
],
|
||||
}),
|
||||
],
|
||||
scope: 'global',
|
||||
}),
|
||||
/relPath must stay inside configDir/
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('rejects runtime config rewrites without a runtime contract citation', (t) => {
|
||||
const configDir = createTempDir('gsd-migration-authoring-runtime-');
|
||||
t.after(() => cleanup(configDir));
|
||||
|
||||
@@ -1128,6 +1128,41 @@ test('runs discovered installer migrations against manifest-managed legacy orpha
|
||||
}
|
||||
});
|
||||
|
||||
test('backs up modified legacy orphan files before removing them', () => {
|
||||
const configDir = createTempInstall();
|
||||
try {
|
||||
writeFile(configDir, 'hooks/statusline.js', 'user modified legacy hook\n');
|
||||
writeManifest(configDir, {
|
||||
'hooks/statusline.js': sha256('legacy managed hook\n'),
|
||||
});
|
||||
|
||||
const plan = planInstallerMigrations({
|
||||
configDir,
|
||||
migrations: discoverInstallerMigrations({
|
||||
migrationsDir: path.join(__dirname, '..', 'get-shit-done', 'bin', 'lib', 'installer-migrations'),
|
||||
}),
|
||||
scope: 'global',
|
||||
now: () => '2026-05-11T00:00:05.000Z',
|
||||
});
|
||||
const action = plan.actions.find((item) => item.relPath === 'hooks/statusline.js');
|
||||
|
||||
assert.equal(action.type, 'backup-and-remove');
|
||||
|
||||
const result = runInstallerMigrations({
|
||||
configDir,
|
||||
scope: 'global',
|
||||
now: () => '2026-05-11T00:00:05.000Z',
|
||||
});
|
||||
const journal = JSON.parse(fs.readFileSync(path.join(configDir, result.journalRelPath), 'utf8'));
|
||||
const backupRelPath = journal.actions.find((item) => item.relPath === 'hooks/statusline.js').backupRelPath;
|
||||
|
||||
assert.equal(fs.existsSync(path.join(configDir, 'hooks/statusline.js')), false);
|
||||
assert.equal(fs.readFileSync(path.join(configDir, backupRelPath), 'utf8'), 'user modified legacy hook\n');
|
||||
} finally {
|
||||
cleanup(configDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('runs a Codex legacy hooks.json cleanup migration without removing user hooks', () => {
|
||||
const configDir = createTempInstall();
|
||||
try {
|
||||
@@ -1164,6 +1199,40 @@ test('runs a Codex legacy hooks.json cleanup migration without removing user hoo
|
||||
}
|
||||
});
|
||||
|
||||
test('preserves unrelated empty hooks.json structure while pruning legacy Codex hooks', () => {
|
||||
const configDir = createTempInstall();
|
||||
try {
|
||||
writeFile(
|
||||
configDir,
|
||||
'hooks.json',
|
||||
JSON.stringify({
|
||||
SessionStart: [
|
||||
legacyCodexHook(configDir),
|
||||
{ hooks: [] },
|
||||
{ metadata: null },
|
||||
],
|
||||
}, null, 2)
|
||||
);
|
||||
writeManifest(configDir, {});
|
||||
|
||||
runInstallerMigrations({
|
||||
configDir,
|
||||
runtime: 'codex',
|
||||
scope: 'global',
|
||||
now: () => '2026-05-11T00:00:06.000Z',
|
||||
});
|
||||
|
||||
const hooksJson = JSON.parse(fs.readFileSync(path.join(configDir, 'hooks.json'), 'utf8'));
|
||||
|
||||
assert.deepEqual(hooksJson.SessionStart, [
|
||||
{ hooks: [] },
|
||||
{ metadata: null },
|
||||
]);
|
||||
} finally {
|
||||
cleanup(configDir);
|
||||
}
|
||||
});
|
||||
|
||||
test('skips runtime-specific migration records for other runtimes', () => {
|
||||
const configDir = createTempInstall();
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user