fix(reconcile): bring main up to hardened production (ed47680) — end the security regression #5

Merged
kavi merged 4 commits from fix/1-reconcile-prod-to-main into main 2026-07-12 07:20:37 +00:00
Owner

Closes #1. Crown-jewel — reviewed PR only; human merges. Protect main after merge (branch protection).

What

Reconciles the diverged, security-regressed main up to the deployed/hardened production (ed47680).

main was missing production`s 3 hardening commits and carried only 2 stale tests:

  • prod-only (main lacked): ed47680 engine-owned registry + mutation API · d75a145 auth-bypass/branch-injection/silent-failure/hardcoded-IP fixes + ensure-checkout · e33b1e9 ensure-checkout + rollback to_ref
  • main-only (dropped): registry-loader.test.js + sha-comparison.test.js — both assert the removed bind-mounted /app/deploy-registry.json; 6a583a8s functionality is already represented in production.

How

  1. merge origin/production into main
  2. resolve server.js + docker-compose.yml to the production versions
  3. drop the 2 stale tests

Verification (mechanical)

git rev-parse HEAD^{tree} == origin/production^{tree} == 6314fc6e — mains application tree is **byte-identical to ed47680`**. Zero functional delta from the deployed ref; this only removes the regression.

Follow-ups

  • Proper unit tests for the current engine-owned registry (registry loader + SHA comparison, importing prod logic, not the removed bind-mount) — separate PR so this one stays trivially verifiable.
  • Enable branch protection on main (audit plan step 5).
  • kua-deploy #4 (ff-only release fix) is currently based on production; once this merges, main==production and #4 converges/rebases onto main.
Closes #1. **Crown-jewel — reviewed PR only; human merges. Protect `main` after merge (branch protection).** ## What Reconciles the diverged, security-**regressed** `main` up to the deployed/hardened `production` (`ed47680`). `main` was missing production`s 3 hardening commits and carried only 2 **stale** tests: - prod-only (main lacked): `ed47680` engine-owned registry + mutation API · `d75a145` auth-bypass/branch-injection/silent-failure/hardcoded-IP fixes + ensure-checkout · `e33b1e9` ensure-checkout + rollback to_ref - main-only (dropped): `registry-loader.test.js` + `sha-comparison.test.js` — both assert the **removed** bind-mounted `/app/deploy-registry.json`; `6a583a8`s functionality is already represented in production. ## How 1. merge `origin/production` into `main` 2. resolve `server.js` + `docker-compose.yml` to the **production** versions 3. drop the 2 stale tests ## Verification (mechanical) `git rev-parse HEAD^{tree}` == `origin/production^{tree}` == **`6314fc6e`** — main`s application tree is **byte-identical to `ed47680`**. Zero functional delta from the deployed ref; this only *removes* the regression. ## Follow-ups - Proper unit tests for the current engine-owned registry (registry loader + SHA comparison, importing prod logic, not the removed bind-mount) — separate PR so this one stays trivially verifiable. - Enable branch protection on `main` (audit plan step 5). - kua-deploy #4 (ff-only release fix) is currently based on `production`; once this merges, main==production and #4 converges/rebases onto main.
- ensureCheckout(server, deployDir, repoUrl): clone-if-missing, inside the
  per-app acquireLock, called before deploy() git_pull and before rollback()
  cd. No-op when .git present (asserts origin==repo_url if set); requires
  registry repo_url when absent; refuses to clobber a non-empty non-repo dir.
- rollback(appName, opts): opts.to_ref (validated /^[A-Za-z0-9._/-]+$/,
  rejected before any mutation) checks out that ref; default = previous
  successful tag from deployHistory. fetch now --prune --tags.
- route POST /api/v1/apps/:app/rollback reads body.to_ref.
Security (4 issues):
1. Remove the 172.* Docker-bridge auth bypass — any bridge container inherited
   tag:admin (incl /unlock, /progress/reset). Bridge callers now need Tailscale
   identity or bearer token. (kua-mcp-core unaffected — reaches engine via
   docker exec localhost.)
2. Validate request-supplied source_branch/target_branch on /release (400 on bad
   input) before they reach the shell in release().
3. Check .ok on previously-ignored runOnServer results: post_deploy hook
   (→partial), no-health-url docker compose ps (→unhealthy); add a catch to
   rollback() so a failed rollback records failure instead of hanging 'running'.
4. Replace hardcoded bruno/gal Tailscale IP map with runtime resolution via the
   tailscaled LocalAPI over the mounted socket (cached per host).

Regression fix (ensure-checkout):
- ensureCheckout now probes/clones the GIT ROOT (registry repo_dir), not deploy_dir.
  They differ for sub-monorepo apps (coder-core: repo_dir=/root/apps/coder-core,
  deploy_dir=.../services/production). Probing deploy_dir/.git falsely reported
  MISSING and broke coder-core deploys (e33b1e9 regression). 18 normal apps where
  repo_dir==deploy_dir are unchanged.
The registry was a git-committed file bind-mounted :ro from coder-core AND hand-patched
live — two writers, neither authoritative. Root cause of the reconcile commits, dirty-tree
deploy blocks, main-push gating, and multi-session collisions (codex/gpt-5.5 audited).

- Registry is now ENGINE-OWNED at /app/data/registry.json (persistent volume), seeded once
  from the legacy bind-mount on first boot, never overwritten thereafter.
- New authenticated mutation API: PUT/PATCH/DELETE /api/v1/apps/:app (+ ?dry_run=1 returns
  a normalized diff, no write), GET /api/v1/registry/export (+ timestamped snapshot).
- Atomic writeRegistry (temp+rename), in-process mutation lock, schema validateEntry
  (allowed servers, branch-name safety, required fields).
- Append-only hash-chained audit at /app/data/registry-events.jsonl (actor from request
  identity, before/after, prev_hash/event_hash).
- FIX: audit.log moved /app/logs -> /app/data (the /app/logs path was never mounted, so the
  audit trail was lost on every restart).
- compose: removed the deploy-registry.json git bind-mount.

Clients (kua-app, kua-mcp-core) migrate to the API in a follow-up; git becomes an export
sink, not the source of truth.
main had DIVERGED from the deployed production ref and was security-REGRESSED:
it lacked production's 3 hardening commits (engine-owned registry + mutation API,
auth-bypass/branch-injection/silent-failure/hardcoded-IP fixes, ensure-checkout),
while adding only 2 stale unit tests that assert the REMOVED bind-mounted
/app/deploy-registry.json.

Reconcile per the audit plan:
- merge origin/production into main
- resolve server.js + docker-compose.yml to the PRODUCTION versions
- drop the 2 stale tests (registry-loader.test.js, sha-comparison.test.js)

Result: main's application tree is byte-identical to production ed47680
(verified: git write-tree == origin/production^{tree} == 6314fc6e). Proper
tests for the current engine-owned registry are a tracked follow-up.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author
Owner

Adversarial review (Fable) — verdict SAFE-TO-MERGE.

  • Tree byte-identical to ed47680 (verified); true merge commit, so dropped tests stay recoverable in history.
  • No real functionality discarded: every part of main-only 6a583a8 is independently present in production:server.js (kvPrefix wrap ~852, stripSha ~863, GET /api/v1/apps/registry ~1454, WEBHOOK_SECRET fatal→warn ~1897). d550824 is only the 2 tests.
  • docker-compose: taking production is correct (main kept the removed deploy-registry.json:ro bind-mount, contradicting decision 0003).
  • Dropped tests: registry-loader.test.js genuinely stale (asserts the removed root bind-mount); sha-comparison.test.js not stale but tests a duplicated copy of stripSha — OK to drop paired with the replacement below. Neither was wired (no test script / CI), so no enforced coverage lost.

Merge either order vs #4; only obligation is the ff-only forward-port noted on #4. Enable branch protection on main after merge.

Adversarial review (Fable) — verdict **SAFE-TO-MERGE**. - Tree byte-identical to `ed47680` (verified); true merge commit, so dropped tests stay recoverable in history. - **No real functionality discarded**: every part of main-only `6a583a8` is independently present in `production:server.js` (kvPrefix wrap ~852, stripSha ~863, `GET /api/v1/apps/registry` ~1454, WEBHOOK_SECRET fatal→warn ~1897). `d550824` is only the 2 tests. - docker-compose: taking production is correct (main kept the removed `deploy-registry.json:ro` bind-mount, contradicting decision 0003). - Dropped tests: `registry-loader.test.js` genuinely stale (asserts the removed root bind-mount); `sha-comparison.test.js` not stale but tests a *duplicated* copy of `stripSha` — OK to drop **paired with** the replacement below. Neither was wired (no `test` script / CI), so no enforced coverage lost. Merge either order vs #4; only obligation is the ff-only forward-port noted on #4. Enable branch protection on `main` after merge.
kavi merged commit bc21393c5d into main 2026-07-12 07:20:37 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
kavi/kua-deploy!5
No description provided.