mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-10-19 07:01:35 +00:00
d629faebc6
Add VOLUME_NAME_REGEX (Docker volume-name format) and enforce it on volumeName in create/update/runManually — a legit volume name never contains shell metacharacters, so this blocks injection across every docker run/rm/rclone sink at once. Escape the user-supplied backupFileName and the mount volumeName in database rebuild with quote().
79 lines
2.8 KiB
TypeScript
79 lines
2.8 KiB
TypeScript
import { decryptValue, encryptValue } from "@dokploy/server/lib/encryption";
|
|
import { generatePassword } from "@dokploy/server/templates";
|
|
import { faker } from "@faker-js/faker";
|
|
import { customType } from "drizzle-orm/pg-core";
|
|
import { customAlphabet } from "nanoid";
|
|
|
|
/**
|
|
* Text column encrypted at rest (AES-256-GCM, key derived from
|
|
* BETTER_AUTH_SECRET). Legacy plaintext values are passed through on read
|
|
* and get encrypted the next time they are written.
|
|
*/
|
|
export const encryptedText = customType<{ data: string; driverData: string }>({
|
|
dataType() {
|
|
return "text";
|
|
},
|
|
toDriver(value) {
|
|
return encryptValue(value);
|
|
},
|
|
fromDriver(value) {
|
|
try {
|
|
return decryptValue(value);
|
|
} catch {
|
|
// Fail open so a key mismatch (e.g. restoring a backup under a
|
|
// different BETTER_AUTH_SECRET) degrades to showing ciphertext
|
|
// instead of breaking every query that touches the row.
|
|
console.error(
|
|
"Failed to decrypt an encrypted column; returning the raw value. This usually means BETTER_AUTH_SECRET changed after the value was encrypted.",
|
|
);
|
|
return value;
|
|
}
|
|
},
|
|
});
|
|
|
|
const alphabet = "abcdefghijklmnopqrstuvwxyz123456789";
|
|
|
|
const customNanoid = customAlphabet(alphabet, 6);
|
|
|
|
/** App name: letters, numbers, dots, underscores, hyphens only (no spaces). Safe for shell/Docker. */
|
|
export const APP_NAME_REGEX = /^[a-zA-Z0-9._-]+$/;
|
|
|
|
export const APP_NAME_MESSAGE =
|
|
"App name can only contain letters, numbers, dots, underscores and hyphens";
|
|
|
|
/** Docker volume name: must start alphanumeric, then letters/numbers/._- only. Safe for shell. */
|
|
export const VOLUME_NAME_REGEX = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
|
|
|
|
export const VOLUME_NAME_MESSAGE =
|
|
"Volume name must start with a letter or number and contain only letters, numbers, dots, underscores and hyphens";
|
|
|
|
/** Database password: blocks shell-dangerous characters like $ ! ' " \ / and spaces. */
|
|
export const DATABASE_PASSWORD_REGEX =
|
|
/^[a-zA-Z0-9@#%^&*()_+\-=[\]{}|;:,.<>?~`]*$/;
|
|
|
|
export const DATABASE_PASSWORD_MESSAGE =
|
|
"Password contains invalid characters. Please avoid: $ ! ' \" \\ / and space characters for database compatibility";
|
|
|
|
export const generateAppName = (type: string) => {
|
|
const verb = faker.hacker.verb().replace(/ /g, "-");
|
|
const adjective = faker.hacker.adjective().replace(/ /g, "-");
|
|
const noun = faker.hacker.noun().replace(/ /g, "-");
|
|
const randomFakerElement = `${verb}-${adjective}-${noun}`;
|
|
const nanoidPart = customNanoid();
|
|
return `${type}-${randomFakerElement}-${nanoidPart}`;
|
|
};
|
|
|
|
export const cleanAppName = (appName?: string) => {
|
|
if (!appName) {
|
|
return appName?.toLowerCase();
|
|
}
|
|
return appName.trim().replace(/ /g, "-").toLowerCase();
|
|
};
|
|
|
|
export const buildAppName = (type: string, baseAppName?: string) => {
|
|
if (baseAppName) {
|
|
return `${cleanAppName(baseAppName)}-${generatePassword(6)}`;
|
|
}
|
|
return generateAppName(type);
|
|
};
|