mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +00:00
fix: reject symlinked cleanup directories
This commit is contained in:
1 parent
23c819cabc
commit
920fba7e13
2 files changed
+44
-12
No files matched your search
@@ -36,12 +36,21 @@ function formatBytes(bytes: number): string {
|
|||||||
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
|
return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Return entries for a real directory, rejecting symlinked directory targets. */
|
||||||
|
function readRealDirectory(dirPath: string): string[] {
|
||||||
|
try {
|
||||||
|
const stats = fs.lstatSync(dirPath);
|
||||||
|
if (!stats.isDirectory() || stats.isSymbolicLink()) return [];
|
||||||
|
return fs.readdirSync(dirPath);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Calculate total size of regular top-level files in a directory */
|
/** Calculate total size of regular top-level files in a directory */
|
||||||
function getDirSize(dirPath: string): number {
|
function getDirSize(dirPath: string): number {
|
||||||
if (!fs.existsSync(dirPath)) return 0;
|
|
||||||
|
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
const entries = fs.readdirSync(dirPath);
|
const entries = readRealDirectory(dirPath);
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const filePath = path.join(dirPath, entry);
|
const filePath = path.join(dirPath, entry);
|
||||||
@@ -60,9 +69,8 @@ function getDirSize(dirPath: string): number {
|
|||||||
|
|
||||||
/** Count files in a directory */
|
/** Count files in a directory */
|
||||||
function countFiles(dirPath: string): number {
|
function countFiles(dirPath: string): number {
|
||||||
if (!fs.existsSync(dirPath)) return 0;
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const entries = fs.readdirSync(dirPath);
|
const entries = readRealDirectory(dirPath);
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const filePath = path.join(dirPath, entry);
|
const filePath = path.join(dirPath, entry);
|
||||||
@@ -78,13 +86,11 @@ function countFiles(dirPath: string): number {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete all regular files in a directory (skips symlinks for safety) */
|
/** Delete all regular files in a real directory (skips symlinks for safety) */
|
||||||
function cleanDirectory(dirPath: string): { deleted: number; freedBytes: number } {
|
function cleanDirectory(dirPath: string): { deleted: number; freedBytes: number } {
|
||||||
if (!fs.existsSync(dirPath)) return { deleted: 0, freedBytes: 0 };
|
|
||||||
|
|
||||||
let deleted = 0;
|
let deleted = 0;
|
||||||
let freedBytes = 0;
|
let freedBytes = 0;
|
||||||
const files = fs.readdirSync(dirPath);
|
const files = readRealDirectory(dirPath);
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const filePath = path.join(dirPath, file);
|
const filePath = path.join(dirPath, file);
|
||||||
@@ -116,11 +122,9 @@ interface ErrorLogInfo {
|
|||||||
|
|
||||||
/** Get error log files with metadata */
|
/** Get error log files with metadata */
|
||||||
function getErrorLogFiles(logsDir: string): ErrorLogInfo[] {
|
function getErrorLogFiles(logsDir: string): ErrorLogInfo[] {
|
||||||
if (!fs.existsSync(logsDir)) return [];
|
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const files: ErrorLogInfo[] = [];
|
const files: ErrorLogInfo[] = [];
|
||||||
const entries = fs.readdirSync(logsDir);
|
const entries = readRealDirectory(logsDir);
|
||||||
|
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
// Only process error-*.log files
|
// Only process error-*.log files
|
||||||
|
|||||||
@@ -53,4 +53,32 @@ describe('cleanup command', () => {
|
|||||||
logSpy.mockRestore();
|
logSpy.mockRestore();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('does not follow a symlinked CCS archive directory during cleanup', async () => {
|
||||||
|
if (process.platform === 'win32') return;
|
||||||
|
|
||||||
|
const ccsLogsDir = getNativeLogsDir();
|
||||||
|
const archiveDir = getLogArchiveDir();
|
||||||
|
const victimDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ccs-cleanup-victim-'));
|
||||||
|
|
||||||
|
fs.mkdirSync(ccsLogsDir, { recursive: true });
|
||||||
|
fs.writeFileSync(path.join(victimDir, 'keepme.log'), 'do not delete');
|
||||||
|
fs.symlinkSync(victimDir, archiveDir, 'dir');
|
||||||
|
|
||||||
|
const logSpy = spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await handleCleanupCommand(['--force']);
|
||||||
|
|
||||||
|
const output = logSpy.mock.calls
|
||||||
|
.flatMap((call) => call.map((value) => String(value)))
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
expect(output).toContain('No CCS or CLIProxy logs found.');
|
||||||
|
expect(fs.existsSync(path.join(victimDir, 'keepme.log'))).toBe(true);
|
||||||
|
} finally {
|
||||||
|
logSpy.mockRestore();
|
||||||
|
fs.rmSync(victimDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Reference in new issue
Block a user