Skip to content

feat(kiloclaw): add generic patchSecrets endpoint with catalog-driven validation#947

Merged
St0rmz1 merged 9 commits intomainfrom
feat/secret-catalog-trpc
Mar 9, 2026
Merged

feat(kiloclaw): add generic patchSecrets endpoint with catalog-driven validation#947
St0rmz1 merged 9 commits intomainfrom
feat/secret-catalog-trpc

Conversation

@St0rmz1
Copy link
Copy Markdown
Contributor

@St0rmz1 St0rmz1 commented Mar 9, 2026

Summary

feat(kiloclaw): add generic patchSecrets endpoint with catalog-driven validation

Adds patchSecrets tRPC mutation and PATCH /api/platform/secrets worker endpoint
alongside existing patchChannels. Uses secret catalog for key validation,
pattern matching, maxLength enforcement, and allFieldsRequired enforcement.

Makes updateChannels delegate to updateSecrets internally so both storage fields
(channels + encryptedSecrets) stay in sync regardless of which endpoint is
called. Eliminates interleave drift risk.

Key design decisions:

  • Key remapping at storage boundary: encryptedSecrets stores env var names
    (e.g., TELEGRAM_BOT_TOKEN) so the existing buildEnvVars/mergeEnvVarsWithSecrets
    pipeline works unchanged. A reverse map (ENV_VAR_TO_FIELD_KEY) converts back to
    field keys when reading into the working set.
  • allFieldsRequired enforced at DO level: The DO validates post-merge state
    (existing secrets + patch), not just the incoming patch. This allows
    single-field rotations (e.g., rotating slackBotToken when slackAppToken is
    already stored) while still rejecting invalid partial clears.
  • secretCount excludes channel env vars: Channel tokens are dual-written into
    encryptedSecrets but excluded from secretCount (via CHANNEL_ENV_VARS filter)
    since they're already counted by channelCount. Applied in getStatus(),
    getDebugState(), and /api/kiloclaw/config.
  • Validation errors return 400: DO validation errors carry status: 400 and an
    Invalid secret patch: prefix so the route handler returns the actual message
    instead of a generic 500.

Verification

  • Catalog package tests pass (37 tests) — includes new allFieldsRequired contract, maxLength contract, and unknown key rejection tests
  • DO updateSecrets tests pass (8 tests) — set, remove, merge, legacy migration, Slack dual-token set/clear, channel-only filtering
  • DO updateChannels tests pass (8 tests) — all 6 existing tests pass through delegation path, plus 2 new interleave consistency tests
  • updateChannels delegation produces identical boolean return shape as original implementation
  • Worker builds without errors (wrangler types + wrangler deploy --dry-run)
  • Integration: provision instance via patchChannels, update via patchSecrets, verify both endpoints see consistent state

Visual Changes

N/A

Reviewer Notes

  • Dual-write strategy: updateSecrets writes to both channels (legacy, field-keyed) and encryptedSecrets (new, env-var-keyed). Channel-category keys go to both; future non-channel secrets (tools, providers) only go to encryptedSecrets.
  • updateChannels delegation: The old method now delegates to updateSecrets. This is the fix for the interleave drift edge case; there's now a single write path. Existing updateChannels callers see no behavior change.
  • tRPC defers allFieldsRequired to DO: The tRPC layer validates key membership, patterns, and maxLength, but does not check allFieldsRequired — only the DO can see existing state to validate post-merge correctness.
  • configured response filtered: The configured array returned by updateSecrets is filtered to ALL_SECRET_FIELD_KEYS only, preventing non-catalog env var names from leaking into the response.
  • Rate limiting: Not yet applied to patchSecrets. Should match whatever strategy patchChannels uses. Can be added as a follow-up.
  • tRPC-level integration tests: Validation logic is tested via catalog contract tests and DO-level tests rather than full tRPC caller tests, since the router test infrastructure requires real DB + user setup + worker client mocking.
  • Depends on: PR feat(kiloclaw): add @kilocode/kiloclaw-secret-catalog package #857 (@kilocode/kiloclaw-secret-catalog package)

@St0rmz1 St0rmz1 changed the title feat(kiloclaw): add generic patchSecrets endpoint with catalog-driven… feat(kiloclaw): add generic patchSecrets endpoint with catalog-driven validation Mar 9, 2026
Comment thread kiloclaw/src/schemas/instance-config.ts Outdated
Comment thread src/routers/kiloclaw-router.ts Outdated
@kilo-code-bot
Copy link
Copy Markdown
Contributor

kilo-code-bot Bot commented Mar 9, 2026

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0

Fix these issues in Kilo Cloud

Issue Details (click to expand)

No new issues on diff lines.

Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
src/routers/kiloclaw-router.ts 287 patchChannels still forwards KiloClawApiError directly, so the new worker-side updateSecrets() validation surfaces through the current dashboard flow as a generic tRPC internal error instead of the actionable 4xx message.
Files Reviewed (14 files)
  • kiloclaw/packages/secret-catalog/src/__tests__/catalog.test.ts - 0 issues
  • kiloclaw/packages/secret-catalog/src/catalog.ts - 0 issues
  • kiloclaw/packages/secret-catalog/src/index.ts - 0 issues
  • kiloclaw/src/durable-objects/kiloclaw-instance.test.ts - 0 issues
  • kiloclaw/src/durable-objects/kiloclaw-instance.ts - 0 issues
  • kiloclaw/src/routes/kiloclaw.ts - 0 issues
  • kiloclaw/src/routes/platform.ts - 0 issues
  • kiloclaw/src/schemas/instance-config.ts - 0 issues
  • package.json - 0 issues
  • pnpm-lock.yaml - 0 issues
  • src/hooks/useKiloClaw.ts - 0 issues
  • src/lib/kiloclaw/kiloclaw-internal-client.ts - 0 issues
  • src/lib/kiloclaw/types.ts - 0 issues
  • src/routers/kiloclaw-router.ts - 1 issue

…schema

  Address two code review findings on PR #947:

  1. SecretsPatchSchema now validates keys against the secret catalog,
     rejecting unknown field keys at the worker layer (defense in depth).

  2. updateSecrets remaps field keys to env var names at the storage
     boundary so buildEnvVars/mergeEnvVarsWithSecrets works correctly.
     A reverse map (ENV_VAR_TO_FIELD_KEY) ensures reading back from
     encryptedSecrets doesn't accumulate phantom entries.

  No changes to env.ts or encryption.ts — the read pipeline works as-is
  since encryptedSecrets now stores env var names.
Comment thread src/routers/kiloclaw-router.ts Outdated
Comment thread kiloclaw/src/durable-objects/kiloclaw-instance.ts
St0rmz1 added 2 commits March 9, 2026 10:24
…nt double-count

  - Add post-merge allFieldsRequired validation in updateSecrets to reject
    partial clears (e.g. removing one Slack token while keeping the other)
  - Filter channel env vars from secretCount to prevent double-counting
    with channelCount
  - Add test for partial clear rejection
Comment thread kiloclaw/src/routes/platform.ts
Comment thread kiloclaw/src/durable-objects/kiloclaw-instance.ts Outdated
Comment thread kiloclaw/src/durable-objects/kiloclaw-instance.ts Outdated
Comment thread src/routers/kiloclaw-router.ts Outdated
Comment thread kiloclaw/src/durable-objects/kiloclaw-instance.ts
@St0rmz1 St0rmz1 requested a review from pandemicsyn March 9, 2026 20:06
Copy link
Copy Markdown
Contributor

@pandemicsyn pandemicsyn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pre-approving, but heads up - i think you have some of your browser image update in here as well.

Comment thread src/routers/kiloclaw-router.ts
Comment thread kiloclaw/src/durable-objects/kiloclaw-instance.ts
Comment thread kiloclaw/Dockerfile Outdated
St0rmz1 added 3 commits March 9, 2026 15:29
… to catalog keys

  - Throw validation errors with status 400 and 'Invalid secret patch:'
    prefix so sanitizeError passes the message through instead of generic 500
  - Add prefix to SAFE_ERROR_PREFIXES in platform routes
  - Filter configured return to only include ALL_SECRET_FIELD_KEYS,
    preventing non-catalog env var names from leaking into the response
…ingle-field rotations

  The patch-only check rejected valid operations like rotating slackBotToken
  when slackAppToken was already stored. The DO's post-merge check is the
  authoritative guard since it sees existing state + patch together
…law/config

The config route still used raw Object.keys(encryptedSecrets).length, double-counting channel tokens that are already reported by channelCount.
Apply the same CHANNEL_ENV_VARS filter used in getStatus().
@St0rmz1 St0rmz1 force-pushed the feat/secret-catalog-trpc branch from 446547e to fb6be31 Compare March 9, 2026 22:30
Comment thread src/routers/kiloclaw-router.ts Outdated
…in patchSecrets

  - Export SecretFieldKey union from secret-catalog package
  - Use Partial<Record<SecretFieldKey, ...>> for patchSecrets input/response types
  - Preserve response body in KiloClawApiError for structured error handling
  - Translate worker 4xx responses into TRPCErrors with actionable messages
Comment thread kiloclaw/src/durable-objects/kiloclaw-instance.ts Outdated
…event field-key collisions

  - Export SecretFieldKey union from secret-catalog package
  - Use Partial<Record<SecretFieldKey, ...>> for patchSecrets input/response types
  - Preserve response body in KiloClawApiError for structured error handling
  - Translate worker 4xx responses into TRPCErrors with actionable messages
  - Track non-catalog secrets separately in updateSecrets to prevent
    field-key collisions from silently rewriting generic secrets
@St0rmz1 St0rmz1 merged commit 40d65ee into main Mar 9, 2026
18 checks passed
@St0rmz1 St0rmz1 deleted the feat/secret-catalog-trpc branch March 9, 2026 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants