Skip to content

kastelldev/kastell

v2.3.0 Security

This release includes 1 security fix for security teams reviewing exposed deployments.

✓ No known CVEs patched
Read the diff → Tool health → What is this tool? →
This release patches 1 known CVE

Topics

automation cli coolify devops digitalocean docker
+10 more
dokploy hetzner linode mcp security-audit self-hosted server-management typescript vps vultr

Affected surfaces

auth breaking_upgrade

ReleasePort's take

Moderate signal
editorial:auto 1mo

Plugin 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 summary

Broad release touches BREAKING, v2.3 Reliability Contracts, P141, and Internal.

Changes in this release

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 helpersrc/utils/exitCode.ts centralizes process.exitCode = 1 signaling; adopted across snapshot, domain, maintain, update, audit, evidence, fix, init commands (P141)
  • isolatedKastellEnv Jest helpertests/helpers/isolatedKastellEnv.ts provides per-test KASTELL_DIR isolation with self-registering afterEach cleanup (P141)
  • Atomic *.tmp rename helpersatomicWriteFileSync, fsRetry, secureAppendFileSync for Windows-safe persistence with EPERM/EACCES retry + copy+unlink fallback (P140)
  • MCP startupDiagnostic helpersrc/mcp/startupDiagnostic.ts reports SDK + build identity at MCP boot (P141)
  • fileLock structured returnassessLockState returns owner PID hash + ownerHost="internal" for safe diagnostic surfacing; stale-lock reclaim now re-acquires the lock and runs fn() 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: false opt-out in plugin manifest for plugins with intentional mutating checkCommands
  • File-mtime cache for getServers() and loadLatestAudit(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-shaped checkCommand: { kind, cmd }. Manifest-level mutates and safeToParallel are removed; mutation intent is declared per check.
  • chunkConcurrent replaces p-limit in fleet.ts (p-limit dependency dropped)
  • serverCompare outputSchema uses discriminatedUnion (dış shape aynı kalır)
  • PluginRegistryEntry is now a discriminated union (loaded/error/disabled)
  • CI workflow: build artifact shared between jobs (~30s saving per run)
  • fileLock reclaim now re-acquires the lock and runs fn() 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_info health outputSchema — single-server fields (server, ip, mode, sshReachable, hostKeyMismatch, platformStatus, coolifyUrl, dokployUrl) now part of the schema; results/summary optional 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

  • serverCompare detail mode returns flat checks array (was object — CQS-11 #1)
  • ssh-factories.ts setTimeout leak — worker force-exit (CQS-10 #1)
  • tests/helpers/fsMock.ts factory 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 health responses now validate against the registered MCP output schema.
  • Windows local state writes — Atomic *.tmp rename persistence now retries transient EPERM/EACCES failures 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 transient EPERM/EACCES.
  • Status command error path exits 1 via markCommandFailed() — partial failures now propagate non-zero exit code matching other reliability contracts (P141)
  • Audit --json and --ci stdout 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)
  • printDiff regression path unconditionally marks process.exitCode = 1 (P141 /simplify)

Internal

  • 175 console.log triage + sweep (5 categories — 0 actionable)
  • 13 slow tests audit (P140 input)
  • createFsMock factory adopted across 32 test files
  • McpServerInternal named type (CQS-05)
  • serverCompare eslint-disable orphan cleanup (CQS-10 #2)

BREAKING

  • server_fix MCP input shape changed: dryRun: boolean removed, replaced by mode: 'dry-run' | 'live' on the apply action branch. CLI users unaffected (--dry-run flag unchanged). MCP consumers must update calls.
  • server_fix MCP output shape changed: non-apply actions (history, rollback, rollback-all, rollback-to) no longer carry a dryRun field. Previously the field held the action name as a string proxy to satisfy a misnamed discriminator; now responses are discriminated by action and dryRun: boolean appears only on apply responses where it semantically belongs.
  • server_secure firewall-status MCP output: rules is now z.array(z.object({port, proto, action, from})) (object array) instead of z.array(z.string()) (string array). SDK probe confirms: MCP SDK strips structuredContent on outputSchema mismatch. Hard-cut BREAKING. (F-020)

Fixed

  • server_secure action audit added as canonical name. secure-audit still accepted (deprecated, removal scheduled for v2.4) (F-011)
  • server_info status summary.running correctly counts running servers when either serverStatus (cloud provider) or platformStatus (Coolify/Dokploy) is "running". Previously only checked platformStatus, missing servers where the cloud reports running but the platform probe fails. (F-024)
  • server_guard status returns success: boolean and logTail: string[] (line array). (F-022)
  • server_logs monitor returns structured metrics.{cpu,mem,disk} objects (bytes for total/used, IEC binary) instead of validation-failing strings. CLI output unchanged. (F-019)
  • server_backup backup-list returns backupCount field (F-021)
  • kastell audit accepts --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 --threshold and all early-return paths now correctly set process.exitCode = 1 via AuditError policy (F-015)
  • kastell add --skip-verify now respects --mode coolify|bare flag (F-002)
  • kastell lock --dry-run error message clarified (F-010)
  • kastell fix --dry-run requires --safe with clear error (F-012)
  • kastell fix --history shows 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 makeServerRecord helper extracted across 10 fixtures (~100 lines saved)
  • isWindows() helper extracts 6 inline platform checks

Added

  • Regression test for server_plugin list reading from the loaded plugin registry (fix landed in v2.2.0 P134c/d; test prevents future drift, F-018)
  • AuditError class for centralized audit error handling
  • chunkConcurrent helper for bounded parallel work
  • PluginSeverity / FixTier shared types
  • Safety Modes section in README
  • kastell provision alias for init

v2.3 Reliability Contracts

  • Immediate MCP durable registrationserver_provision returns as soon as the provider creates the server and Kastell durably persists the record. readiness.status may be pending; follow with server_info status or server_info health.
  • Verified CLI failures return non-zero — unsupported and failed CLI operations exit with 1; valid empty results and user cancellation exit with 0. Mixed --all failures exit with 1.
  • Parse-clean audit stdoutaudit --json and audit --ci reserve stdout for a single JSON payload.
  • Actionable Windows lock diagnostics — persistent EPERM/EACCES failures on Windows file-lock paths now surface hint-rich diagnostics.
  • MCP SDK round-trip coverageserver_manage add/remove/destroy outputSchemas now have full normalizeObjectSchema + safeParseAsync round-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):
    • MEDIUMstarter CLI 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-new carries first-connection TOFU risk; strict host-key handling needs separate policies for interactive SSH and newly provisioned servers.
    • LOWdebugLog does 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.
  • 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

Track kastelldev/kastell

Get notified when new releases ship.

Sign up free

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.

All releases →

Related context

Beta — feedback welcome: [email protected]