Commit Graph

3 Commits

Author SHA1 Message Date
Anso 1c82e3e1d4 fix: contain file-explorer writes and browse reachable out-of-base binds (#1465)
The file-explorer editor save resolved a dangling symlink leaf to the link
path and wrote through it with a plain writeFile, which followed the link and
created a file outside the bind/stack root. Reject a resolved leaf that is
itself a symlink (mirroring the managed-stack guard) and promote the save
through the atomic stage-and-rename helper, so the editor save matches its
documented atomicity and can never leave a partial file or land outside the
root.

Bind-root discovery reported every source outside the compose base as
unreachable without probing it, so a config directory mounted into both the
app and the Sencho container was wrongly non-browsable. Probe the declared
source as Sencho actually sees it; dangerous host roots, docker-socket mounts,
and managed-area overlaps stay blocked, and the dangerous classification also
reads the literal declared source so it holds across platforms.
2026-06-26 09:23:50 -04:00
Anso b753d2d5e0 fix(deploy): preserve compose.override.yml when Mesh is enabled (#1420)
When a single-file stack is opted into Sencho Mesh, the deploy builds an
explicit `docker compose -f <base> -f <mesh override>` list. Passing any
explicit -f disables Compose's automatic discovery of compose.override.yml
(and the docker-compose.override variants), so a user's hand-authored
override was silently dropped from the effective deploy once Mesh was on.

Resolve the user's override file (first existing variant, with the same
stack-name and symlink-containment guards as the base compose file) and
insert it between the base and the mesh override, so it layers exactly as
Compose's implicit discovery would, with the mesh override still taking
precedence. A transient read failure during the lookup degrades to "no
override" rather than failing the deploy; a stack-name or containment-guard
rejection still aborts. Multi-file Git-source stacks and non-mesh deploys
are unaffected.
2026-06-23 10:44:49 -04:00
Anso 69ba0e6d21 fix(stacks): harden stack file path containment against symlink escapes (#1415)
* fix(stacks): harden stack file path containment against symlink escapes

The legacy managed-stack methods enforced path containment lexically
(path.resolve + startsWith), which does not follow symlinks. A stack
directory under the compose root that is itself a symlink or junction
could let a managed-file operation (write compose.yaml/.env, delete a
stack, backup, restore, snapshot) follow the link and read, write, or
delete a file of the same name outside the compose root.

Add a realpath-based containment guard that walks up to the deepest
existing path component, confirms its canonical location is inside the
canonical compose root, and rejects both an out-of-tree resolution and a
dangling symlink (which a write or mkdir would still follow). The guard
runs at every legacy managed-stack sink, alongside the existing lexical
barriers. A legitimately symlinked compose root is not a false positive
because both sides are canonicalized through the same link, and the
guard is a no-op for not-yet-created targets so stack creation and the
flat-to-directory migration are unaffected.

* fix(stacks): satisfy the path-injection sanitizer in the symlink containment guard

assertRealWithinBase resolves a user-derived path and probes it with
realpath/lstat to run the containment check, which static analysis flags
as path injection because the probes lacked the inline barrier its
sanitizer recognizes. Add the canonical path.resolve + startsWith barrier
at the top of the helper, the same form every other sink in this file
uses, and seed the realpath walk from the sanitized value.

Behavior is unchanged: callers always pass an absolute, already-contained
path, so the barrier is a no-op pre-check for them, and the realpath walk
still catches the symlink and dangling-link escapes.
2026-06-22 19:53:59 -04:00