tighten permission checks and input validation across the api

This commit is contained in:
Jarek Krochmalski committed 2026-09-02 15:52:44 +02:00
1 parent 77686da02a
commit 5af4e69eb2
47 files changed
+721 -225

No files matched your search

+17 -30
View File
@@ -1,11 +1,11 @@
import { json } from '@sveltejs/kit';
import { join, resolve, basename } from 'path';
import { join } from 'path';
import { existsSync, rmSync, renameSync } from 'fs';
import type { RequestHandler } from './$types';
import { getEnvironment, updateEnvironment, deleteEnvironment, getEnvironmentPublicIps, setEnvironmentPublicIp, deleteEnvironmentPublicIp, deleteEnvUpdateCheckSettings, deleteImagePruneSettings, getGitStacksForEnvironmentOnly, deleteGitStack, getBackupConfigs, getStackSources } from '$lib/server/db';
import { getEnvironment, updateEnvironment, deleteEnvironment, getEnvironmentPublicIps, setEnvironmentPublicIp, deleteEnvironmentPublicIp, deleteEnvUpdateCheckSettings, deleteImagePruneSettings, getGitStacksForEnvironmentOnly, deleteGitStack, getBackupConfigs } from '$lib/server/db';
import { clearDockerClientCache } from '$lib/server/docker';
import { deleteGitStackFiles, getGitReposDir } from '$lib/server/git';
import { getDefaultStacksDir, getLocalStacksDir, isLocalConnection, isStacksDirEnvSet } from '$lib/server/stacks';
import { getStacksDir } from '$lib/server/stacks';
import { authorize } from '$lib/server/authorize';
import { auditEnvironment } from '$lib/server/audit';
import { refreshSubprocessEnvironments } from '$lib/server/subprocess-manager';
@@ -34,9 +34,11 @@ export const GET: RequestHandler = async ({ params, cookies }) => {
if (auth.authEnabled && !await auth.can('environments', 'view')) {
return json({ error: 'Permission denied' }, { status: 403 });
}
const id = parseInt(params.id);
const envAccessDenied = await auth.requireEnvAccess(id);
if (envAccessDenied) return envAccessDenied;
try {
const id = parseInt(params.id);
const env = await getEnvironment(id);
if (!env) {
@@ -80,10 +82,11 @@ export const PUT: RequestHandler = async (event) => {
if (auth.authEnabled && !await auth.can('environments', 'edit')) {
return json({ error: 'Permission denied' }, { status: 403 });
}
const id = parseInt(params.id);
const envAccessDenied = await auth.requireEnvAccess(id);
if (envAccessDenied) return envAccessDenied;
try {
const id = parseInt(params.id);
// Get old values before update for diff
const oldEnv = await getEnvironment(id);
if (!oldEnv) {
@@ -116,7 +119,7 @@ export const PUT: RequestHandler = async (event) => {
// regardless of where they ultimately deploy — so the rename matters
// locally for every env type.
if (isRename) {
const stacksDir = getDefaultStacksDir();
const stacksDir = getStacksDir();
const gitReposDir = getGitReposDir();
const oldStacks = join(stacksDir, oldEnv.name);
const newStacks = join(stacksDir, data.name);
@@ -241,6 +244,12 @@ export const PUT: RequestHandler = async (event) => {
export const DELETE: RequestHandler = async (event) => {
const { params, cookies } = event;
const auth = await authorize(cookies);
// Deleting an environment cascades a filesystem cleanup of its stack dirs, git
// files, and schedules - an instance-level action, not an env-scoped one. Require
// admin under RBAC so an env-scoped role can't remove environments it doesn't own.
if (auth.authEnabled && auth.isEnterprise && !auth.isAdmin) {
return json({ error: 'Permission denied' }, { status: 403 });
}
if (auth.authEnabled && !await auth.can('environments', 'delete')) {
return json({ error: 'Permission denied' }, { status: 403 });
}
@@ -317,7 +326,7 @@ export const DELETE: RequestHandler = async (event) => {
// Clean up stack directory for this environment
// Safety: only delete subdirectory named after the env, never the parent
try {
const stacksDir = getDefaultStacksDir();
const stacksDir = getStacksDir();
const envStackDir = join(stacksDir, env.name);
if (envStackDir !== stacksDir && envStackDir.startsWith(stacksDir) && existsSync(envStackDir)) {
rmSync(envStackDir, { recursive: true, force: true });
@@ -326,28 +335,6 @@ export const DELETE: RequestHandler = async (event) => {
console.error(`Failed to clean up stack directory for environment "${env.name}":`, err);
}
// Flat STACKS_DIR layout: remove per-stack managed dirs for this local environment
if (isStacksDirEnvSet() && isLocalConnection(env)) {
try {
const localStacksDir = getLocalStacksDir();
const resolvedLocalRoot = resolve(localStacksDir);
const stackSources = await getStackSources(id);
for (const source of stackSources) {
const stackDir = join(localStacksDir, source.stackName);
const resolvedStackDir = resolve(stackDir);
if (
resolvedStackDir.startsWith(resolvedLocalRoot + '/') &&
basename(resolvedStackDir) === source.stackName &&
existsSync(stackDir)
) {
rmSync(stackDir, { recursive: true, force: true });
}
}
} catch (err) {
console.error(`Failed to clean up flat STACKS_DIR stacks for environment "${env.name}":`, err);
}
}
// Clean up git-repos directory for this environment
try {
const gitReposDir = getGitReposDir();