This release includes 1 security fix for security teams reviewing exposed deployments.
Topics
+10 more
Affected surfaces
ReleasePort's take
Moderate signalPlugin SDK audits now mandate apiVersion: "2" and a structured checkCommand. MCP server_fix input no longer accepts dryRun; use mode: 'dry-run' | 'live'.
Why it matters: Breaking API changes require updating plugin manifests and client code before the v2.3 release to avoid failures.
Summary
AI summaryBroad release touches BREAKING, v2.3 Reliability Contracts, P141, and Internal.
Changes in this release
| Type | Severity | Summary | CVE |
|---|---|---|---|
| Security | High |
Notify webhook SSRF hardening: private/reserved IPv4/IPv6 targets rejected during DNS lookup; redirects and proxies disabled. Notify webhook SSRF hardening: private/reserved IPv4/IPv6 targets rejected during DNS lookup; redirects and proxies disabled. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Breaking | High |
Plugin SDK audit checks now require apiVersion: "2" and object-shaped checkCommand. Plugin SDK audit checks now require apiVersion: "2" and object-shaped checkCommand. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Breaking | High |
`server_fix` MCP input shape changed: `dryRun` removed, replaced by `mode: 'dry-run' | 'live'`. `server_fix` MCP input shape changed: `dryRun` removed, replaced by `mode: 'dry-run' | 'live'`. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Breaking | High |
`server_fix` MCP output shape changed: non-apply actions no longer carry `dryRun` field. `server_fix` MCP output shape changed: non-apply actions no longer carry `dryRun` field. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Breaking | High |
`server_secure firewall-status` MCP output: `rules` now an object array instead of string array. `server_secure firewall-status` MCP output: `rules` now an object array instead of string array. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Feature | Medium |
Added `markCommandFailed()` exit‑code helper centralizing process.exitCode signaling. Added `markCommandFailed()` exit‑code helper centralizing process.exitCode signaling. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Feature | Medium |
Plugin audit checks now run in parallel (max 4 per host) with AbortController‑based aggregate timeout. Plugin audit checks now run in parallel (max 4 per host) with AbortController‑based aggregate timeout. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Dependency | Low |
Dropped `p-limit` dependency; replaced with internal `chunkConcurrent`. Dropped `p-limit` dependency; replaced with internal `chunkConcurrent`. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Bugfix | Medium |
`serverCompare` detail mode now returns flat checks array instead of object. `serverCompare` detail mode now returns flat checks array instead of object. Source: llm_adapter@2026-06-12 Confidence: high |
— |
| Bugfix | Medium |
`ssh-factories.ts` removes setTimeout leak via worker force‑exit. `ssh-factories.ts` removes setTimeout leak via worker force‑exit. Source: llm_adapter@2026-06-12 Confidence: high |
— |
Full changelog
Added
markCommandFailed()exit code helper —src/utils/exitCode.tscentralizesprocess.exitCode = 1signaling; adopted acrosssnapshot,domain,maintain,update,audit,evidence,fix,initcommands (P141)isolatedKastellEnvJest helper —tests/helpers/isolatedKastellEnv.tsprovides per-testKASTELL_DIRisolation with self-registeringafterEachcleanup (P141)- Atomic
*.tmprename helpers —atomicWriteFileSync,fsRetry,secureAppendFileSyncfor Windows-safe persistence withEPERM/EACCESretry + copy+unlink fallback (P140) - MCP
startupDiagnostichelper —src/mcp/startupDiagnostic.tsreports SDK + build identity at MCP boot (P141) fileLockstructured return —assessLockStatereturns owner PID hash +ownerHost="internal"for safe diagnostic surfacing; stale-lock reclaim now re-acquires the lock and runsfn()when removal succeeds (P141)- Plugin checks now run in parallel (cap=3, configurable via
PLUGIN_AUDIT_PARALLELISM) - Aggregate timeout for plugin audit (
PLUGIN_AUDIT_TOTAL_TIMEOUT_MS, default 120s) safeToParallel: falseopt-out in plugin manifest for plugins with intentional mutating checkCommands- File-mtime cache for
getServers()andloadLatestAudit(ip)with network FS guard - MCP SDK round-trip test for serverCompare schema
- Plugin tarball smoke test: MCP boot time measurement + empty dir detection
Changed
- Breaking: Plugin SDK audit checks now require
apiVersion: "2"and object-shapedcheckCommand: { kind, cmd }. Manifest-levelmutatesandsafeToParallelare removed; mutation intent is declared per check. chunkConcurrentreplacesp-limitin fleet.ts (p-limit dependency dropped)serverCompareoutputSchema usesdiscriminatedUnion(dış shape aynı kalır)PluginRegistryEntryis now a discriminated union (loaded/error/disabled)- CI workflow: build artifact shared between jobs (~30s saving per run)
fileLockreclaim now re-acquires the lock and runsfn()when stale removal succeeds — previously only recorded the reclaim error and gave up (P141 Codex gap)- Explicit
process.env[PROVIDER_TOKEN]overrides buffered/keychain tokens — prevents stale desktop keychain entries from breaking MCP/container calls (P141 Codex gap) server_infohealth outputSchema — single-server fields (server,ip,mode,sshReachable,hostKeyMismatch,platformStatus,coolifyUrl,dokployUrl) now part of the schema;results/summaryoptional for per-server response (P141 Codex gap)
Release-time version bumps remain owned by /release minor; package.json and kastell-plugin/.claude-plugin/plugin.json must be bumped together.
Fixed
serverComparedetail mode returns flat checks array (was object — CQS-11 #1)ssh-factories.tssetTimeout leak — worker force-exit (CQS-10 #1)tests/helpers/fsMock.tsfactory prevents Linux CI chmodSync mock omission- Explicit provider token environment variables now override buffered and keychain credentials, preventing stale desktop keychain entries from breaking MCP/container calls.
- Single-server
server_info healthresponses now validate against the registered MCP output schema. - Windows local state writes — Atomic
*.tmprename persistence now retries transientEPERM/EACCESfailures and falls back to copy+unlink safely. Coverage:servers.json, audit history, evidence manifests (MANIFEST.json,SHA256SUMS), audit snapshots, fix history, regression baselines, and metric history. File-lock reclaim and security-log rotation also retry on transientEPERM/EACCES. - Status command error path exits 1 via
markCommandFailed()— partial failures now propagate non-zero exit code matching other reliability contracts (P141) - Audit
--jsonand--cistdout parse-clean — no log pollution; reserved for a single JSON payload (P141) - Windows file-lock diagnostics for persistent
EPERM/EACCES— hint-rich surface, replaces "transient" claim (P141) printDiffregression path unconditionally marksprocess.exitCode = 1(P141/simplify)
Internal
- 175
console.logtriage + sweep (5 categories — 0 actionable) - 13 slow tests audit (P140 input)
createFsMockfactory adopted across 32 test filesMcpServerInternalnamed type (CQS-05)serverCompareeslint-disable orphan cleanup (CQS-10 #2)
BREAKING
server_fixMCP input shape changed:dryRun: booleanremoved, replaced bymode: 'dry-run' | 'live'on theapplyaction branch. CLI users unaffected (--dry-runflag unchanged). MCP consumers must update calls.server_fixMCP output shape changed: non-apply actions (history,rollback,rollback-all,rollback-to) no longer carry adryRunfield. Previously the field held the action name as a string proxy to satisfy a misnamed discriminator; now responses are discriminated byactionanddryRun: booleanappears only onapplyresponses where it semantically belongs.server_secure firewall-statusMCP output:rulesis nowz.array(z.object({port, proto, action, from}))(object array) instead ofz.array(z.string())(string array). SDK probe confirms: MCP SDK stripsstructuredContenton outputSchema mismatch. Hard-cut BREAKING. (F-020)
Fixed
server_secureactionauditadded as canonical name.secure-auditstill accepted (deprecated, removal scheduled for v2.4) (F-011)server_info statussummary.runningcorrectly counts running servers when eitherserverStatus(cloud provider) orplatformStatus(Coolify/Dokploy) is "running". Previously only checkedplatformStatus, missing servers where the cloud reports running but the platform probe fails. (F-024)server_guard statusreturnssuccess: booleanandlogTail: string[](line array). (F-022)server_logs monitorreturns structuredmetrics.{cpu,mem,disk}objects (bytes for total/used, IEC binary) instead of validation-failing strings. CLI output unchanged. (F-019)server_backup backup-listreturnsbackupCountfield (F-021)kastell auditaccepts--framework <cis-level1|cis-level2|pci-dss|hipaa>(parity with MCP) (F-016)- Keychain decrypt warnings now deduplicate into single line with provider list (CQS-07)
- Audit
--thresholdand all early-return paths now correctly setprocess.exitCode = 1via AuditError policy (F-015) kastell add --skip-verifynow respects--mode coolify|bareflag (F-002)kastell lock --dry-runerror message clarified (F-010)kastell fix --dry-runrequires--safewith clear error (F-012)kastell fix --historyshows informative empty state (F-014)- Bash completions synced with command registry (F-025)
Changed
- MCP server tool registration refactored to iterate
ALL_MCP_TOOLS(~340 lines removed) - Plugin audit checks now parallelize (max 4 per host) with AbortController-based aggregate timeout (CQS-06)
- Fixture
makeServerRecordhelper extracted across 10 fixtures (~100 lines saved) isWindows()helper extracts 6 inline platform checks
Added
- Regression test for
server_plugin listreading from the loaded plugin registry (fix landed in v2.2.0 P134c/d; test prevents future drift, F-018) AuditErrorclass for centralized audit error handlingchunkConcurrenthelper for bounded parallel workPluginSeverity/FixTiershared typesSafety Modessection in READMEkastell provisionalias forinit
v2.3 Reliability Contracts
- Immediate MCP durable registration —
server_provisionreturns as soon as the provider creates the server and Kastell durably persists the record.readiness.statusmay bepending; follow withserver_info statusorserver_info health. - Verified CLI failures return non-zero — unsupported and failed CLI operations exit with
1; valid empty results and user cancellation exit with0. Mixed--allfailures exit with1. - Parse-clean audit stdout —
audit --jsonandaudit --cireserve stdout for a single JSON payload. - Actionable Windows lock diagnostics — persistent
EPERM/EACCESfailures on Windows file-lock paths now surface hint-rich diagnostics. - MCP SDK round-trip coverage —
server_manage add/remove/destroyoutputSchemas now have fullnormalizeObjectSchema+safeParseAsyncround-trip tests.
Security
- Notify webhook SSRF hardening (HIGH-001) — Discord/Slack webhook connections now reject private/reserved IPv4 and IPv6 targets during the actual socket DNS lookup, pin connections to validated public answers, and disable redirects and environment proxies.
- Reclassified P142 security follow-ups (reviewed 2026-06-12):
- MEDIUM —
starterCLI template skips the optional extra SSH hardening step. Platform cloud-init still configures firewall rules; bare mode also installs fail2ban and unattended-upgrades. - MEDIUM — SSH
StrictHostKeyChecking=accept-newcarries first-connection TOFU risk; strict host-key handling needs separate policies for interactive SSH and newly provisioned servers. - LOW —
debugLogdoes not detect raw secret-shaped strings, though current core error-object call sites are collapsed to[object]. - LOW / operational — CLI safe mode defaults off for trusted local operation; non-TTY automation can still benefit from safer defaults or warnings.
- MEDIUM —
- Removed from the security backlog — Ubuntu 24.04 provider pinning is a provisioning reliability policy, not a security vulnerability; the previously documented DigitalOcean SSH issue has dedicated cloud-init mitigations.
Breaking Changes
- Plugin SDK audit checks now require `apiVersion: "2"` and object‑shaped `checkCommand: { kind, cmd }`; manifest-level `mutates` and `safeToParallel` are removed.
- `server_fix` MCP input shape changed: `dryRun: boolean` removed, replaced by `mode: 'dry-run' | 'live'` on the `apply` action branch. CLI users unaffected; MCP consumers must update calls.
- `server_fix` MCP output shape changed: non‑apply actions no longer carry a `dryRun` field; it appears only on `apply` responses where semantically relevant.
- `server_secure firewall-status` MCP output: `rules` is now an array of objects (`{port, proto, action, from}`) instead of an array of strings.
Security Fixes
- Notify webhook SSRF hardening (HIGH-001): Discord/Slack webhooks now reject private/reserved IPv4/IPv6 targets, pin to validated public answers, and disable redirects/proxies.
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About kastelldev/kastell
Server security auditing and hardening toolkit. 413 security checks across 29 categories (SSH, Firewall, Docker, TLS, HTTP Headers), CIS/PCI-DSS/HIPAA compliance mapping, 19-step production hardening, fleet management, and forensic evidence collection. Supports Hetzner, DigitalOcean, Vultr, and Linode. 13 MCP tools.
Related context
Related tools
Beta — feedback welcome: [email protected]