mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-13 12:17:34 +00:00
fix: enforce 1:1 compose path mapping for Pilot agent mounts (#1516)
* fix: enforce 1:1 compose path mapping for Pilot agent mounts Pilot enrollment now generates validated 1:1 bind mounts so every agent path maps to a unique compose directory. Persisted agent paths reconcile during startup to catch drift. Unsafe relative-bind redeploys are blocked before container removal to prevent path escapes. - Add composePathMapping utility with strict path validation - Generate COMPOSE_DIR and validated mounts during Pilot enrollment - Reconcile persisted agent paths during startup bootstrap - Block redeploy when a relative-bind mount would escape the compose root - Default Pilot UI path to /opt/docker/sencho - Update multi-node and pilot-agent documentation - Add regression tests for enrollment, bootstrap, compose-service, and environment-check paths * fix: update E2E enrollment regexes for YAML-quoted token values
This commit is contained in:
@@ -26,6 +26,7 @@ import DockerController from './DockerController';
|
||||
import { NodeRegistry } from './NodeRegistry';
|
||||
import SelfIdentityService from './SelfIdentityService';
|
||||
import { withTimeout } from '../utils/withTimeout';
|
||||
import { pathsMatch, resolveHostBindPath } from '../utils/composePathMapping';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
@@ -222,17 +223,8 @@ function checkPathMapping(dir: string, mounts: BindMounts): EnvironmentCheck {
|
||||
detail: 'Sencho is not running in a container; host and container paths are the same.',
|
||||
};
|
||||
}
|
||||
const target = normPath(dir);
|
||||
// The bind mount covering the compose dir is the one whose destination is the
|
||||
// longest path prefix of it, so a parent bind (-v /opt:/opt) covers
|
||||
// COMPOSE_DIR=/opt/compose just as a direct -v /opt/compose:/opt/compose does.
|
||||
const match = mounts
|
||||
.filter(m => {
|
||||
const d = normPath(m.destination);
|
||||
return target === d || target.startsWith(d + '/') || target.startsWith(d + '\\');
|
||||
})
|
||||
.sort((a, b) => normPath(b.destination).length - normPath(a.destination).length)[0];
|
||||
if (!match) {
|
||||
const hostPath = resolveHostBindPath(dir, mounts);
|
||||
if (!hostPath) {
|
||||
return {
|
||||
...base,
|
||||
status: 'warn',
|
||||
@@ -242,11 +234,7 @@ function checkPathMapping(dir: string, mounts: BindMounts): EnvironmentCheck {
|
||||
+ `bind mounts in your stacks resolve against the container filesystem instead of the host.`,
|
||||
};
|
||||
}
|
||||
// The host path the daemon resolves for the compose dir: the mount source
|
||||
// plus the compose dir's path below the mount destination.
|
||||
const relative = target.slice(normPath(match.destination).length);
|
||||
const hostPath = normPath(normPath(match.source) + relative);
|
||||
if (hostPath !== target) {
|
||||
if (!pathsMatch(hostPath, dir)) {
|
||||
return {
|
||||
...base,
|
||||
status: 'warn',
|
||||
|
||||
Reference in New Issue
Block a user