Commit Graph

48 Commits

Author SHA1 Message Date
Anso ce4eeb6b93 fix(security): bump grpc to 1.82.1 in docker CLI and compose builds (#1678)
* fix(security): bump grpc to 1.82.1 in docker CLI and compose builds

Clear GHSA-hrxh-6v49-42gf from the source-built docker and
docker-compose binaries scanned by Trivy.

* docs: clarify grpc bump is a minor security release
2026-07-22 17:02:47 -04:00
dependabot[bot] ed15153fea chore(deps): bump node from 725aeba to e88a35b (#1634)
Bumps node from `725aeba` to `e88a35b`.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 21:18:10 -04:00
dependabot[bot] 2d21982840 chore(deps): bump golang from 7870fdc to dcbb18c (#1633)
Bumps golang from `7870fdc` to `dcbb18c`.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.27rc2-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-15 21:17:54 -04:00
dependabot[bot] 7bf663442b chore(deps): bump golang from 1.26.4-alpine to 1.27rc2-alpine (#1593)
Bumps golang from 1.26.4-alpine to 1.27rc2-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.27rc2-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-08 14:07:50 -04:00
dependabot[bot] d2269cbb87 chore(deps): bump node from a2dc166 to 725aeba (#1540)
Bumps node from `a2dc166` to `725aeba`.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-01 12:43:25 -04:00
dependabot[bot] fdd43003c6 chore(deps): bump node from 9c0e1e5 to a2dc166 (#1437)
Bumps node from `9c0e1e5` to `a2dc166`.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 16:15:00 -04:00
dependabot[bot] 5fdef516be chore(deps): bump golang from f1ddd9f to 3ad5730 (#1436)
Bumps golang from `f1ddd9f` to `3ad5730`.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.26.4-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 16:14:57 -04:00
Anso 7c5ba1cf68 fix(build): patch bundled Docker CLI golang.org/x/net to v0.55.0 (#1421)
The Docker CLI binary baked into the image (cli-builder stage, built from
docker/cli v29.4.1's vendored tree) shipped golang.org/x/net v0.53.0, which the
image scan flags for six HIGH advisories: CVE-2026-25680, -25681, -27136, -39821,
-42502, and -42506 (x/net/html parsing denial of service and an x/net/idna
Punycode issue), all fixed in v0.55.0.

Drop docker/cli's committed vendor/ tree, bump golang.org/x/net to v0.55.0 with
`go get`, and build the CLI in module mode (-mod=mod) so the patched module is
resolved from the module proxy instead of the stale vendored v0.53.0. This stage
now fetches modules at build time rather than building fully offline. `go mod tidy`
is intentionally avoided because docker/cli ships a vendor.mod manifest rather than
a standard go.mod, so a full tidy does not run cleanly against it.
2026-06-23 08:44:47 -04:00
Anso b9314eb67f chore(deps): bump bundled containerd to v2.2.5 to clear CVE-2026-53488/53489/53492 (#1401)
Three newly published HIGH containerd advisories (CVE-2026-53488,
CVE-2026-53489, CVE-2026-53492) affect github.com/containerd/containerd/v2
v2.2.4, which the compose-builder stage pins via go get. They are CRI
checkpoint/restore and restart-monitor issues, fixed in v2.2.5.

Bump the pin from v2.2.4 to v2.2.5 so the bundled docker-compose binary
scans clean again. The vulnerable code paths are daemon-side (containerd's
CRI service) and are not reached by compose, so this stays defense-in-depth.

This is a build-time dependency bump with no product behavior change, so it
is typed chore (no release-please bump, no user-facing changelog entry).
2026-06-20 15:45:27 -04:00
dependabot[bot] d364f6acc8 chore(deps): bump node from 144769e to 9c0e1e5 (#1384)
Bumps node from `144769e` to `9c0e1e5`.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 13:25:49 -04:00
dependabot[bot] fca659c815 chore(deps): bump golang from f23e8b2 to f1ddd9f (#1383)
Bumps golang from `f23e8b2` to `f1ddd9f`.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.26.4-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-17 13:25:30 -04:00
dependabot[bot] a521db2ba8 chore(deps): bump golang from 1.26.3-alpine to 1.26.4-alpine (#1304)
Bumps golang from 1.26.3-alpine to 1.26.4-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.26.4-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anso <dev@saelix.com>
2026-06-04 16:23:34 -04:00
dependabot[bot] c0551a656b chore(deps): bump node from 7c6af15 to 144769e (#1303)
Bumps node from `7c6af15` to `144769e`.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anso <dev@saelix.com>
2026-06-04 16:23:07 -04:00
Anso c67478b50d chore(security): VEX three new compose CVE findings and correct dependency comment (#1201)
Trivy reported three new advisories against the compose binary today:

- CVE-2026-41568 (docker cp symlink-swap empty-file creation, sibling of
  CVE-2026-42306)
- CVE-2026-33997 (Moby plugin install privilege bypass)
- GHSA-pmwq-pjrm-6p5r (in-toto-golang glob-negation operator mismatch)

All three are daemon-side or supply-chain attestation code paths that
compose only embeds as client-side libraries. Sencho invokes compose
exclusively for up/down/ps against user-authored compose files, never
runs daemon endpoints, never performs plugin installation, and never
verifies in-toto attestations. Each gets a not_affected statement with
a full impact rationale; bump version 7 to 8 and last_updated to today.

Also correct the compose-builder comment in Dockerfile. The previous
text claimed compose v5.1.3 "eliminated CVE-2026-34040 and CVE-2026-33997
at the dependency level" by moving docker/docker to an indirect dep.
That is wrong: the module is still pulled via buildkit and other
transitive paths, the CVEs still appear in scans, and they are tracked
in the VEX file rather than eliminated. The corrected comment points
the reader at the VEX file as the source of truth for these findings.
2026-05-24 18:30:39 -04:00
Anso 0951a0e792 chore(deps): bump containerd to v2.2.4 to clear CVE-2026-46680 (#1199)
The compose-builder stage now pulls containerd/v2 v2.2.4 alongside the
existing otel security bumps. containerd v2.2.3 (the version pinned by
docker/compose v5.1.3) carries CVE-2026-46680, a runAsNonRoot evasion in
the runtime executor; v2.2.4 is the upstream patch release that fixes
it. The vulnerable code path is daemon-side and was never reachable from
compose, but bumping at the dependency level removes the entry from the
SBOM rather than relying on a VEX suppression.

Removed the corresponding statement from security/vex/sencho.openvex.json
and bumped version + last_updated.

The remaining three statements (docker/docker daemon CVEs) stay
suppressed: the fix lives on a new github.com/moby/moby/v2 module path,
and compose has not migrated imports yet, so no resolvable version bump
clears them.
2026-05-24 18:22:27 -04:00
dependabot[bot] ec87a63f0a chore(deps): bump node from e71ac5e to 7c6af15 (#1171)
Bumps node from `e71ac5e` to `7c6af15`.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-05-23 03:05:24 -04:00
Anso efcc06d50b ci: harden CI and supply-chain pipeline (#1169)
* ci: harden CI and supply-chain pipeline

* Add frontend Vitest step to ci.yml so the 241 existing frontend tests run on
  every PR (mirrors the backend build/test/lint/audit order).
* Pin Node 26 as a single source of truth: new .node-version, node-version-file
  on all setup-node calls, engines.node ">=26.0.0" in all three package.json
  files. Matches the Dockerfile's node:26-alpine.
* SHA-pin remaining mutable actions in the start-app composite
  (actions/setup-node v6, actions/cache v4.3.0).
* Pin Dockerfile supply-chain inputs: golang:1.26.3-alpine by sha256 digest in
  both builder stages; replace mutable-tag git clone with commit-SHA fetch for
  docker/cli (v29.4.1) and docker/compose (v5.1.3). LDFLAGS version strings
  and otel patch preserved unchanged.
* Ref-scope docker-publish concurrency so two different release tags cannot
  cancel each other; same-ref reruns still cancel as before.
* Harden CLA workflow: drop actions:write from permissions; tighten the
  issue_comment trigger to PRs only (github.event.issue.pull_request != null)
  matching the two documented CLA phrases. No PR code is checked out.
* Drop trivy-version: latest from both Trivy scans so the SHA-pinned
  aquasecurity/trivy-action governs the bundled binary version. The
  HIGH/CRITICAL gate, severity filter, and trivy.yaml (OpenVEX) are unchanged.
* Restructure Dependabot: add applies-to: security-updates groups for npm
  (root/backend/frontend), docker, and github-actions; switch github-actions
  to directories so the local composite action is monitored alongside the
  top-level workflows.
* Add a daily scheduled SARIF security scan (security-scan.yml): two parallel
  jobs scanning saelix/sencho:latest and a fresh main HEAD build, uploading to
  GitHub code scanning. Least-privilege (contents: read, security-events:
  write). Visibility only; existing PR-blocking and release-blocking Trivy
  gates are not weakened.

Validation: backend tsc clean; frontend tsc clean; frontend npm test 27 files
/ 241 tests pass; npm audit --audit-level=high passes at root, backend, and
frontend; docker buildx build --check passes with no warnings (all pinned
digests resolve from the registry).

* ci(frontend): set explicit jsdom URL so localStorage initializes in CI

jsdom does not instantiate window.localStorage / sessionStorage when the
document has the opaque about:blank origin. Five frontend test files that
call localStorage.clear() in beforeEach started failing once the new
frontend Vitest step in this PR began running them on Linux CI runners.

Configuring environmentOptions.jsdom.url with a real same-origin URL is
the documented Vitest 4.x workaround and is a config-only change. All 27
test files (241 tests) pass locally with the fix applied.
2026-05-23 02:37:29 -04:00
dependabot[bot] 90702c0999 chore(deps): bump node from 25-alpine to 26-alpine (#1038)
Bumps node from 25-alpine to 26-alpine.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Anso <dev@saelix.com>
2026-05-14 10:17:40 -04:00
Anso 19cdb3681d fix: harden blueprint deployment guardrails (#1027)
* fix: harden blueprint deployment guardrails

* fix: update Docker toolchain to Go 1.26.3

* fix: repair Dockerfile tr argument split across lines

* fix: bump protobufjs to clear npm audit high-severity advisories
2026-05-12 15:49:19 -04:00
Anso e5b1c7b22b refactor(backend): collapse entitlement provider abstraction back to LicenseService (#889)
Removes backend/src/entitlements/ (registry, loadProvider,
CommunityEntitlementProvider, types, headers, normalize) and the two
abstraction-only tests. Relocates headers/normalize/types to
services/license-*.ts. Swaps 22 consumer call sites from
getEntitlementProvider() to LicenseService.getInstance(). Drops the
Dockerfile install step plus PRO_PACKAGE_VERSION build-arg and
github_token BuildKit secret in docker-publish.yml. Removes the now
stale no-restricted-imports rule in backend/eslint.config.mjs.

Net: 37 files changed, ~700 lines removed, no behavior change. Local
dev no longer requires GitHub Packages auth to start the backend.

Rationale and revisit conditions in
docs/internal/adrs/2026-05-02-collapse-entitlement-provider.md.
2026-05-02 23:45:44 -04:00
Anso 638bd808f8 ci(docker): install @studio-saelix/sencho-pro in production builds (#881)
Phase 2b of the open-core hybrid extraction. After Phase 2a (PR #880)
wired the public-side loader to dynamic-import the private package,
this PR makes production Docker builds actually install the package
so the runtime path uses it. Single image; saelix/sencho remains the
only published image (the original ADR's dual-image plan was rejected
because customers buying paid tiers would otherwise need GitHub auth
to pull a second image, breaking the purchase flow).

Dockerfile (prod-deps stage): a new RUN block after npm ci installs
@studio-saelix/sencho-pro using a BuildKit secret-mounted github_token
for npm.pkg.github.com auth. The .npmrc carrying the token is written
and removed inside the same RUN, plus /root/.npm is wiped to scrub
any verbose-log artifacts that npm might otherwise stash. The token
never enters an image layer (BuildKit excludes secret content from
both layer filesystems and cache keys; docker history shows the
literal $(cat /run/secrets/...) command, not the substituted value).

PRO_PACKAGE_VERSION is a build arg pinned by CI to a literal SemVer
(0.1.0 today) so the scan build and the publish build resolve to the
same package version. Default of `latest` keeps local builds
convenient. When the pro package ships a new version, bump the value
in docker-publish.yml in the same PR that ships the matching public
Sencho release; release-please does not coordinate the two cadences.

Empty-secret branch (no github_token provided, e.g. local dev or
fork PRs) skips the install and prints a notice. The resulting image
runs through the loader's in-tree LicenseService fallback, so PR
validation builds and contributor builds work without any GitHub
auth setup.

docker-publish.yml: both build-push-action invocations (the
pre-publish scan and the multi-arch publish) pass the github_token
secret and PRO_PACKAGE_VERSION build arg. The auto-provisioned
GITHUB_TOKEN's packages:read scope is sufficient because the public
Sencho repo and the private package live in the same Studio-Saelix
GitHub org. Moving the package to a different org would silently
break this contract; the Dockerfile comment block records the
invariant.

ci.yml is intentionally not changed. The PR-time Docker validation
job builds without the secret and exercises the loader's in-tree
fallback path, which is correct for fork PRs (no token access) and
useful for catching fallback-path regressions.

Test plan: tsc clean (no TS changes). The dockerfile install path is
exercised by the next release's pre-publish scan + smoke test, both
of which boot the actual image and call /api/health. Failed dynamic
import or constructor throw would block bootstrap before the listener
binds, so the existing smoke test covers the runtime contract.
2026-05-02 13:24:33 -04:00
Anso e124874dac fix(docker): upgrade CLI to v29.4.1 and Compose to v5.1.3, clear VEX (#836)
* fix(docker): upgrade CLI to v29.4.1 and Compose to v5.1.3, clear VEX

* fix(vex): restore CVE-2026-34040 not_affected statement for compose

* fix: correct VEX product matching for CVE-2026-34040
2026-04-29 01:18:08 -04:00
Anso eb1d627096 perf(docker): switch builder stages to npm ci (#822)
Both frontend-builder and backend-builder ran `npm install` even
though `prod-deps` already used `npm ci`. `npm install` walks the
dep graph and silently rewrites the lockfile when there is drift,
which costs build time and lets a stale lockfile slip into a release
image. `npm ci` enforces the lockfile, fails fast on drift, and skips
the resolution work since the dep tree is fully described by the
lockfile.

Both lockfiles are in sync (verified with `npm install
--package-lock-only` reporting "up to date"), so the change is a
pure tightening with no behavior delta in the happy path.
2026-04-28 02:35:50 -04:00
Anso 06d9ef5904 fix(docker): build compose plugin from ./cmd, not ./cmd/compose (#803)
* fix(docker): build compose plugin from ./cmd, not ./cmd/compose

The compose-builder stage in Dockerfile was building ./cmd/compose, which
in docker/compose v5.1.2 is `package compose` (cobra command definitions),
not the CLI plugin entry point. `go build -o file pkg` against a non-main
package writes a Go archive; COPY and chmod accept it, but the kernel
cannot exec an ar-format file, so the Docker CLI plugin manager rejected
the binary and surfaced only as `docker: unknown command: docker compose`
in the v0.64.0 release smoke gate. The plugin's actual main package is at
./cmd (cmd/main.go calls plugin.Run from the cli-plugins framework), per
the upstream Makefile in v5.1.2.

Also fix the version ldflag to target the v5 module path: in v5 the module
moved from github.com/docker/compose/v2 to .../v5, so the previous
-X .../v2/internal.Version= silently no-op'd and `docker compose version`
would have reported an empty string even after the build target fix.

Add -trimpath and -s -w to match the upstream Makefile's release flags.

Add an inline ELF-magic sanity check inside the compose-builder stage so
this exact failure mode (non-main package emitting an archive) fails the
build at the producer instead of surfacing 200 lines later. Hex match via
`od -tx1` works on both busybox and GNU coreutils; the named-character
form (`od -c`) renders fields differently across the two.

Add a binary smoke step to the docker-validate PR job that mirrors the
release-time gate, so a regression in either the CLI or compose source
build is caught at PR review time rather than at tag-push time.

* fix(vex): match Trivy purl format for bundled docker/docker version

The compose plugin build target fix exposes a real consequence: now that
compose-builder produces a true ELF binary (rather than a Go archive),
Trivy can read the embedded Go module metadata and correctly reports the
bundled docker/docker dependency as `v28.5.2+incompatible`. The existing
VEX subcomponent ID omitted the `+incompatible` Go-module suffix, so
Trivy's purl matcher rejected the suppression and CVE-2026-34040 surfaced
in the docker-validate scan against the rebuilt image.

Update the @id to the literal version Trivy emits and bump the document
version per OpenVEX spec. The justification (compose acts as a CLI
client, never as a daemon, so the authz-hook path is unreachable) is
unchanged and continues to apply to v28.5.2+incompatible identically.

* docs(security): explain why CVE-2026-34040 cannot be upgraded out

A direct in-build upgrade attempt (`go get github.com/docker/docker@<sha
of docker-v29.3.1>`) fails with:

  invalid version: go.mod has post-v1 module path
  "github.com/moby/moby/v2" at revision f78c987a

moby/moby migrated its Go module path to `github.com/moby/moby/v2` on
the docker-29.x branch. The legacy `github.com/docker/docker` import path
is therefore frozen at v28.5.2+incompatible from Go's perspective; the
proxy.golang.org listing for that module path confirms no v29.x version
is resolvable. compose v5.1.2 (and v5.1.3, the latest tag at the time of
writing) both still import the legacy path, so the bundled docker/docker
library cannot be moved past v28.5.2+incompatible until upstream compose
migrates its imports.

Document the constraint in two places so a future maintainer (or future
me) does not re-attempt the same `go get` and arrive at the same dead
end:

- Dockerfile compose-builder stage: comment block above the build step.
- security/vex/sencho.openvex.json: expand the CVE-2026-34040 statement's
  impact_statement to spell out the upstream module-split blocker. The
  not_affected status (compose runs as a CLI client, never executes the
  daemon authz hook code path) is the principled triage and remains the
  correct OpenVEX call, not a deferred upgrade.

No build or runtime behaviour changes. Pure documentation enrichment.

* fix(security): mirror CVE-2026-34040 VEX statement in .trivyignore

Trivy reports `Some vulnerabilities have been ignored/suppressed` but
still fails the docker-validate scan on CVE-2026-34040, because the
OpenVEX statement's product identifier `pkg:oci/sencho` cannot match
the build-local image tag `sencho:pr-test`. Trivy does not generate an
OCI purl for images that lack a registry digest (aquasecurity/trivy
issue 9399), so VEX product matching is a no-op at PR and release scan
time. The dual-layer pattern documented in .trivyignore's header was
designed for exactly this case: VEX is the canonical, attested,
published triage record, and .trivyignore mirrors entries that scan-time
matching cannot resolve, with a comment pointing to the corresponding
VEX justification.

The CVE-2026-34040 OpenVEX statement is unchanged (status=not_affected,
justification=vulnerable_code_not_in_execute_path). The .trivyignore
entry is a build-time scan filter only, with a referencing comment so
auditors land on the VEX file as the source of truth. CVE-2026-39883
already follows this pattern and continues to.
2026-04-27 09:10:21 -04:00
dependabot[bot] 82894164e2 chore(deps): bump node from 22-alpine to 25-alpine (#792)
Bumps node from 22-alpine to 25-alpine.

---
updated-dependencies:
- dependency-name: node
  dependency-version: 25-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-27 00:22:44 -04:00
Anso 7e4ea714c1 feat(security): rebuild Docker CLI/Compose from source, pin base image digests (#789)
* feat(security): rebuild Docker CLI/Compose from source, pin base image digests

Build Docker CLI v29.4.0 and Compose v5.1.2 from source against Go 1.26.2
to resolve 7 CVEs that were accepted in .trivyignore:

- CVE-2026-32280/32281/32282/32283/33810: Go stdlib x509/TLS and DNS
  issues in the upstream static CLI binary (compiled with Go 1.26.1).
  All fixed by rebuilding with Go 1.26.2.
- CVE-2026-33186: grpc 1.78.0 in Docker CLI. Eliminated from the SBOM
  by building with the patched Go toolchain and updated module graph.
- CVE-2026-33671: picomatch ReDoS in npm bundled with node:22-alpine.
  Resolved by removing npm/npx from the runtime image entirely (npm is
  only needed at build time).

Remaining 5 entries in .trivyignore are vendored deps inside Compose
v5.1.2 (buildkit, moby/docker, otel) that cannot be patched without an
upstream Compose release. These will be expressed as OpenVEX
not_affected statements in the follow-up PR (feat/security-sbom-vex).

Also in this commit:
- Pin all Dockerfile FROM lines to @sha256: digests (node:22-alpine,
  tonistiigi/xx, golang:1.26-alpine) to prevent silent base image
  changes between scan and publish.
- SHA-pin all GitHub Actions in docker-publish.yml and ci.yml that
  were previously referenced by mutable @vN tags.
- Add a binary version smoke test to confirm docker/compose produce
  expected output before the multi-arch push proceeds.

* fix(docker): fix cli-builder vendor mode and compose-builder cache path

docker/cli v29.4.0 uses CalVer and ships vendor.mod instead of go.mod,
so plain `go build` fails with "cannot find main module." Fix: copy
vendor.mod -> go.mod and vendor.sum -> go.sum before building, then
pass -mod=vendor so all deps come from the vendored tree with no network
access. Cache mount is not needed with vendor mode and is removed.

compose-builder used /root/go/pkg/mod as the cache mount target, but
golang:alpine sets GOPATH=/go, so the module cache lives at /go/pkg/mod.
The wrong path caused a silent cache miss on every build. Corrected.
2026-04-26 22:13:35 -04:00
Anso ed553f1f19 feat: change default listen port from 3000 to 1852 (#756)
Updates the backend listen port, Vite dev proxy target, Docker EXPOSE,
compose port mapping, .env.example default, GitHub Actions smoke-test
default, healthcheck URLs, and every doc/example reference. Test fixtures
that include example URLs were updated for consistency, though their
assertions are port-agnostic.

The rate-limit value of 3000 in middleware/rateLimiters.ts and the
3000 entry in WEB_UI_PORTS (which detects user containers like Grafana)
are intentionally untouched.
2026-04-24 22:23:31 -04:00
Anso dc8370f5a4 fix(security): harden Trivy scan lifecycle, logging, and docs (#639)
* fix(security): harden Trivy scan lifecycle, logging, and docs

- Call TrivyService.initialize() at startup so capability state is
  accurate before first request; add periodic re-detect to the scheduler
  so newly installed Trivy binaries light up without a restart.
- Add markStaleScansAsFailed sweep (+ idx_vuln_scans_status index) to
  recover any scan row left in_progress after a crash or timeout; sweep
  runs before the paid-tier gate so every tier self-heals.
- Split scanImage persistence into beginScan/finishScan so the manual
  scan route owns a single code path and can return a scanId synchronously
  while work continues asynchronously.
- Validate image refs on /api/security/scan and /sbom via new utility;
  defense-in-depth against shell-metacharacter payloads.
- Dispatch a warning-level alert when a post-deploy scan fails so the
  operator has a user-visible path to the failure instead of a silent log.
- Share DIGEST_CACHE_TTL_MS and severity ordering across service and
  route layers; remove dead invalidateDetection().
- Add [Trivy:diag] logging gated behind developer_mode for support
  diagnostics; production logs unchanged.
- Frontend: defensive toast fallback chain, sr-only SheetDescription,
  and a truncation badge when the 500-item detail fetch is capped.
- Tests: extend trivy-service and vulnerability-db suites; add
  image-ref and severity unit tests.
- Docs: expand vulnerability-scanning troubleshooting with recovery,
  re-detect, and diagnostic-log guidance; link Dockerfile comment to
  trivy-setup.

* fix(security): drop unnecessary escape in image-ref forbidden-char regex
2026-04-16 20:32:38 -04:00
Anso a17b16b258 fix(scheduler): harden auto-update policies with cascade deletes, error reporting, and UI fixes (#545)
* fix(scheduler): harden auto-update policies with cascade deletes, error reporting, and UI fixes

- Fix orphaned task runs on policy/node deletion with transaction-wrapped cascade deletes
- Make manual trigger non-blocking (202 Accepted) to prevent proxy timeouts
- Distinguish registry check failures from clean "no update" results via structured ImageCheckResult
- Trim whitespace-only policy names in both frontend and backend validation
- Add strokeWidth={1.5} to action icons per design system
- Add sr-only DialogDescription for Radix accessibility
- Replace Select with Combobox for frequency picker
- Wrap run history sheet content in ScrollArea
- Support concurrent Run Now indicators via Set-based state
- Abort stale stack fetches on node switch with AbortController
- Add standard and diagnostic logging to SchedulerService and ImageUpdateService
- Add tests for cascade deletes, image checking, and scheduler edge cases
- Add troubleshooting section to auto-update docs

* fix(tests): resolve lint errors in image-update-service tests

Remove unused mock variables (mockGetImage, mockGetDocker) and unused
ImageCheckResult type import. Replace CommonJS require('yaml') with
ESM import to satisfy no-require-imports rule.

* chore(deps): bump Docker CLI to 29.4.0 and Compose to v5.1.2

Resolves Trivy CVE-2026-32282 (Go stdlib symlink follow in Root.Chmod)
by upgrading to releases that ship Go 1.25.9. Compose v5.1.2 also bumps
grpc to 1.80.0, resolving CVE-2026-33186.

* chore(security): accept CVE-2026-32282 in .trivyignore, update stale refs

Go stdlib symlink-following in Root.Chmod (CVE-2026-32282) affects both
Docker CLI 29.4.0 (Go 1.26.1) and Compose v5.1.2 (Go 1.25.8). Fix
requires Go 1.25.9 or 1.26.2; no upstream static binary ships a patched
runtime yet. The vulnerable code path requires a chroot context with
attacker-controlled filesystem, which does not apply to our usage.

Also updates version references from v5.1.1/v29.3.1 to v5.1.2/v29.4.0
for existing CVE entries, and notes that Compose v5.1.2 resolved
CVE-2026-33186 (grpc bumped to 1.80.0) for the compose binary.
2026-04-13 09:49:11 -04:00
Anso 16a6adfda9 docs(dockerfile): fix stale entrypoint comment (#504)
The comment next to the ENTRYPOINT directive still described the old
default ("fixes volume ownership as root then drops to sencho via
su-exec"), contradicting the updated explanatory block a few lines up.
Reword to match the new default: exec as root, drop to $SENCHO_USER
only when opted in.
2026-04-10 22:16:01 -04:00
Anso 9eb945a6f0 fix: run as root by default to eliminate stack-folder permission failures (#501)
Every filesystem operation against user compose folders (save, create,
deploy, update, rollback, template install, fleet snapshot restore)
previously failed with EACCES whenever a stack container had chowned
its own bind mount to another UID, which is extremely common with
linuxserver/* images and anything that runs as root by default.

Running Sencho as root eliminates the entire class of permission bugs
at the source and matches the default posture of Portainer, Dockge,
Komodo, and Yacht. Mounting /var/run/docker.sock is already equivalent
to root-on-host, so the previous non-root hardening provided essentially
no additional isolation while breaking real features.

Changes:

- docker-entrypoint.sh: default path stays root, no GID dance, no
  privilege drop. Opt-out via SENCHO_USER=sencho restores the legacy
  behavior bit-for-bit (chown data dir, match Docker socket GID,
  su-exec to the user). Fails fast if SENCHO_USER names a nonexistent
  account. Kubernetes / OpenShift forced-non-root compat preserved via
  the existing id -u = 0 guard.
- FileSystemService: delete forceDeleteViaDocker (the ~40-line helper
  that shelled out to an alpine container to work around EACCES during
  deleteStack) and simplify deleteStack to a single fsPromises.rm call.
  Tests updated accordingly.
- Dockerfile: keep the sencho user+group pre-created so the opt-out
  path works out of the box; comments updated to document the new
  default.
- Docs: new "Container user" section in configuration.mdx documenting
  the root default and the SENCHO_USER opt-out; troubleshooting and
  self-hosting updated to match.
2026-04-10 21:35:31 -04:00
Anso bed7269c27 ci: daily cache-bust the Alpine apk upgrade layer (#491)
The runtime stage's `apk upgrade --no-cache` layer was being reused from
the buildx gha/registry cache indefinitely, so newly-patched Alpine
packages (e.g. openssl CVE fixes) could sit behind a stale layer until
an unrelated Dockerfile change invalidated it by coincidence. This
surfaced as Trivy hard-failing on CVE-2026-28390 (libcrypto3/libssl3
DoS in CMS) on the github-app-token-migration PR, even though upstream
Alpine had already published 3.5.6-r0 with the fix.

Add an APK_CACHE_BUST build-arg that CI sets to the current UTC date
(YYYY-MM-DD). Each calendar day the arg value changes, which changes
the RUN layer's hash, which forces buildx to re-execute `apk upgrade`
and pick up whatever Alpine has published that day. All three build
invocations (PR docker-validate, release pre-publish scan, release
multi-arch push) pass the same value, so the scan and the published
artifact always share a layer and Trivy never scans stale bits.

Default value `unset` lets local developers `docker build` without
the arg; production CI always supplies the date.
2026-04-10 15:44:40 -04:00
Anso a55d1245f8 fix(fleet): resolve version detection pipeline for Docker builds (#402)
* fix(fleet): resolve version detection pipeline for Docker builds

The Dockerfile backend-builder stage was missing a COPY of the root
package.json, causing generate-version.js to fall back to "0.0.0-dev"
at build time. At runtime, the filesystem walk also failed (root
package.json not in the final image), producing the string "unknown"
which the frontend rendered as "vunknown".

Changes:
- Dockerfile: copy root package.json into backend-builder stage
- CapabilityRegistry: return null (not "unknown") for unresolvable
  versions; add isValidVersion() type guard; normalize remote meta
  responses to strip "unknown"/"0.0.0-dev" sentinel values
- Fleet endpoints: hoist gateway version validation outside per-node
  loops; treat unresolvable remote versions as "potentially outdated"
  instead of silently marking them up to date
- FleetView: guard all version display points (card badge, update
  button, gateway label, modal columns) via shared formatVersion()
- EditorLayout, CapabilityGate: use shared isValidVersion utility
- New frontend/src/lib/version.ts shared utility
- Docs: add troubleshooting section for version display edge cases
- Screenshots: updated Fleet Overview and Node Updates modal

* docs: update fleet node updates screenshot with live remote node
2026-04-06 03:03:56 -04:00
Anso 36ebd5a9c1 fix(docker): upgrade Compose v2.40.3 → v5.1.1 to remediate dependency CVEs (#283)
Compose v2.40.3 was compiled with Go 1.24.9, grpc 1.74.2, and
x/crypto 0.38.0 — all below the minimum safe versions identified
in the March 2026 security audit. Upgrading to v5.1.1 (Go 1.25.8,
x/crypto 0.46.0) resolves CVE-2025-68121, CVE-2025-61726,
CVE-2025-61729, CVE-2026-25679, and CVE-2025-47913.

CVE-2026-33186 (grpc ≥1.79.3) remains unresolved — no upstream
Docker CLI or Compose release includes grpc past 1.78.0 yet.

CVE-2026-33671 (picomatch) and CVE-2026-33747/33748 (buildkit) were
already patched in prior releases.
2026-03-30 17:46:44 -04:00
Anso f9b86e6f53 fix(docker): install Docker CLI v29.3.1 from static binaries to resolve CVEs (#268)
Alpine 3.23 ships docker-cli 29.1.3 which contains unpatched vulnerabilities:
- CVE-2026-33186 (Critical): gRPC-Go authorization bypass
- CVE-2026-34040 (High): Moby AuthZ plugin bypass
- CVE-2026-33747 (High): BuildKit path traversal via frontend
- CVE-2026-33748 (High): BuildKit path traversal via git URL

Replace apk-based docker-cli and docker-cli-compose with official static
binaries (Docker CLI v29.3.1 + Compose v2.40.3) pinned via build args.
2026-03-29 23:28:55 -04:00
Anso 59fd528535 fix: remediate Dependabot and Docker Scout security vulnerabilities (#265)
- Upgrade Docker base image from node:20-alpine to node:22-alpine
  (Node 22.22.2, Alpine 3.23.3) to resolve 31 CVEs flagged by Docker Scout
- Add apk upgrade to runtime stage for latest Alpine security patches
- Force dompurify to 3.3.3 via npm overrides to fix two XSS advisories
  (transitive dep of monaco-editor pinned at 3.2.7)
2026-03-29 22:31:19 -04:00
Anso 32a7d53b2b feat: RBAC, atomic deployments, fleet backups, and licensing (Pro) (#185)
* feat: add RBAC viewer accounts, atomic deployments, and fleet-wide backups (Pro)

Introduces three Pro-tier features:

- RBAC: Multi-user system with admin/viewer roles, user management UI,
  automatic migration from single-admin credentials, viewer restrictions
  across the entire UI (read-only editor, hidden action buttons)

- Atomic Deployments: Pre-deploy file backup to .sencho-backup/, automatic
  rollback on health probe failure, manual rollback button, health probes
  added to stack updates, webhook-triggered deploys use atomic rollback

- Fleet-Wide Backups: Point-in-time snapshots of compose files across all
  nodes (local + remote), stored centrally in SQLite, per-stack restore
  with optional redeploy, graceful handling of offline nodes

* fix(settings): use correct ProGate prop name in UsersSection

* fix(settings): remove unused isPro prop from UsersSection

* fix(auth): fetch user info after login and setup so isAdmin is set correctly

* feat(pricing): revise pricing strategy and enforce variant-based seat limits

Raise Personal Pro from $49/yr to $69/yr with 3 viewer seats (up from 1).
Add $15/mo billing option for Team Pro. Mark lifetime pricing as a
90-day early-adopter offer. Store Lemon Squeezy variant_name on
activation/validation and enforce seat limits server-side per variant.

* feat(licensing): add Lemon Squeezy checkout, webhook, and billing portal integration

Server-side checkout URL generation (POST /api/checkout) with admin email
pre-fill and instance_id custom data. HMAC-SHA256 verified webhook endpoint
(POST /api/webhooks/lemonsqueezy) handling order, subscription, and payment
lifecycle events for automatic license activation. Customer billing portal
link stored from webhook events and exposed via GET /api/billing/portal.
In-app checkout buttons in Settings with manual license key fallback.

* fix(licensing): exempt Lemon Squeezy webhook from auth middleware

The catch-all auth middleware on /api/* was blocking the public webhook
endpoint. Added /webhooks/lemonsqueezy to the exemption list alongside
/auth/* and /webhooks/:id/trigger.

* feat(pricing): update pricing to final live rates

Personal Pro: $7.99/month, $69.99/year, $249 lifetime.
Team Pro: $49.99/month, $499.99/year, $1,499 lifetime.
Added personal_monthly checkout variant across backend, frontend, and website.

* refactor(licensing): remove server-side checkout/webhook for self-hosted model

Sencho is self-hosted — each user runs their own instance, so there is
no central server to receive webhooks or hold the store API key. Replaced
in-app checkout buttons with a "View Pricing" redirect to sencho.io and
kept manual license key activation as the primary flow.

- Delete LemonSqueezyService (checkout, webhook, HMAC verification)
- Remove POST /api/checkout, GET /api/billing/portal, POST /api/webhooks/lemonsqueezy
- Remove raw body parser and auth exemption for webhook route
- Remove all LEMONSQUEEZY_* env vars from .env.example
- Replace checkout buttons in SettingsModal with single "View Pricing" button
- Simplify LicenseContext checkout to open sencho.io pricing page
- Update licensing docs to reflect website-based purchase flow

* chore: normalize em-dashes to hyphens across codebase (linter)

* chore: remove accidentally tracked directories from index
2026-03-26 21:58:24 -04:00
Anso 4f26f22cce feat: add Community/Pro licensing, fleet view, and UI reorganization (#145)
* feat: add license gating system with Lemon Squeezy integration

Add Community/Pro tier infrastructure:
- LicenseService singleton with Lemon Squeezy license API integration
- /api/license endpoints (GET info, POST activate/deactivate/validate)
- 14-day Pro trial activated automatically on first boot
- 72-hour periodic validation with 30-day offline grace period
- LicenseContext provider for frontend tier awareness
- License settings tab with activation UI and status display
- ProBadge and ProGate reusable components for feature gating
- requirePro per-route guard for backend Pro-only endpoints
- Proxy bypass for /api/license routes (local-only, never proxied)

* feat: add user profile dropdown and reorganize top navigation

- Create UserProfileDropdown component with settings, billing, theme
  toggle (System/Light/Dark), documentation links, and logout button
- Remove logout button from sidebar header
- Remove standalone settings button from top bar
- Move theme toggle from Settings modal to profile dropdown
- Inject app version via Vite define from root package.json
- Add globals.d.ts for __APP_VERSION__ type declaration

* refactor(settings): remove appearance tab from settings modal

Theme toggle was moved to the User Profile Dropdown in the previous
commit. Remove the now-redundant Appearance section, its nav button,
and the unused theme/setTheme props from SettingsModal.

* feat: add fleet view dashboard and about settings section

Fleet Overview: aggregates all nodes into a card grid showing status,
container counts, CPU/RAM/disk usage bars. Pro tier unlocks stack
drill-down with auto-refresh (30s). Backend endpoints /api/fleet/overview
and /api/fleet/node/:nodeId/stacks query nodes in parallel.

About section in Settings: displays version, license tier, status,
instance ID, and links to docs/changelog/issues.

Sidebar perf fix: stack status fetches now run in parallel via
Promise.allSettled instead of sequential for-loop, significantly
reducing load time for nodes with many stacks.

Also removes version number from User Profile Dropdown (now in About).

* fix(ci): resolve Docker build and E2E test failures

- Copy root package.json into frontend build stage so vite.config.ts
  can read the app version during Docker multi-stage build.
- Update auth E2E test: logout button moved into User Profile Dropdown.
- Update nodes E2E test: Settings button moved into User Profile Dropdown.
2026-03-25 08:32:07 -04:00
Anso 68a1dbe671 chore(deps): upgrade major dependencies (ESLint 10, recharts 3, TS 6, Vite 8) (#130)
* chore(deps): upgrade ESLint 9 → 10 with plugin compatibility fixes

- eslint: ^9 → ^10.0.0 (backend + frontend)
- @eslint/js: ^9 → ^10.0.0 (backend + frontend)
- eslint-plugin-react-refresh: ^0.4.24 → ^0.5.2 (ESM, config factory API)
- Update frontend eslint.config.js: destructured import for react-refresh,
  call configs.vite() as factory function (0.5 API change)
- Downgrade new ESLint 10 rules (no-useless-assignment, preserve-caught-error)
  to warnings for existing code patterns
- eslint-plugin-react-hooks stays at 7.0.1 (stable) with --legacy-peer-deps
  until a stable release adds ESLint 10 peer support

* chore(deps): upgrade recharts 2.x to 3.8 with chart.tsx type fixes

recharts 3.x moved Tooltip/Legend props to context-based API.
Updated chart.tsx to use explicit prop interfaces with internal
recharts type imports (LegendPayload, TooltipPayload, TooltipFormatter).

* chore(deps): upgrade TypeScript 5.9 to 6.0

- Remove deprecated baseUrl from frontend tsconfig (paths works standalone in TS 6)
- Add react-is dependency required by recharts 3.x at build time
- Backend and frontend both compile and lint cleanly

* chore(deps): upgrade Vite 7.3 to 8.0 and @vitejs/plugin-react to 6.0

Vite 8 replaces Rollup+esbuild with Rolldown, significantly improving
build speed (~2s vs ~18s). No config changes required.

* fix(ci): add .npmrc with legacy-peer-deps for CI and Docker builds

typescript-eslint@8.x requires typescript <6.0.0 and
eslint-plugin-react-hooks@7.0.1 requires eslint <=9. Until upstream
packages release compatible versions, legacy-peer-deps is needed.

* docs: add logo assets and re-ignore CLAUDE.md

* fix(ci): copy .npmrc into prod-deps Docker stage

The prod-deps stage also runs npm ci with backend/package.json but was
missing the .npmrc needed to bypass peer dep conflicts.
2026-03-25 00:03:08 -04:00
Anso 381701ee25 fix(docker): fix xx cross-compilation sysroot for native modules (#80)
* fix(docker): fix xx cross-compilation sysroot for node-pty and C++ modules

Three issues in the prod-deps cross-compilation stage:

1. xx-apk had `gcc` (C only) instead of `g++` — all three native modules
   (bcrypt, better-sqlite3, node-pty) use C++, so libstdc++ headers must
   be present in the target sysroot for xx-clang++ to link against them.

2. Missing `linux-headers` in the target sysroot — node-pty requires
   <pty.h> and <termios.h> which live in the Linux kernel headers package.

3. Missing AR=xx-ar — node-gyp uses `ar` to create static archives during
   the native build; without this override it falls back to the host ar
   (amd64), producing wrong-arch .a files that fail at the link step.

* fix(docker): use native g++ for same-platform builds, xx-clang only for cross

xx-clang introduces sysroot flags that break node-gyp header resolution on
Alpine when TARGETARCH == BUILDARCH (e.g. the docker-validate CI job which
builds amd64-only). The native path now uses the standard g++ toolchain
(identical to what worked before the xx refactor). The cross-compilation
path (amd64 → arm64) continues to use xx-clang / xx-clang++ / xx-ar with
the target sysroot populated via xx-apk.
2026-03-24 09:32:58 -04:00
Anso 1e0014e183 fix(docker): replace QEMU npm execution with tonistiigi/xx cross-compilation (#78)
Node.js v20 uses ARMv8.1 LSE atomic instructions that the GitHub Actions
QEMU version does not support, causing SIGILL / "Illegal instruction" core
dumps when npm ci runs inside the arm64 emulated layer.

Solution: add a dedicated prod-deps build stage that runs on $BUILDPLATFORM
(amd64, native) and uses tonistiigi/xx + xx-clang to cross-compile native
modules (bcrypt, better-sqlite3, node-pty) for the TARGET architecture
without ever executing Node.js under QEMU. The final runtime stage simply
copies the pre-compiled node_modules from prod-deps.

Stage breakdown:
  xx            - cross-compilation helper (provides xx-clang, xx-apk)
  frontend-builder  - $BUILDPLATFORM, vite build (no native modules)
  backend-builder   - $BUILDPLATFORM, tsc compilation
  prod-deps         - $BUILDPLATFORM + xx-clang cross-compiles for $TARGET
  final             - TARGET platform, no compilation at all
2026-03-24 08:29:52 -04:00
Anso 278f7f18d9 fix(ci): add linux/arm64 Docker build support with QEMU optimization (#76)
- Add docker/setup-qemu-action@v3 to docker-publish.yml (was missing,
  causing multi-platform builds to hang indefinitely)
- Add platforms: linux/amd64,linux/arm64 to build-push-action step
- Optimize Dockerfile with --platform=$BUILDPLATFORM on builder stages
  so TypeScript compilation runs at native amd64 speed; only the lean
  npm ci --omit=dev step runs under QEMU in the final stage, compiling
  the three native modules (bcrypt, better-sqlite3, node-pty) for the
  correct target architecture — reduces arm64 build time from 6+ hours
  to ~15-30 minutes
2026-03-24 07:50:33 -04:00
SaelixCode 987fc3d339 fix(docker): repair broken entrypoint from bad merge conflict resolution
The merge of PR58 into PR59's branch produced a corrupt docker-entrypoint.sh
(97 lines) where PR59's if block was cut off mid-way (missing exec su-exec
and closing fi) and then PR58's entire if block was appended. This caused:

  /usr/local/bin/docker-entrypoint.sh: line 98: syntax error: unexpected
  end of file (expecting "fi")

Fix: overwrite with the clean 71-line version and add a defensive
`sed -i 's/\r//'` step in the Dockerfile to strip Windows CRLF line
endings at build time, preventing this class of error even if CRLF
slips past .gitattributes in future.
2026-03-22 16:08:50 -04:00
SaelixCode 593a709197 fix(docker): add entrypoint for volume permission handling
Add docker-entrypoint.sh that runs as root at startup, fixes ownership
of the DATA_DIR volume (only files with wrong user or group), then drops
to the non-root sencho user via su-exec before starting Node.

This eliminates the SQLITE_READONLY crash that occurs when a host-mounted
data volume was created by root or chowned to the wrong UID. The pattern
mirrors the official PostgreSQL, Redis, and MariaDB Docker images.

- Install su-exec in Stage 3 (10KB Alpine tool, idiomatic alternative to gosu)
- Remove USER directive; entrypoint handles the privilege drop instead
- Use ENTRYPOINT + CMD so Node becomes PID 1 (correct SIGTERM handling)
- Add .gitattributes to enforce LF line endings for *.sh files
2026-03-22 15:16:34 -04:00
SaelixCode ce50db0fde security: pre-release hardening, automated testing, and production readiness
SECURITY (critical fixes):
- Add authMiddleware to /api/system/console-token (was publicly accessible)
- Validate api_url on node create/update to prevent SSRF (rejects localhost/loopback)
- Add rate limiting (5 req/15 min/IP) to /api/auth/login and /api/auth/setup
- Fix path traversal in env_file resolution — absolute/escaping paths rejected
- Add stack name validation to GET routes (was only on PUT/POST)
- Add helmet security headers middleware
- Restrict CORS to FRONTEND_URL in production

PRODUCTION READINESS:
- Add GET /api/health public endpoint + HEALTHCHECK in Dockerfile
- Add SIGTERM/SIGINT graceful shutdown handler (drains connections, closes DB)
- Run container as non-root sencho user in Dockerfile

QUALITY:
- Fix 4 silent empty catch{} blocks in EditorLayout (now show toast.error)
- Connect ErrorBoundary to root App in main.tsx
- Replace WebSocket.Server with named WebSocketServer import (ESM compat)

TESTING (new automated test suite):
- Install Vitest; 38 backend tests across 4 suites covering validation utilities,
  health endpoint, auth middleware, login flows, SSRF protection, and path traversal
- Extract isValidStackName/isValidRemoteUrl/isPathWithinBase to utils/validation.ts
- Playwright E2E scaffolding: auth, stacks, nodes specs + shared login helper
- CI: run Vitest + ESLint on every PR
2026-03-21 21:59:44 -04:00
SaelixCode 0032c6757a fix: improve npm install reliability with retry configurations in Dockerfile 2026-03-03 09:20:06 -05:00
SaelixCode fcf0c9f983 feat: add HostConsole component for interactive terminal access and integrate into EditorLayout 2026-02-26 19:52:43 -05:00
unknown 293f9cef26 Initial commit: Sencho V1 complete with Auth and Dockerization 2026-02-20 18:39:32 -05:00