Fix Duplicate/Edit cloning (volumes/settings) and initial image update check.
Release rolling / release (push) Successful in 9m49s

This commit is contained in:
Raven Scott
2026-07-16 00:01:07 -04:00
parent b53f4a58b1
commit 645f025042
5 changed files with 455 additions and 143 deletions
+59 -12
View File
@@ -94,23 +94,70 @@ function isValidPortMapping(portMapping) {
}
/**
* Validates volume mount format
* @param {string} volume - Volume mount string (e.g., "/host:/container:ro")
* Mount option suffix (Docker bind mode flags).
* @param {string} part
*/
function isMountModePart(part) {
if (!part || typeof part !== 'string') return false;
// e.g. ro, rw, ro,Z, rw,z, shared, rprivate, …
return /^(?:ro|rw|z|Z|shared|rshared|slave|rslave|private|rprivate)(?:,(?:ro|rw|z|Z|shared|rshared|slave|rslave|private|rprivate))*$/i.test(
part.trim()
);
}
/**
* Validates volume mount format.
* Accepts bind mounts and named volumes:
* /host/path:/container/path
* /host/path:/container/path:ro
* volume-name:/container/path
* volume-name:/container/path:rw
* /host:/container:ro,Z
* @param {string} volume - Volume mount string
* @returns {boolean} - True if valid
*/
function isValidVolumeMount(volume) {
if (!volume || typeof volume !== 'string') return false;
if (!volume.includes(':')) return false;
const parts = volume.split(':');
if (parts.length < 2 || parts.length > 3) return false;
// Check for path traversal attempts
if (parts.some(part => part.includes('..'))) return false;
// Basic path validation
const pathPattern = /^(\/[^\/]+)*\/?$/;
return parts.slice(0, 2).every(part => pathPattern.test(part) || part.startsWith('/'));
// Block path traversal in any segment
if (volume.includes('..')) return false;
const raw = volume.trim();
const parts = raw.split(':');
if (parts.length < 2) return false;
let source;
let dest;
if (parts.length >= 3 && isMountModePart(parts[parts.length - 1])) {
dest = parts[parts.length - 2];
source = parts.slice(0, -2).join(':');
} else if (parts.length === 2) {
source = parts[0];
dest = parts[1];
} else if (parts.length > 2) {
// e.g. Windows drive or unusual source with colons: join all but last as source
dest = parts[parts.length - 1];
source = parts.slice(0, -1).join(':');
} else {
return false;
}
if (!source || !dest) return false;
// Container path must be absolute
if (!dest.startsWith('/')) return false;
// Named volume (Docker volume name) OR host path (absolute/relative/~ / Windows drive)
const namedVol = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
const isNamed = namedVol.test(source);
const isHostPath =
source.startsWith('/') ||
source.startsWith('./') ||
source.startsWith('~/') ||
source === '~' ||
/^[A-Za-z]:[\\/]/.test(source);
return isNamed || isHostPath;
}
/**