export function validateSiwcRegistrationId(value) { if (typeof value !== "string" || !/^[A-Za-z0-9_-]{8,64}$/u.test(value)) { throw new TypeError("The selected SIWC registration is invalid."); } return value; } export function validateSiwcRuntimeId(value) { if (typeof value !== "string" || !/^[A-Za-z0-9_-]{1,64}$/u.test(value)) { throw new TypeError("The selected SIWC runtime is invalid."); } return value; } export function validateSiwcAuthBinding(value) { if ( !value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).length !== 5 ) throw new TypeError("The selected SIWC account binding is invalid."); validateSiwcRegistrationId(value.registrationId); if (typeof value.clientId !== "string" || !/^[!-~]{1,256}$/u.test(value.clientId)) { throw new TypeError("The selected SIWC client is invalid."); } if (typeof value.generation !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value.generation)) { throw new TypeError("The selected SIWC generation is invalid."); } if (typeof value.ownerHostId !== "string" || !/^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value.ownerHostId)) { throw new TypeError("The selected SIWC host is invalid."); } validateSiwcRuntimeId(value.ownerRuntimeId); return Object.freeze({ ...value }); } const CATALOG_RECOVERIES = new Set([ "retry-later", "reauthorize", "enable-plan", "manage-usage", "fix-request", "fix-configuration", "review-again", "resolve-handoff", ]); const safeCatalogField = value => typeof value === "string" && /^[A-Za-z0-9_.:-]{1,128}$/u.test(value) && !/^sk-[A-Za-z0-9_-]{8,}$/u.test(value) && !/^eyJ[A-Za-z0-9_-]{16,}\./u.test(value) ? value : undefined; export function safeSiwcCatalogFailure(error) { return { error: "The selected account model catalog could not be verified.", status: Number.isInteger(error?.status) && error.status >= 400 && error.status <= 599 ? error.status : 503, ...(safeCatalogField(error?.code) ? { code: error.code } : {}), ...(safeCatalogField(error?.param) ? { param: error.param } : {}), ...(safeCatalogField(error?.requestId) ? { requestId: error.requestId } : {}), recovery: CATALOG_RECOVERIES.has(error?.recovery) ? error.recovery : "retry-later", }; } export function safeSiwcRuntimeFailure() { return { error: "The owned runtime could not be verified after transfer.", status: 503, recovery: "resolve-handoff", }; } export const INSTALL_ROOT = "/docker/n8n-openai-oauth"; export const PROJECT_NAME = "n8n-openai-oauth"; export const SERVICE_NAME = "openai-oauth"; export const MANAGED_MARKER_PATH = `${INSTALL_ROOT}/.managed-by-n8n-openai-oauth`; export const SHARED_ROOT_MARKER_PATH = `${INSTALL_ROOT}/.managed-by-relmio-root`; export const SHARED_ROOT_MARKER_CONTENT = "relmio-managed-root:v1\n"; export const SIDECAR_MARKER_CONTENT = "Managed by n8n-openai-oauth-setup.\n"; export const SIWC_MIGRATION_PENDING_PATH = `${INSTALL_ROOT}/.siwc-migration-pending`; export const SIWC_MIGRATED_PATH = `${INSTALL_ROOT}/.siwc-migrated`; export const SIWC_MIGRATION_MARKER_CONTENT = "relmio-siwc-migration:v1\n"; export const SIWC_STAGING_PATH = `${INSTALL_ROOT}/.siwc-staging.json`; export const SIDECAR_BUILD_IGNORE_PATH = `${INSTALL_ROOT}/Dockerfile.dockerignore`; export const SIDECAR_BUILD_IGNORE_CONTENT = "*\n!Dockerfile\n!package.json\n!package-lock.json\n!services/\n!services/siwc-session.mjs\n!services/siwc-handoff.mjs\n!services/local-integration-lifecycle-lock.js\n!services/codex-images.mjs\n!services/model-discovery.mjs\n!gateway/\n!gateway/openai-oauth-sidecar.mjs\n!infrastructure/\n!infrastructure/local-process.js\n!infrastructure/process-identity.js\n"; export const SIDECAR_BUILD_IGNORE_GUARD = `[ ! -L ${SIDECAR_BUILD_IGNORE_PATH} ] && { [ ! -e ${SIDECAR_BUILD_IGNORE_PATH} ] || { [ -f ${SIDECAR_BUILD_IGNORE_PATH} ] && [ "$(stat -c %u:%h ${SIDECAR_BUILD_IGNORE_PATH})" = 0:1 ] && [ $((0$(stat -c %a ${SIDECAR_BUILD_IGNORE_PATH}) & 022)) -eq 0 ] && [ "$(stat -c %s ${SIDECAR_BUILD_IGNORE_PATH})" = ${Buffer.byteLength(SIDECAR_BUILD_IGNORE_CONTENT)} ] && [ "$(cat ${SIDECAR_BUILD_IGNORE_PATH})" = '${SIDECAR_BUILD_IGNORE_CONTENT.trim()}' ]; }; }`; export const SIDECAR_FRESH_CONTEXT_GUARD = `for p in ${MANAGED_MARKER_PATH} ${SIDECAR_BUILD_IGNORE_PATH} ${INSTALL_ROOT}/Dockerfile ${INSTALL_ROOT}/docker-compose.yml ${INSTALL_ROOT}/package.json ${INSTALL_ROOT}/package-lock.json ${INSTALL_ROOT}/services ${INSTALL_ROOT}/gateway ${INSTALL_ROOT}/infrastructure; do [ ! -e "$p" ] && [ ! -L "$p" ] || exit 42; done; [ ! -L ${INSTALL_ROOT}/siwc ] || exit 43; if [ -d ${INSTALL_ROOT}/siwc ]; then [ -z "$(find ${INSTALL_ROOT}/siwc -mindepth 1 -maxdepth 1 -print -quit)" ] || exit 42; fi; if [ -d ${INSTALL_ROOT}/auth ]; then [ -z "$(find ${INSTALL_ROOT}/auth -mindepth 1 -maxdepth 1 -print -quit)" ] || exit 42; fi`; export const SIDECAR_MANAGED_CONTEXT_GUARD = `for p in /docker ${INSTALL_ROOT} ${INSTALL_ROOT}/services ${INSTALL_ROOT}/gateway ${INSTALL_ROOT}/infrastructure ${INSTALL_ROOT}/legacy; do if [ -e "$p" ] || [ -L "$p" ]; then [ ! -L "$p" ] && [ -d "$p" ] && [ "$(stat -c %u "$p")" = 0 ] && [ $((0$(stat -c %a "$p") & 022)) -eq 0 ] || exit 43; fi; done; for p in ${SHARED_ROOT_MARKER_PATH} ${MANAGED_MARKER_PATH} ${SIWC_STAGING_PATH} ${SIWC_MIGRATION_PENDING_PATH} ${SIWC_MIGRATED_PATH} ${SIDECAR_BUILD_IGNORE_PATH} ${INSTALL_ROOT}/Dockerfile ${INSTALL_ROOT}/docker-compose.yml ${INSTALL_ROOT}/package.json ${INSTALL_ROOT}/package-lock.json ${INSTALL_ROOT}/services/siwc-session.mjs ${INSTALL_ROOT}/services/siwc-handoff.mjs ${INSTALL_ROOT}/services/local-integration-lifecycle-lock.js ${INSTALL_ROOT}/services/codex-images.mjs ${INSTALL_ROOT}/services/model-discovery.mjs ${INSTALL_ROOT}/gateway/openai-oauth-sidecar.mjs ${INSTALL_ROOT}/gateway/codex-chat.js ${INSTALL_ROOT}/gateway/codex-app-server.mjs ${INSTALL_ROOT}/infrastructure/local-process.js ${INSTALL_ROOT}/infrastructure/process-identity.js ${INSTALL_ROOT}/openai-oauth-sidecar.mjs; do if [ -e "$p" ] || [ -L "$p" ]; then [ ! -L "$p" ] && [ -f "$p" ] && [ "$(stat -c %u:%h "$p")" = 0:1 ] && [ $((0$(stat -c %a "$p") & 022)) -eq 0 ] || exit 43; fi; done; if [ -e ${INSTALL_ROOT}/siwc ] || [ -L ${INSTALL_ROOT}/siwc ]; then [ ! -L ${INSTALL_ROOT}/siwc ] && [ -d ${INSTALL_ROOT}/siwc ] && [ "$(stat -c %u:%g:%a ${INSTALL_ROOT}/siwc)" = 1000:1000:700 ] || exit 43; fi`; const COMPOSE_PREFIX = "docker compose --project-name n8n-openai-oauth --file /docker/n8n-openai-oauth/docker-compose.yml"; export const PRECHECK_COMMAND = `if [ -e ${INSTALL_ROOT} ]; then [ ! -L ${INSTALL_ROOT} ] && [ -d ${INSTALL_ROOT} ] || exit 43; for p in ${SHARED_ROOT_MARKER_PATH} ${MANAGED_MARKER_PATH} ${SIDECAR_BUILD_IGNORE_PATH} ${SIWC_MIGRATION_PENDING_PATH} ${SIWC_MIGRATED_PATH} ${INSTALL_ROOT}/auth ${INSTALL_ROOT}/siwc ${INSTALL_ROOT}/Dockerfile ${INSTALL_ROOT}/package.json ${INSTALL_ROOT}/package-lock.json ${INSTALL_ROOT}/services ${INSTALL_ROOT}/services/siwc-session.mjs ${INSTALL_ROOT}/services/siwc-handoff.mjs ${INSTALL_ROOT}/services/local-integration-lifecycle-lock.js ${INSTALL_ROOT}/services/codex-images.mjs ${INSTALL_ROOT}/services/model-discovery.mjs ${INSTALL_ROOT}/gateway ${INSTALL_ROOT}/gateway/openai-oauth-sidecar.mjs ${INSTALL_ROOT}/infrastructure ${INSTALL_ROOT}/infrastructure/local-process.js ${INSTALL_ROOT}/infrastructure/process-identity.js ${INSTALL_ROOT}/docker-compose.yml; do [ ! -L "$p" ] || exit 43; done; if [ -e ${INSTALL_ROOT}/auth/auth.json ]; then [ -f ${SIWC_MIGRATED_PATH} ] && [ "$(cat ${SIWC_MIGRATED_PATH})" = '${SIWC_MIGRATION_MARKER_CONTENT.trim()}' ] || exit 42; fi; if [ -e ${SHARED_ROOT_MARKER_PATH} ]; then [ -f ${SHARED_ROOT_MARKER_PATH} ] && [ "$(cat ${SHARED_ROOT_MARKER_PATH})" = "${SHARED_ROOT_MARKER_CONTENT.trim()}" ] || exit 42; if [ -e ${MANAGED_MARKER_PATH} ]; then [ -f ${MANAGED_MARKER_PATH} ] && [ "$(cat ${MANAGED_MARKER_PATH})" = "${SIDECAR_MARKER_CONTENT.trim()}" ] || exit 42; printf '%s\\n' managed; else [ ! -e ${INSTALL_ROOT}/siwc/host.json ] || exit 42; printf '%s\\n' new; fi; elif [ -f ${MANAGED_MARKER_PATH} ] && [ "$(cat ${MANAGED_MARKER_PATH})" = "${SIDECAR_MARKER_CONTENT.trim()}" ]; then printf '%s\\n' managed; else exit 42; fi; else printf '%s\\n' new; fi`; // Relmio's shared folder is for root only. Both commands change the server, so a caller runs // them only after it has checked the owner's final confirmation. Status reads, sign-in polls // and operation locks never run them. The session folder inside keeps its own owner; Docker // mounts it for the container. const SHARED_ROOT_PARENT_GUARD = `[ -d /docker ] && [ ! -L /docker ] && [ "$(stat -c %u /docker)" = 0 ] && [ $((0$(stat -c %a /docker) & 022)) -eq 0 ] && [ ! -L ${INSTALL_ROOT} ]`; // The one place the folder's mode is set: only on a real folder that is already root's own. const SHARED_ROOT_SET_MODE = `[ -d ${INSTALL_ROOT} ] && [ "$(stat -c %u ${INSTALL_ROOT})" = 0 ] && [ $((0$(stat -c %a ${INSTALL_ROOT}) & 022)) -eq 0 ] && chmod 0700 ${INSTALL_ROOT}`; // A confirmed install or replacement: creates the folder with mode 0700, or sets an existing one. export const SHARED_ROOT_COMMAND = `${SHARED_ROOT_PARENT_GUARD} && if [ ! -e ${INSTALL_ROOT} ]; then mkdir -m 0700 ${INSTALL_ROOT} && chown -h 0:0 ${INSTALL_ROOT}; fi && ${SHARED_ROOT_SET_MODE}`; // A confirmed change to something already installed: sets an existing folder and never creates one. export const SHARED_ROOT_TIGHTEN_COMMAND = `${SHARED_ROOT_PARENT_GUARD} && ${SHARED_ROOT_SET_MODE}`; const DEPLOYMENT_COMMANDS = Object.freeze([ SHARED_ROOT_COMMAND, `install -d -m 0700 -o 1000 -g 1000 ${INSTALL_ROOT}/siwc`, `install -d -m 0755 ${INSTALL_ROOT}/services ${INSTALL_ROOT}/gateway ${INSTALL_ROOT}/infrastructure`, `${COMPOSE_PREFIX} config --quiet`, `${COMPOSE_PREFIX} build ${SERVICE_NAME}`, `${COMPOSE_PREFIX} up -d --wait --wait-timeout 60 --no-build --no-deps ${SERVICE_NAME}`, ]); const VERIFICATION_COMMANDS = Object.freeze({ runningService: `${COMPOSE_PREFIX} ps --status running --services`, publicationState: `${COMPOSE_PREFIX} ps --format json ${SERVICE_NAME}`, managementConfig: `${COMPOSE_PREFIX} config --format json`, ownerContainer: `docker inspect --format '{{json .}}' n8n-openai-oauth-openai-oauth-1`, ownerPresence: `docker container ls --all --no-trunc --filter 'name=^/n8n-openai-oauth-openai-oauth-1$' --format '{{.ID}}'`, ownerImage: `docker image inspect --format '{{json .}}' n8n-openai-oauth:local`, sharedRoot: `${SIDECAR_MANAGED_CONTEXT_GUARD}; if [ -e ${SHARED_ROOT_MARKER_PATH} ]; then [ "$(cat ${SHARED_ROOT_MARKER_PATH})" = '${SHARED_ROOT_MARKER_CONTENT.trim()}' ] && printf managed; else printf absent; fi`, staging: `${SIDECAR_MANAGED_CONTEXT_GUARD}; if [ -e ${SIWC_STAGING_PATH} ]; then [ "$(stat -c %s ${SIWC_STAGING_PATH})" -le 16384 ] && cat ${SIWC_STAGING_PATH}; else printf null; fi`, stagedFiles: `${SIDECAR_MANAGED_CONTEXT_GUARD}; for p in ${MANAGED_MARKER_PATH} ${SIDECAR_BUILD_IGNORE_PATH} ${INSTALL_ROOT}/Dockerfile ${INSTALL_ROOT}/docker-compose.yml ${INSTALL_ROOT}/package.json ${INSTALL_ROOT}/package-lock.json ${INSTALL_ROOT}/services/siwc-session.mjs ${INSTALL_ROOT}/services/siwc-handoff.mjs ${INSTALL_ROOT}/services/local-integration-lifecycle-lock.js ${INSTALL_ROOT}/services/codex-images.mjs ${INSTALL_ROOT}/services/model-discovery.mjs ${INSTALL_ROOT}/gateway/openai-oauth-sidecar.mjs ${INSTALL_ROOT}/gateway/codex-chat.js ${INSTALL_ROOT}/gateway/codex-app-server.mjs ${INSTALL_ROOT}/infrastructure/local-process.js ${INSTALL_ROOT}/infrastructure/process-identity.js ${INSTALL_ROOT}/openai-oauth-sidecar.mjs; do if [ -e "$p" ]; then sha256sum "$p" || exit 43; fi; done`, legacyCredential: `[ ! -L ${INSTALL_ROOT}/auth/auth.json ] && [ -f ${INSTALL_ROOT}/auth/auth.json ] && [ "$(stat -c %u:%a ${INSTALL_ROOT}/auth/auth.json)" = 1000:600 ] && [ "$(stat -c %s ${INSTALL_ROOT}/auth/auth.json)" -le 131072 ] && stat -c '%d:%i:%u:%a:%s:%Y' ${INSTALL_ROOT}/auth/auth.json`, archiveLegacy: `${SIDECAR_MANAGED_CONTEXT_GUARD}; [ ! -L ${INSTALL_ROOT}/legacy ] && if [ ! -e ${INSTALL_ROOT}/legacy ]; then install -d -m 0700 ${INSTALL_ROOT}/legacy; fi && for p in ${INSTALL_ROOT}/Dockerfile ${INSTALL_ROOT}/docker-compose.yml ${MANAGED_MARKER_PATH}; do [ ! -L "$p" ] && [ -f "$p" ] || exit 43; done && cp -p -n ${INSTALL_ROOT}/Dockerfile ${INSTALL_ROOT}/docker-compose.yml ${MANAGED_MARKER_PATH} ${INSTALL_ROOT}/legacy/ && for p in ${INSTALL_ROOT}/openai-oauth-sidecar.mjs ${SIDECAR_BUILD_IGNORE_PATH}; do if [ -e "$p" ]; then [ ! -L "$p" ] && [ -f "$p" ] && cp -p -n "$p" ${INSTALL_ROOT}/legacy/ || exit 43; fi; done`, legacyArchiveFiles: `${SIDECAR_MANAGED_CONTEXT_GUARD}; if [ ! -e ${INSTALL_ROOT}/legacy ]; then printf absent; else for p in ${INSTALL_ROOT}/legacy/Dockerfile ${INSTALL_ROOT}/legacy/docker-compose.yml ${INSTALL_ROOT}/legacy/.managed-by-n8n-openai-oauth ${INSTALL_ROOT}/legacy/openai-oauth-sidecar.mjs ${INSTALL_ROOT}/legacy/Dockerfile.dockerignore; do if [ -e "$p" ] || [ -L "$p" ]; then [ ! -L "$p" ] && [ -f "$p" ] && [ "$(stat -c %u:%h "$p")" = 0:1 ] && [ $((0$(stat -c %a "$p") & 022)) -eq 0 ] && sha256sum "$p" || exit 43; fi; done; fi`, legacyStatus: `if [ -L ${INSTALL_ROOT} ] || [ -L ${MANAGED_MARKER_PATH} ] || [ -L ${INSTALL_ROOT}/auth ] || [ -L ${INSTALL_ROOT}/auth/auth.json ] || [ -L ${SIWC_MIGRATION_PENDING_PATH} ] || [ -L ${SIWC_MIGRATED_PATH} ]; then exit 43; elif [ -f ${SIWC_MIGRATED_PATH} ] && [ "$(cat ${SIWC_MIGRATED_PATH})" = '${SIWC_MIGRATION_MARKER_CONTENT.trim()}' ]; then printf managed; elif [ -f ${SIWC_MIGRATION_PENDING_PATH} ] && [ "$(cat ${SIWC_MIGRATION_PENDING_PATH})" = '${SIWC_MIGRATION_MARKER_CONTENT.trim()}' ]; then printf partial; elif [ -f ${MANAGED_MARKER_PATH} ] && [ "$(cat ${MANAGED_MARKER_PATH})" = "${SIDECAR_MARKER_CONTENT.trim()}" ] && [ -f ${INSTALL_ROOT}/auth/auth.json ]; then printf legacy; elif [ -f ${MANAGED_MARKER_PATH} ] && [ "$(cat ${MANAGED_MARKER_PATH})" = "${SIDECAR_MARKER_CONTENT.trim()}" ]; then printf managed; else printf absent; fi`, host: `${COMPOSE_PREFIX} run --rm --no-deps -T --entrypoint node ${SERVICE_NAME} /app/services/siwc-handoff.mjs host`, accept: `${COMPOSE_PREFIX} run --rm --no-deps -T --entrypoint node ${SERVICE_NAME} /app/services/siwc-handoff.mjs accept`, receipt: `${COMPOSE_PREFIX} run --rm --no-deps -T --entrypoint node ${SERVICE_NAME} /app/services/siwc-handoff.mjs receipt`, oneOffContainers: "docker ps -a --filter label=com.docker.compose.project=n8n-openai-oauth --filter label=com.docker.compose.oneoff=True -q", account: `${COMPOSE_PREFIX} run --rm --no-deps -T --entrypoint node ${SERVICE_NAME} /app/services/siwc-handoff.mjs account`, accountLive: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/siwc-handoff.mjs account`, imagesStatus: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/codex-images.mjs status`, imagesLoginStart: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/codex-images.mjs login-start`, imagesLoginPoll: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/codex-images.mjs login-poll`, imagesLoginCancel: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/codex-images.mjs login-cancel`, imagesSignOut: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/codex-images.mjs sign-out`, modelsDiscoveryStatus: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/model-discovery.mjs status`, modelChecksOn: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/model-discovery.mjs checks-on`, modelChecksOff: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/model-discovery.mjs checks-off`, usageStatus: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node /app/services/model-discovery.mjs usage`, signOut: `${COMPOSE_PREFIX} run --rm --no-deps -T --entrypoint node ${SERVICE_NAME} /app/services/siwc-handoff.mjs sign-out`, disablePlan: `${COMPOSE_PREFIX} run --rm --no-deps -T --entrypoint node ${SERVICE_NAME} /app/services/siwc-handoff.mjs disable-plan`, enablePlan: `${COMPOSE_PREFIX} run --rm --no-deps -T --entrypoint node ${SERVICE_NAME} /app/services/siwc-handoff.mjs enable-plan`, stop: `${COMPOSE_PREFIX} stop -t 30 ${SERVICE_NAME}`, models: `${COMPOSE_PREFIX} exec -T ${SERVICE_NAME} node -e '(async()=>{let key="";for await(const chunk of process.stdin){key+=chunk;if(key.length>128)process.exit(1)}const response=await fetch("http://127.0.0.1:10531/v1/models",{headers:{Authorization:"Bearer "+key}});console.log(await response.text());process.exit(response.ok?0:1)})().catch(()=>{console.log("RELMIO_MODEL_CHECK_UNREACHABLE");process.exit(1)})'`, }); const ALLOWED_SIDECAR_COMMANDS = new Set([ PRECHECK_COMMAND, SIDECAR_BUILD_IGNORE_GUARD, SIDECAR_FRESH_CONTEXT_GUARD, SIDECAR_MANAGED_CONTEXT_GUARD, SHARED_ROOT_TIGHTEN_COMMAND, ...DEPLOYMENT_COMMANDS, ...Object.values(VERIFICATION_COMMANDS), ]); export function createDeploymentCommands() { return [...DEPLOYMENT_COMMANDS]; } export function createVerificationCommands() { return { ...VERIFICATION_COMMANDS }; } export function assertSidecarOnlyCommands(commands) { if (!Array.isArray(commands) || commands.length === 0) { throw new TypeError("A sidecar command list is required."); } for (const command of commands) { if ( typeof command !== "string" || !ALLOWED_SIDECAR_COMMANDS.has(command) ) { throw new Error( "Command rejected: only the installer-managed sidecar may be changed; n8n must remain untouched.", ); } } return commands; } export function parseManagedFileHashes(output) { if (typeof output !== "string" || Buffer.byteLength(output) > 8192) { throw new Error("The managed file identities are invalid."); } const files = {}; for (const line of output.trim().split("\n").filter(Boolean)) { const match = /^([a-f0-9]{64}) (\/docker\/n8n-openai-oauth\/[A-Za-z0-9/._-]+)$/u.exec(line); if (!match || match[2].split("/").some(part => part === "." || part === "..") || Object.hasOwn(files, match[2])) throw new Error("The managed file identities are invalid."); files[match[2]] = match[1]; } return files; }