Merge commit from fork
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user