v2.3.0
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.