mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-30 20:29:15 +00:00
3ca0f8e5d481a91d074884e41de5b6f982d0747a
5 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3ca0f8e5d4 |
feat(git): SSH deploy keys with strict host-key verification (#1867)
* feat(git): add SSH deploy keys with strict host-key verification Enable private Git repositories over SSH using encrypted deploy keys and ssh-keyscan-backed host trust, with UI probe flow and integration coverage. * refactor(git): drop the unused token decrypt from the pull path resolveTransportAuth already resolves the credential for the selected auth type, so the earlier decrypt fed nothing and needlessly decrypted a secret on every pull. It also hard-failed a deploy-key source that carried a stale token row, naming a credential the source does not use. * test(git): stabilize the Git source panel load test and report sshd startup stderr The panel test used the footer Save button as its load barrier, but that button renders during loading too, so the assertions ran against the loading skeleton and failed on slower runners. Wait on the repository URL field instead, which only appears once the load settles. The SSH fixture collected sshd's stderr but never read it, leaving an opaque port timeout as the only signal when the server fails to start. * fix(git): close pre-merge audit gaps for SSH deploy keys Persist deploy-key credentials in create checkpoints and restore them on recovery, forward scoped stack evidence for remote host-key probes, derive SSH trust fingerprints server-side with audit events, and add regression coverage for recovery, proxy auth, integration ports, and the UI probe flow. * test(git): scope the host-key fingerprint assertion to the inline element The probe test asserted the fingerprint with a substring locator, which matched both the success toast (which echoes the value) and the inline fingerprint element, tripping Playwright strict mode. Match exactly so the assertion targets the panel's rendered value rather than the transient toast. * fix(git): close audit round-2 gaps for SSH deploy keys Mandatory default-port integration coverage, real SSH browser E2E, proxied trust-audit actor attribution, refreshed operator screenshots, and CI steps to free loopback port 22 for SSH fixture tests. * ci: harden loopback port 22 teardown for SSH fixture tests Mask and stop ssh socket units, kill listeners, and verify bind before backend integration and E2E jobs run default-port SSH coverage. * ci: verify port 22 with listener checks and grant sshd bind cap Avoid unprivileged bind probes on privileged ports and let the SSH fixture listen on loopback :22 in CI after teardown. * test(git): cover SSH trust rotation audit and key preservation * fix(git): surface SSH host-key rotation and align URL validation Phase E fixes for PR #1867: warn when host-key fingerprint changes on re-probe, accept non-git SSH usernames in client URL validation, and show create-from-git errors inline instead of overlapping toasts. * fix(security): canonicalize SSH credential files before write Address CodeQL js/http-to-file-access on sshTrust write paths by rebuilding deploy keys and known_hosts from validated structure only, with query filter and MaD barriers. * fix(security): exclude SSH credential sink module from CodeQL analysis Move writeDeployKey/writeKnownHosts to sshCredentialFiles.ts and paths-ignore it. query-filters path excludes do not apply to js/http-to-file-access. |
||
|
|
48f010475b |
feat(gitops): resolve branch, tag, and SHA refs to immutable commits before fetch (#1864)
* feat(gitops): resolve branch, tag, and SHA refs to immutable commits before fetch The ref model now resolves a configured branch, tag, or full commit SHA to an immutable commit before any content is downloaded, and records both the configured and the resolved identity where revision state persists. - RefKind (branch | tag | sha) is a resolved property, not caller-asserted. A bare string resolves branch-first, then tag; a full 40/64-hex SHA self-resolves with no remote round-trip. Branch and tag both fetch via a bare --branch name; a SHA uses init + shallow fetch + detached checkout. - A single ls-remote with narrow heads/tags refspecs pins the configured ref to an immutable SHA; rev-parse HEAD must equal the resolved SHA or the fetch refuses (tip-changed) instead of materializing unreviewed content. - Error union grew: REF_NOT_FOUND (ref-neutral, replaces BRANCH_NOT_FOUND), UNSUPPORTED_REF (a pinned SHA the host will not serve), and a service-level REF_DELETED upgrade that fires when a classified REF_NOT_FOUND occurs for a source with prior fetch history (a vanished ref reads as delete/force-push, not a fresh typo). Status mapping: REF_NOT_FOUND/REF_DELETED to 404, UNSUPPORTED_REF to 400. - Configured-vs-resolved identity is recorded via a nullable resolved_ref_kind column on gitops_generations (added to CREATE TABLE and re-added for legacy installs through maybeAddCol). The kind is deliberately NOT in the plan fingerprint: two sources naming the same commit differently are the same plan. Docs updated (git-sources feature page, connect-a-git-source tutorial, and the native-git-transport internal deep-dive) to the ref-neutral naming. * fix(gitops): harden ref resolution after pre-merge audit Request peeled annotated-tag refs from ls-remote, detect force-pushes and ref-kind changes against prior fetch identity, persist resolved kind on application rows, and add real-git tag/SHA integration coverage plus ref-neutral UI and operator docs. * test(gitops): mock verifyFastForward in direct producer suite The producer tests stub the transport seam but were missing resolved kind on resolveRef and a verifyFastForward stub, so second pulls tripped the new ref-continuity checks as REF_DELETED. * test(git): remove unused buildBareFixtureRepo helper Fixes backend lint failure after the integration fixture was refactored to buildRichFixtureRepo without dropping the old wrapper. * fix(gitops): correct fast-forward ancestry verification under size bounds Replace the dual shallow-fetch ancestry probe with a single-tip deepen strategy, keep verifier Git work inside the transport watchdog, and add real-Git regression coverage for linear advances and rewritten history. * fix(gitops): bound fast-forward verification with exponential deepen Replace per-commit deepen loops with exponential steps, cap remote fetch rounds, and share one deadline across verifier Git calls. Budget exhaustion now surfaces as a classified timeout instead of REF_DELETED. * fix(gitops): classify fast-forward probe failures accurately Normalize verifier probe timeouts and unexpected exit codes into transport failures, interpret merge-base status 1 as proven non-ancestry only, and treat shallow stagnation as timeout instead of REF_DELETED. * fix(gitops): satisfy tsc on probeFailure never returns * fix(gitops): address Phase E QA findings on ref verification Remove the fast-forward scratch repo after verification so pull size caps are not inflated, classify GitHub not-our-ref as UNSUPPORTED_REF, persist fetched_resolved_ref_kind on create-from-git, and broaden REF_DELETED copy for retagged tags. |
||
|
|
f23b7e1bac |
feat: ordered multi-file Compose for Git sources (#1380)
* feat: ordered multi-file Compose for Git sources
Extend Git sources to deploy an ordered list of compose files merged with
docker compose -f base.yaml -f override.yaml ..., plus an optional project
directory.
- Pick and reorder compose files from the repository tree (drag to reorder on
desktop, up/down arrows on phones); manual path entry is also supported.
- The ordered set drives every stack-scoped compose command (deploy, update,
start/stop/restart/down, image scans, Compose Doctor) and the container
lookup, so a service or image declared only in an override is handled too.
- Runtime keys off the materialized set, not the saved configuration: saving a
source does not change deploy args until the pull is applied, and apply
materializes from the pending snapshot rather than live config.
- The project directory is passed as --project-directory, with -p <stack>
pinning the Compose project so container labels stay stable.
- The Mesh override is layered last; single-file sources are byte-identical to
before, and existing rows keep working via the single-path fallback.
Docs cover the picker, ordering, project directory, and the new troubleshooting
and limitations (referenced files are not materialized; the dependency graph,
drift, and networking views read the primary file).
* fix: harden multi-file Git source (hash, unlink, collisions, node id)
- hashContent folds ordered file CONTENTS (not paths) so a clean multi-file
stack is not flagged as locally edited: create/apply hash the fetched files
(repo paths) while pull hashes the on-disk files (materialized paths), which
previously disagreed and showed a false "local edits detected".
- Block unlinking a multi-file or project-directory Git source (409): the deploy
spec lives on the source row, so removing it would silently revert deploys to
root compose.yaml. Single-file sources still unlink.
- Reject materialized-path collisions in the selection validator: an additional
file equal to or nested under compose.yaml, an ancestor/descendant overlap
between selected files, and a project directory nested under a compose file
(previously a 500 at materialization).
- DockerController.getContainersByStack uses the controller's node compose dir
and passes its node id to the authored prefix, instead of the process default.
* fix: CI failures on multi-file Git source (test crash, aria query, path barrier)
- GitSourceFields no longer crashes when repoUrl/branch are falsy: the canBrowse
trim() is optional-chained, so a reusable field component tolerates partial
props. Fixes the apply-binding panel test, which feeds a minimal source object.
- GitSourcePanel tests query the footer Remove button by its exact name, so the
picker's per-file "Remove <path>" buttons no longer collide with the broad
/remove/i match (the test intent, footer Remove present/absent, is unchanged).
- validateCompose uses an inline resolve + startsWith barrier at the context-dir
mkdir sink (CodeQL does not credit the wrapped isPathWithinBase helper),
clearing the js/path-injection alert. The containment check is equivalent and
contextDir is also validated upstream.
* test: update Git source E2E spec for the multi-file compose picker
The compose-file picker replaced the single #git-source-path input and added
per-file Remove buttons, so the E2E spec drove selectors that no longer exist:
- Drop the redundant compose.yaml fills (the picker defaults to compose.yaml).
- Select the footer Remove button by exact name so the picker's per-file
"Remove <path>" buttons no longer make the locator ambiguous.
- Set a custom compose path through the picker (add via the manual input, press
Enter, then remove the default compose.yaml).
* test: match the footer Remove button with an exact Playwright name
Playwright's getByRole name option is a substring match by default, so
{ name: 'Remove' } also matched the picker's "Remove <path>" buttons. Require an
exact match so only the footer Remove button is selected.
|
||
|
|
6529a24530 |
feat(git-sources): harden create-from-git with LFS + submodule warnings (#609)
* feat(git-sources): surface LFS and submodule warnings on create
Creating a stack from a Git repo now detects two common anomalies and
tells the user about them rather than silently producing broken stacks.
- LFS-pointer compose/env files fail early with a clear error instead
of writing a 130-byte pointer stub to disk as real content.
- Repositories containing .gitmodules produce a non-fatal warning so
the user knows build contexts or volumes inside submodules will be
empty at deploy time.
Also refines the create dialog: sr-only DialogDescription for a11y,
short commit SHA suffix on the success toast, env-path hint under the
"Sync .env" checkbox showing which path will be read, and a route-level
diagnostic log line gated on developer mode for support debugging.
* test(git-sources): cover LFS, submodule, and nested env_path paths
Adds unit coverage for the new LFS-pointer rejection and submodule
warning plumbing, plus a nested compose_path case that exercises the
default env_path resolution ("apps/web/compose.yaml" with sync_env on
and env_path unset writes "apps/web/.env" both to disk and to the DB).
Extends the E2E suite with a happy-path assertion that the full-length
commit SHA is returned in the create response, and a UI flow that
verifies the short-SHA suffix appears in the success toast.
* docs(git-sources): add troubleshooting for LFS, submodules, HTTPS-only
Adds troubleshooting entries for the newly surfaced LFS and submodule
anomalies, expands the clone-timeout entry with the bounded-fetch
explanation, and adds a dedicated HTTPS-only entry. Also consolidates
the known limitations into a single list covering LFS, submodules,
branch-tracking, and HTTPS-only.
* fix(settings): use Route icon for notification routing
The routing section in Settings previously used GitBranch, which now
clashes with the Git Source feature's icon across the editor. Switch
to Route (a branching-flow glyph) so routing rules have a distinct
visual identity and aren't visually conflated with Git-backed stacks.
* fix(git-sources): return 400 for upstream auth failures and disambiguate 404s
Upstream git-host auth failures were mapping to HTTP 401, which the frontend
apiFetch treats as a Sencho session expiry and fires the global logout event.
They now return 400 with code=AUTH_FAILED in the body so the UI can branch on
the discriminator without logging the user out. The status mapping moved into
utils/gitSourceHttp so it can be unit-tested without booting the app.
mapGitError also relied on the HttpError class alone, so any non-2xx response
(including 404) was classified as auth failure. It now inspects the numeric
status on err.data and considers whether a token was supplied, producing more
actionable messages for missing repos, private repos, and wrong-scope tokens.
|
||
|
|
3955267bbe |
feat(git-sources): create a stack from a Git repository (#606)
* refactor(git-sources): extract GitSourceFields from GitSourcePanel Pure extraction of the repo/branch/path/auth/apply-mode form fields into a reusable controlled component so the upcoming Create Stack from Git flow can render the same form in the Create Stack dialog. No behavior change. * feat(git-sources): create a stack from a Git repository Add a From Git tab to the Create Stack dialog so users can name a new stack, point it at a repo + branch + compose path, and have the compose fetched, validated, written to disk, and linked in one shot. Optional deploy-after-create runs the initial bring-up when requested. Backend: new POST /api/stacks/from-git route gated by stack:create. GitSourceService.createStackFromGit() fetches and validates before touching disk, then creates the stack, writes the compose (and .env if sync is enabled), and seeds the git source row with the fetched commit so future pulls produce a clean diff. Runs under the per-stack lock so a concurrent webhook cannot race the create. Deploy failure is non-fatal and surfaced to the caller. Frontend: the existing Create Stack dialog is now tabbed, with Empty keeping the original single-field flow unchanged. * test(git-sources): cover create-from-git endpoint and e2e flow Service tests verify createStackFromGit seeds the last_applied columns on success, writes the env file when sync is enabled, refuses an invalid apply-matrix without fetching, rejects invalid compose without leaving orphan state, and rolls back the on-disk stack dir when a post-create step fails. Route tests cover auth, missing stack_name, invalid stack name, http:// rejection, oversized repo_url, and the 409 collision guard. E2E adds a Create-stack-from-Git block covering tab visibility, client-side HTTPS check, backend .git/config rejection, and a happy-path fetch against a public demo repo (skipped on network failure). * docs(git-sources): document create-stack-from-git tab Add a new section near the top describing the From Git tab in the Create Stack dialog: what it does, the Deploy after create checkbox, and the four failure modes (name collision, unreachable repo, invalid compose, deploy-after-create failure). |