Merge commit from fork

This commit is contained in:
Ben Lambert
2026-01-20 16:05:28 +01:00
committed by GitHub
parent ae4dd5d157
commit c641c147ab
12 changed files with 238 additions and 11 deletions
@@ -20,6 +20,7 @@ import { createDebugLogAction } from './log';
import { join } from 'path';
import yaml from 'yaml';
import { createMockDirectory } from '@backstage/backend-test-utils';
import fs from 'fs-extra';
describe('debug:log', () => {
const logger = {
@@ -139,4 +140,30 @@ describe('debug:log', () => {
expect.stringContaining('Hello Backstage!'),
);
});
it('should handle symlinks to external paths', async () => {
const externalContent = 'external-file-content';
const externalPath = mockDir.resolve('external.txt');
await fs.writeFile(externalPath, externalContent);
const linkPath = join(mockContext.workspacePath, 'link');
await fs.symlink(externalPath, linkPath);
const context = {
...mockContext,
input: {
listWorkspace: 'with-contents' as const,
},
};
await action.handler(context);
expect(logger.info).toHaveBeenCalledWith(expect.stringContaining('link'));
expect(logger.info).not.toHaveBeenCalledWith(
expect.stringContaining(externalContent),
);
expect(logger.info).toHaveBeenCalledWith(
expect.stringContaining('[skipped]'),
);
});
});
@@ -14,6 +14,7 @@
* limitations under the License.
*/
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
import { readdir, stat } from 'fs-extra';
import { join, relative } from 'path';
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
@@ -66,8 +67,16 @@ export function createDebugLogAction() {
.map(f => {
const relativePath = relative(ctx.workspacePath, f);
if (ctx.input?.listWorkspace === 'with-contents') {
const content = fs.readFileSync(f, 'utf-8');
return ` - ${relativePath}:\n\n ${content}`;
try {
const safePath = resolveSafeChildPath(
ctx.workspacePath,
relativePath,
);
const content = fs.readFileSync(safePath, 'utf-8');
return ` - ${relativePath}:\n\n ${content}`;
} catch {
return ` - ${relativePath}: [skipped]`;
}
}
return ` - ${relativePath}`;
})
@@ -229,4 +229,26 @@ describe('fs:delete', () => {
expect(fileExists).toBe(false);
});
});
it('should not delete files outside workspace via symlinks', async () => {
// Create an external file that should not be deleted
const externalDir = resolvePath(mockDir.path, 'external');
const externalFile = resolvePath(externalDir, 'config.yaml');
await fs.ensureDir(externalDir);
await fs.writeFile(externalFile, 'external content');
// Create a symlink inside workspace pointing to external directory
const linkPath = resolvePath(workspacePath, 'link');
await fs.symlink(externalDir, linkPath);
// Try to delete files through the symlink
await expect(() =>
action.handler({
...mockContext,
input: { files: ['link/**'] },
}),
).rejects.toThrow(
/Relative path is not allowed to refer to a directory outside its parent/,
);
});
});
@@ -16,7 +16,10 @@
import { createTemplateAction } from '@backstage/plugin-scaffolder-node';
import { InputError } from '@backstage/errors';
import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
import {
isChildPath,
resolveSafeChildPath,
} from '@backstage/backend-plugin-api';
import fs from 'fs-extra';
import globby from 'globby';
import { examples } from './delete.examples';
@@ -58,10 +61,11 @@ export const createFilesystemDeleteAction = () => {
for (const filepath of resolvedPaths) {
try {
await fs.remove(filepath);
ctx.logger.info(`File ${filepath} deleted successfully`);
const safePath = resolveSafeChildPath(ctx.workspacePath, filepath);
await fs.remove(safePath);
ctx.logger.info(`File ${safePath} deleted successfully`);
} catch (err) {
ctx.logger.error(`Failed to delete file ${filepath}:`, err);
ctx.logger.error(`Failed to delete file`, err);
throw err;
}
}