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:
Anso
2026-06-29 14:35:50 -04:00
committed by GitHub
parent 9ff678a7bb
commit 41dc339c26
14 changed files with 481 additions and 53 deletions
+3 -3
View File
@@ -87,7 +87,7 @@ test.describe('Pilot Agent enrollment', () => {
const cmd = await composeFile.innerText();
expect(cmd).toContain('SENCHO_MODE: pilot');
expect(cmd).toContain('SENCHO_PRIMARY_URL:');
expect(cmd).toMatch(/SENCHO_ENROLL_TOKEN: [A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/);
expect(cmd).toMatch(/SENCHO_ENROLL_TOKEN: "?[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+"?/);
});
test('regenerating the enrollment token issues a fresh compose file', async ({ page }) => {
@@ -108,7 +108,7 @@ test.describe('Pilot Agent enrollment', () => {
const firstCompose = page.locator('pre').filter({ hasText: /SENCHO_MODE: pilot/ });
await expect(firstCompose).toBeVisible({ timeout: 10_000 });
const firstText = await firstCompose.innerText();
const firstToken = firstText.match(/SENCHO_ENROLL_TOKEN: ([A-Za-z0-9_.-]+)/)?.[1];
const firstToken = firstText.match(/SENCHO_ENROLL_TOKEN: "?([A-Za-z0-9_.-]+)"?/)?.[1];
expect(firstToken).toBeTruthy();
// Close the enrollment dialog (Escape lands on the row view).
@@ -123,7 +123,7 @@ test.describe('Pilot Agent enrollment', () => {
const secondCompose = page.locator('pre').filter({ hasText: /SENCHO_MODE: pilot/ });
await expect(secondCompose).toBeVisible({ timeout: 10_000 });
const secondText = await secondCompose.innerText();
const secondToken = secondText.match(/SENCHO_ENROLL_TOKEN: ([A-Za-z0-9_.-]+)/)?.[1];
const secondToken = secondText.match(/SENCHO_ENROLL_TOKEN: "?([A-Za-z0-9_.-]+)"?/)?.[1];
expect(secondToken).toBeTruthy();
expect(secondToken).not.toBe(firstToken);
});