mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-09 18:32:52 +00:00
fix(git-sources): harden validation, RBAC, concurrency, and deploy recovery (#603)
* fix(git-sources): harden validation, RBAC, concurrency, and deploy recovery
Tightens the surface area around the Git source feature:
- Enforce HTTPS-only repo URLs server-side (regex was permissive).
- Add stack:read permission check on git-source reads and filter the
list endpoint by callable permission.
- Validate stack names before permission checks on mutation routes so
scoped lookups never see unvalidated input.
- Cap repo_url / branch / compose_path / env_path / token lengths and
require the stack directory to exist before upsert.
- Wrap pull() in the per-stack mutex to eliminate the pull/delete race
that could orphan pending data.
- Block .git/ path components in compose_path / env_path so a
misconfigured clone cannot leak repo metadata.
- Return {applied, deployed, deployError?} on deploy failure instead of
throwing, and surface deployError as a warning toast so the user can
retry deploy without re-pulling.
- Always clean the stack_git_sources row on stack delete even when the
file deletion step fails.
- Add shadow-card-bevel to the pending alert and metadata card per the
design system.
- Handle the new 403 response on the panel fetch gracefully.
- Add diagnostic logging gated on developer_mode (isDebugEnabled) across
fetch / pull / apply / webhook paths with credential scrubbing.
* test(git-sources): expand coverage for hardening and route validation
- New route-level suite covers HTTPS enforcement, required fields,
max-length caps on repo_url / branch / compose_path / env_path /
token, the stack-existence 404 guard, and GET authz.
- Service tests cover the .git metadata guard on compose and env
paths (including nested and substring-containing "git"), pull and
apply rejections when no source is configured or pending is
cleared, the sha-mismatch branch, and the deploy-failure return
shape that now carries deployError.
- E2E adds three server-side contract assertions: PUT against a
missing stack returns 404, http:// is rejected with 400, and
.git/config is rejected as compose_path.
* docs(git-sources): document deploy-failure recovery path
Adds a Troubleshooting entry explaining that when apply succeeds but
the subsequent deploy fails, the compose content is already on disk
and the user can retry deploy from the stack editor without
re-pulling.
* docs(git-sources): add configuration, diff, pending, and webhook screenshots
This commit is contained in:
@@ -112,6 +112,9 @@ export function GitSourcePanel({
|
||||
setAuthType('none');
|
||||
setToken('');
|
||||
setApplyModeOverride(null);
|
||||
} else if (res.status === 403) {
|
||||
setSource(null);
|
||||
toast.error('You do not have permission to view this stack\'s Git source.');
|
||||
} else {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
toast.error(err?.error || 'Failed to load Git source.');
|
||||
@@ -134,7 +137,7 @@ export function GitSourcePanel({
|
||||
toast.error('Repository URL, branch, and compose path are required.');
|
||||
return;
|
||||
}
|
||||
if (!/^https?:\/\//i.test(repoUrl.trim())) {
|
||||
if (!/^https:\/\//i.test(repoUrl.trim())) {
|
||||
toast.error('Only HTTPS repository URLs are supported.');
|
||||
return;
|
||||
}
|
||||
@@ -235,8 +238,12 @@ export function GitSourcePanel({
|
||||
body: JSON.stringify({ commitSha, deploy }),
|
||||
});
|
||||
if (res.ok) {
|
||||
const data: { applied: boolean; deployed: boolean } = await res.json();
|
||||
toast.success(data.deployed ? 'Applied and deployed.' : 'Applied successfully.');
|
||||
const data: { applied: boolean; deployed: boolean; deployError?: string } = await res.json();
|
||||
if (data.deployError) {
|
||||
toast.warning(`Applied, but deploy failed: ${data.deployError}`);
|
||||
} else {
|
||||
toast.success(data.deployed ? 'Applied and deployed.' : 'Applied successfully.');
|
||||
}
|
||||
setDiffOpen(false);
|
||||
setPull(null);
|
||||
await load();
|
||||
@@ -323,7 +330,7 @@ export function GitSourcePanel({
|
||||
) : (
|
||||
<>
|
||||
{source?.pending_commit_sha && (
|
||||
<div className="flex items-start gap-2 rounded-md border border-brand/30 bg-brand/5 px-3 py-2 text-xs">
|
||||
<div className="flex items-start gap-2 rounded-md border border-brand/30 bg-brand/5 px-3 py-2 text-xs shadow-card-bevel">
|
||||
<AlertCircle className="w-4 h-4 shrink-0 mt-0.5 text-brand" strokeWidth={1.5} />
|
||||
<div className="flex-1">
|
||||
<p className="font-medium">Pending update</p>
|
||||
@@ -450,7 +457,7 @@ export function GitSourcePanel({
|
||||
</div>
|
||||
|
||||
{source && (
|
||||
<div className="rounded-md border border-glass-border bg-muted/30 px-3 py-2 text-[11px] text-stat-subtitle space-y-0.5">
|
||||
<div className="rounded-md border border-glass-border bg-muted/30 px-3 py-2 text-[11px] text-stat-subtitle space-y-0.5 shadow-card-bevel">
|
||||
<div className="flex justify-between gap-2">
|
||||
<span>Last applied commit</span>
|
||||
<span className="font-mono tabular-nums">
|
||||
|
||||
Reference in New Issue
Block a user