Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d4181c0
fix: improve error logging format in plugin subscription plan service
adkif Jan 5, 2026
a179a2f
fix(plugin-system): enhance zip extraction security
adkif Jan 5, 2026
a99e177
fix(plugin-category): simplify slug generation logic
adkif Jan 5, 2026
3d0ffe2
fix(cspell): add new words to the spell checker dictionary
adkif Jan 5, 2026
1cefc38
fix(cdn-download): use replaceAll for entry path normalization
adkif Jan 5, 2026
c27bac5
fix(cspell): remove duplicated word "Zrdm" from the spell checker dic…
adkif Jan 5, 2026
f69d677
fix(cdn-download): improve file extraction handling and add error tra…
adkif Jan 5, 2026
4d78a64
fix(cdn-download): correct path normalization logic in isSafePath method
adkif Jan 5, 2026
d2e885c
fix: arbitrary file access during archive extraction ("Zip Slip")
adkif Jan 5, 2026
1ef3ec8
fix(cdn-download): enhance security by rejecting Unix-style absolute …
adkif Jan 5, 2026
47bb1d7
fix(cdn-download): update import for finished stream utility and remo…
adkif Jan 5, 2026
03a36c9
fix(cdn-download): improve path normalization
adkif Jan 5, 2026
1474196
refactor(cdn-download): simplify archive extraction logic
adkif Jan 5, 2026
ddef7ef
fix(cdn-download): prevent path traversal in zip extraction
adkif Jan 7, 2026
24b9bdc
fix(plugin-system): improve error handling and stream management in c…
adkif Jan 7, 2026
b88c1b9
fix(cdn-download): re-throw stream write errors
adkif Jan 7, 2026
240ae29
fix: Rethrowing here propagates the error, but the unzipper entry str…
adkif Jan 7, 2026
8c7bcf9
fix(cdn-download): improve error handling and resource cleanup
adkif Jan 7, 2026
f9ca126
fix(cdn-download): ensure error is thrown in stream pipe
adkif Jan 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -807,8 +807,11 @@
"prepopulate",
"prepopulates",
"prepopulation",
"Liskov"
"Zrdm"
"Liskov",
"Zrdm",
"backoff",
"autodrain",
"pluginname"
],
"useGitignore": true,
"ignorePaths": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,8 +296,10 @@
let totalSize = 0;

await new Promise<void>((resolve, reject) => {
const baseExtractPath = path.resolve(extractDir);

createReadStream(filePath)
.pipe(unzipper.Parse())
.on('entry', (entry: any) => {
const { path: entryPath, type, size } = entry;

Expand Down Expand Up @@ -327,12 +329,31 @@
}

const fullPath = path.join(extractDir, entryPath);
const resolvedFullPath = path.resolve(fullPath);

// Additional Security: Ensure resolved path is within extraction directory
if (
!resolvedFullPath.startsWith(baseExtractPath + path.sep) &&
resolvedFullPath !== baseExtractPath
) {
logger.warn(`Resolved path escapes extract directory, skipping: ${entryPath}`);
entry.autodrain();
return;
}

if (type === 'Directory') {
entry.autodrain();
} else {
// Extract file
entry.pipe(createWriteStream(fullPath));
// Ensure directory exists before writing file
fs.mkdir(path.dirname(resolvedFullPath), { recursive: true })
.then(() => {
// Extract file
entry.pipe(createWriteStream(resolvedFullPath));
})
.catch((err) => {
logger.error(`Failed to create directory for ${resolvedFullPath}: ${err.message}`);
entry.autodrain();
});
}
})
.on('close', resolve)
Expand Down Expand Up @@ -415,10 +436,36 @@
/**
* Validates that a file path is safe and doesn't attempt path traversal
*/
private isSafePath(basePath: string, filePath: string): boolean {
const resolvedPath = path.resolve(basePath, filePath);
const normalizedBase = path.normalize(basePath);
return resolvedPath.startsWith(normalizedBase);
private isSafePath(rootDir: string, entryPath: string): boolean {
if (!entryPath) {
return false;
}

// Normalize entry path separators
const normalizedEntry = entryPath.replace(/\\/g, '/');

// Disallow absolute paths and drive letters
if (path.isAbsolute(normalizedEntry)) {
return false;
}
if (/^[a-zA-Z]:/.test(normalizedEntry)) {
return false;
}

// Resolve the full path and ensure it stays within rootDir
const resolvedRoot = path.resolve(rootDir);
const resolvedTarget = path.resolve(rootDir, normalizedEntry);

// Ensure the resolved target is within the resolved root directory
if (resolvedTarget === resolvedRoot) {
return false;
}

if (!resolvedTarget.startsWith(resolvedRoot + path.sep)) {
return false;
}

return true;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,8 @@ export class PluginCategoryService extends TenantAwareCrudService<PluginCategory
): Promise<string> {
let baseSlug = name
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-+|-+$/g, '');
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');

let slug = baseSlug;
let counter = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ export class PluginSubscriptionPlanService extends CrudService<PluginSubscriptio
return count > 0;
} catch (error) {
// If there's an error checking, default to false (treat as free plugin)
console.error(`Error checking if plugin ${pluginId} requires subscription:`, error);
console.error('Error checking if plugin %s requires subscription:', pluginId, error);
return false;
}
}
Expand Down
Loading