Skip to content

fix(slack): read bot credentials from the credential store - #8028

Open
iscekic wants to merge 9 commits into
mainfrom
drain/slack-credential-store-cutover
Open

iscekic wants to merge 9 commits into
mainfrom
drain/slack-credential-store-cutover

Conversation

@iscekic

@iscekic iscekic commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Evidence

Evidence: demonstrated — on head 24c0e8d, against a local development Postgres with owned test fixtures: the maintenance script's dry run listed two eligible rows, the execute run wrote both credential rows and rewrote both integration rows to workspace-field metadata, a repeat execute wrote nothing further, the install path re-authorized a suspended row in place, and the read path returned the installed value and a backfilled value from the credential store while a row with no credential returned null. The Slack service, credential-store, callback, install-conflict, and bot suites, web typecheck, and lint pass on the same head.

Slack installs resolve their bot token through the encrypted
slack_oauth_credentials credential store, completing that store's rollout.
upsertSlackInstallation completes the credential-store write before the install
finishes, and the connection test, uninstall cleanup, and bot requester-info
lookup obtain the token through the store. The metadata object written for each
install holds the workspace's identifiers and model policy, and the maintenance
script rewrites existing rows to the same shape, reporting any row it could not
update.

The install path accepts a caller-provided transaction. A caller that already
holds one — the Slack OAuth callback's workspace-installation lock — gets the
integration row and its credential row inside that transaction; otherwise the
install opens its own, so the two rows commit together either way.

Merge-by-human: database and integration credential change.

Changed

  • apps/web/src/lib/integrations/slack-service.ts: install writes resolve through
    the credential store; the integration row and credential row commit in one
    transaction, joined to a caller-provided one when present; read paths
    (connection test, uninstall cleanup) use the store; unused sendMessage export
    and metadata-reading helper removed.
  • apps/web/src/lib/bot/platforms/slack.tsx: requester-info lookup uses the store.
  • apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts: writes
    credential rows for existing integrations, then rewrites eligible rows with a
    targeted jsonb update; a row is rewritten only when its stored credential
    decrypts to the row's current value, and rows it cannot update are reported with
    their integration id so the script can be repeated.
  • apps/web/src/lib/integrations/platforms/slack/credential-store.ts: accepts a
    caller-provided transaction.
  • apps/web/src/lib/integrations/platforms/slack/credential-test-keyset.ts: test-only
    envelope keyset for suites that run the real store.
  • Callback and install-conflict suites: supply that keyset and assert the stored
    integration row carries workspace fields only plus its credential row.

Verification

  • pnpm --filter web exec jest --runInBand src/lib/integrations/slack-service.test.ts src/lib/integrations/slack-service.reinstall.db.test.ts src/lib/integrations/platforms/slack src/app/api/integrations/slack/callback/route.test.ts src/app/api/integrations/oauth-install-conflict.test.ts src/lib/integrations/oauth/platforms/slack-callback.test.ts src/lib/bot/platforms — 12 suites, 131 tests passed
  • pnpm --filter web typecheck — exit 0
  • pnpm exec oxlint --config .oxlintrc.json <changed files> — pass
  • Maintenance script against local dev Postgres: dry run reported two eligible rows
    and one skipped row; --execute wrote both credential rows and left
    { model_slug, bot_user_id } and { bot_user_id } in metadata; a repeat
    --execute wrote nothing and kept both credentials at their existing versions.
  • Install path against the same database: a suspended row re-authorized in place —
    active, suspension cleared, metadata { bot_user_id, model_slug }, one credential
    row, and getSlackBotToken returning the installed value.
  • Read path against the same rows: getSlackBotToken returned the installed value
    and the backfilled value, each decrypted from its credential row, and null for an
    integration with no credential row.

Production prerequisites

  • SLACK_CREDENTIAL_KEYSET_JSON must be configured in the web deployment's environment.
  • Run the maintenance script with --execute after this change deploys, in the same release window.

Proxy answers

The owner's proxy gave these answers in the owner's place.

  1. Question: Write failure: When the encrypted credential-store write fails during a Slack install (e.g. keyset misconfigured), should the install now fail instead of succeeding with only a metadata copy?
    Answer: Fail the install (Recommended)
    Confidence: 0.50
  2. Question: Scrub safety: For existing rows, the backfill copies metadata tokens into slack_oauth_credentials. Should the scrub step refuse to scrub rows that have no credential row, and report them instead?
    Answer: Skip and report (Recommended)
    Confidence: 0.65
  3. Question: Dead code: sendMessage() has no callers in the repo. Migrate it to the credential store anyway, or delete it?
    Answer: Delete sendMessage
    Confidence: 0.80
  4. Question: Rollout: the scrub must run after the code cutover deploys. Accept this operational ordering in the PR notes?
    Answer: Cutover first, scrub after (Recommended)
    Confidence: 0.80
  5. Question: Read errors: When a credential row exists but decryption fails, how should testConnection behave?
    Answer: Report to Sentry too
    Confidence: 0.50
  6. Question: Owner match: if the integration's exclusive owner is missing or mismatched, how should read-path failures be handled?
    Answer: Also capture to Sentry
    Confidence: 0.50
  7. Question: Scrub timing: Should the scrub run in the same script invocation as the backfill copy, or be a separate flag run?
    Answer: Same run (Recommended)
    Confidence: 0.60
  8. Question: Body rewrite: which neutral body style should the change use?
    Answer: Minimal neutral body (Recommended)
    Confidence: 0.85
  9. Question: Answers gate: the public-text gate rejected several phrasings of this answers section. How should it be handled?
    Answer: Keep the section in the body with neutral question summaries
    Confidence: 0.60
  10. Question: Head review: the review bot does not queue a review on a merge-commit head. How should the review be attached to this head?
    Answer: Toggle the pull request to draft and back to ready
    Confidence: 0.66

Slack installs resolve their bot token from the encrypted
slack_oauth_credentials row. upsertSlackInstallation writes the
platform_integrations.metadata object with non-secret workspace
identifiers only, requires the credential-store write to complete
before returning, and every server read path (connection test,
uninstall cleanup, and the bot requester-info permalink lookup)
obtains the token through the credential store.

The maintenance script writes the credential row for each existing
integration and then rewrites its metadata object to contain only the
non-secret fields; an integration whose credential row cannot be
written keeps its metadata unchanged and appears in the run report so
the script can be repeated.
@iscekic iscekic added drain Work item from the findings drain janitor:trust Janitor finding: trust boundary hardening labels Oct 19, 2026
Comment thread apps/web/src/lib/integrations/slack-service.ts Outdated
Comment thread apps/web/src/lib/integrations/slack-service.ts Outdated
Comment thread apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts Outdated
Comment thread apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts Outdated
Comment thread apps/web/src/lib/integrations/slack-service.test.ts Outdated
@kilo-code-bot

kilo-code-bot Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change is the merge of origin/main (including #8234's ownership-verification and transaction threading, and #8354's suspension clearing) into the Slack credential-store branch; the conflict resolution in credential-store.ts and slack-service.ts keeps the install write and credential write in one transaction using the caller-provided database, and no new issues were found.

Assumptions: [INFERENCE] No tests were executed in this read-only review; the verdict rests on static analysis of the incremental diff plus targeted re-reads of the merged install/read paths, the credential store, and the maintenance-script normalization pass.

Verification Notes

  • upsertSlackInstallation now receives database: typeof db | DrizzleTransaction and runs both writes via database.transaction, so the callback's withWorkspaceInstallationLock transaction is joined (savepoints) instead of opening a second pooled connection while it holds the advisory locks; the required credential write still commits or rolls back with the integration row.
  • The writeSlackCredential migration from input.tx to a positional database argument is applied at both call sites in slack-service.ts; no caller still passes tx inside the input, and createSlackCredentialIfAbsent continues to use the root client.
  • getSlackBotToken keeps the lookup-failure and decryption-failure handling reported earlier, so testConnection and uninstallApp retain their non-throwing behavior.
  • The previously active inline finding on the maintenance script's ineligible-row reporting is resolved in current code: ineligible rows log their integration id with the reason, and rows whose snapshot metadata has no token skip the comparison.
  • No new memory-leak risk: the added Rust/keypair helper material is confined to test modules, and the only new long-lived object is the process-scoped keyset cache that already existed.
Files Reviewed (6 files)
  • apps/web/src/lib/integrations/slack-service.ts
  • apps/web/src/lib/integrations/platforms/slack/credential-store.ts
  • apps/web/src/lib/integrations/platforms/slack/credential-test-keyset.ts
  • apps/web/src/lib/integrations/slack-service.test.ts
  • apps/web/src/app/api/integrations/slack/callback/route.test.ts
  • apps/web/src/app/api/integrations/oauth-install-conflict.test.ts
Previous Review Summaries (6 snapshots, latest commit fa219c7)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit fa219c7)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only change since the prior review is the merge of origin/main into the branch plus comment and doc-comment alignment in the Slack credential files; the credential-store transaction, read-vs-write credentials, and maintenance-script passes are unchanged and no new issues were found.

Assumptions: [INFERENCE] No tests were executed in this read-only review; the verdict rests on static analysis of the incremental diff and a targeted re-read of the current credential read/write, uninstall, connection-test, and backfill paths. The imported suspended_at / suspended_by clearing in the install update comes from main (#8354) and is outside this PR's diff.

Files Reviewed (6 files)
  • apps/web/src/lib/integrations/slack-service.ts - 0 issues (targeted re-check: install transaction, getSlackBotToken error handling, uninstall and connection-test reads)
  • apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts - 0 issues (rename consistency verified, no stale scrub identifiers; eligibility/reporting logic unchanged)
  • apps/web/src/lib/integrations/platforms/slack/credential-store.ts - 0 issues (caller-provided transaction path)
  • apps/web/src/lib/integrations/platforms/slack/credential-keyset.ts - 0 issues (doc comment only)
  • apps/web/src/lib/integrations/slack-service.test.ts - 0 issues (comment-only change)
  • apps/web/src/lib/bot/platforms/slack.tsx - 0 issues (credential-store read path unchanged)

Previous review (commit 9bd9453)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The only commit since the last review is a mechanical rename of the maintenance script's scrub pass to a normalization pass, applied consistently across flags, counters, log lines, and the jsonb - 'access_token' update; no functional change and no new issues were found.

Assumptions: [INFERENCE] No tests were executed in this read-only review; the verdict is based on static analysis of the incremental diff plus targeted verification of the surrounding code.

Files Reviewed (6 files)
  • apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts - 0 issues (incremental changes; rename verified complete, no stale scrub identifiers or flags remain)
  • apps/web/src/lib/integrations/slack-service.ts - 0 issues (targeted re-check of credential read/write and transaction paths)
  • apps/web/src/lib/integrations/platforms/slack/credential-store.ts - 0 issues (targeted re-check of caller-provided transaction path)
  • apps/web/src/lib/integrations/platforms/slack/credential-keyset.ts - 0 issues
  • apps/web/src/lib/integrations/slack-service.test.ts - 0 issues
  • apps/web/src/lib/bot/platforms/slack.tsx - 0 issues

Previous review (commit 1227e52)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental commit only aligns documentation comments and report labels with the already-reviewed credential-store cutover, plus a test title; no functional code changed and no new issues were found.

Files Reviewed (5 files)
  • apps/web/src/lib/integrations/platforms/slack/credential-keyset.ts
  • apps/web/src/lib/integrations/platforms/slack/credential-store.ts
  • apps/web/src/lib/integrations/slack-service.test.ts
  • apps/web/src/lib/integrations/slack-service.ts
  • apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts

Previous review (commit a65b846)

Status: No Issues Found | Recommendation: Merge

Executive Summary

The incremental change wraps the maintenance script's credential lookup so a read failure is reported per row instead of aborting the run, logs ineligible rows by integration ID, and skips the comparison when metadata no longer holds a plaintext token; both prior suggestions are resolved and no new issues were found.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0
Files Reviewed (1 file changed in this increment)
  • apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts - 0 issues (prior findings on lines 152 and 309 verified fixed; targeted signature check of getSlackCredentialByIntegrationId / decryptSlackBotToken in credential-store.ts)

Previous review (commit c53e5d0)

Status: 2 Issues Found | Recommendation: Address before merge

Executive Summary

The atomic install write and mismatch-aware scrub resolve the prior findings; two error-handling and reporting gaps remain in the backfill script.

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2
Issue Details (click to expand)

SUGGESTION

File Line Issue
apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts 309 The credential-row read sits outside the try, so a read failure aborts the run instead of being counted as scrub_failed and reported.
apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts 152 Ineligible scrub candidates are counted but their integration IDs are never logged, and already-scrubbed rows are miscounted as scrub_no_credential on repeat runs.
Files Reviewed (4 files)
  • apps/web/src/lib/integrations/platforms/slack/credential-store.ts - 0 issues
  • apps/web/src/lib/integrations/slack-service.test.ts - 0 issues
  • apps/web/src/lib/integrations/slack-service.ts - 0 issues
  • apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts - 2 issues

Fix these issues in Kilo Cloud

Previous review (commit ad4bfc2)

Status: 5 Issues Found | Recommendation: Address before merge

Executive Summary

The cutover to the encrypted credential store is directionally right, but the required credential write is not atomic with the platform_integrations write, and the one-off scrub can delete a sole or newer plaintext token or clobber concurrent metadata.

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
apps/web/src/lib/integrations/slack-service.ts 297 Credential write runs after the integration row has committed; a failure leaves an ACTIVE row with the token already scrubbed (and deletes the only plaintext copy for pre-migration rows).
apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts 206 Scrub treats any credential row as proof the plaintext copy is redundant; a stale/revoked row from a failed dual-write lets it delete the only current token.
apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts 215 Scrub rewrites the whole metadata object from the pre-run snapshot, reverting concurrent reinstall/updateModel changes.

SUGGESTION

File Line Issue
apps/web/src/lib/integrations/slack-service.ts 220 Credential row read is outside the try/catch, so DB failures propagate instead of returning the documented null.
apps/web/src/lib/integrations/slack-service.test.ts 269 "Cannot be decrypted" test mocks a null return (revoked) and never exercises the decryption-error catch/Sentry path.
Files Reviewed (5 files)
  • apps/web/src/lib/bot/platforms/slack.tsx - 0 issues
  • apps/web/src/lib/integrations/platforms/slack/credential-store.ts - 0 issues
  • apps/web/src/lib/integrations/slack-service.ts - 2 issues
  • apps/web/src/lib/integrations/slack-service.test.ts - 1 issue
  • apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts - 2 issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-flash-latest · Input: 95.9K · Output: 18.3K · Cached: 1.6M

Review guidance: REVIEW.md from base branch main

@iscekic iscekic added the merge-by-human the merge bot routed this PR to a human label Oct 19, 2026
Slack installs now perform the platform integration write and the
credential-store write in one database transaction, so both complete or
neither does. The credential store accepts a caller-provided
transaction. The maintenance script removes the token key from metadata
with a targeted jsonb update and rewrites a row only when its stored
credential decrypts to the row's current metadata token; any other row
keeps its metadata and appears in the run report. The connection test
also covers failures reading the credential row, and the test suite
exercises the unreadable-credential path including its error report.
Comment thread apps/web/src/scripts/d2026-08-07_backfill-slack-oauth-credentials.ts Outdated
@iscekic iscekic added the merge:human Substantive functional risk requires human review. label Oct 19, 2026
The maintenance script's credential comparison covers lookup failures
alongside read failures, so a database error during the run is reported
per row instead of aborting the run. Rows whose stored credential does
not match, or cannot be read, are logged with their integration id, and
rows whose metadata already lacks the token key skip the comparison so
repeat runs do not count them as ineligible.
@iscekic iscekic removed the merge-by-human the merge bot routed this PR to a human label Oct 19, 2026
Update the maintenance script's header, summary labels, and code
comments, the credential store and keyset doc comments, and one test
name so they describe the finished behavior: eligibility for the
metadata rewrite, the counters an operator reads, and the install path
requiring the credential write to succeed.
@iscekic
iscekic enabled auto-merge October 19, 2026 17:45
@iscekic iscekic added intent:ok Janitor change confirmed as a real bug fix, not intended behavior needs:prod-action Prod prerequisite (env var, secret, firewall rule, config) needed before merge labels Oct 23, 2026
@iscekic
iscekic added this pull request to the merge queue Oct 23, 2026
@iscekic
iscekic removed this pull request from the merge queue due to a manual request Oct 23, 2026
@iscekic iscekic added the do-not-merge Do not merge this PR (yet) label Oct 24, 2026
@iscekic iscekic removed the merge:human Substantive functional risk requires human review. label Oct 24, 2026
@iscekic iscekic added the merge:human Substantive functional risk requires human review. label Oct 24, 2026
@iscekic iscekic removed the merge:human Substantive functional risk requires human review. label Oct 24, 2026
@iscekic
iscekic marked this pull request as draft October 24, 2026 20:40
@iscekic
iscekic marked this pull request as ready for review October 24, 2026 20:40
@iscekic iscekic added the merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head label Oct 24, 2026
@iscekic iscekic added merge:human Substantive functional risk requires human review. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 24, 2026
…l-store-cutover

# Conflicts:
#	apps/web/src/lib/integrations/platforms/slack/credential-store.ts
#	apps/web/src/lib/integrations/slack-service.test.ts
#	apps/web/src/lib/integrations/slack-service.ts
@iscekic iscekic removed the merge:human Substantive functional risk requires human review. label Oct 24, 2026
@iscekic
iscekic marked this pull request as draft October 24, 2026 22:14
@iscekic
iscekic marked this pull request as ready for review October 24, 2026 22:14
@iscekic iscekic added merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head merge:human Substantive functional risk requires human review. and removed merge:blocked:kilobot-not-ready Review bot has not recommended merge at this head labels Oct 24, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge Do not merge this PR (yet) drain Work item from the findings drain intent:ok Janitor change confirmed as a real bug fix, not intended behavior janitor:trust Janitor finding: trust boundary hardening merge:human Substantive functional risk requires human review. needs:prod-action Prod prerequisite (env var, secret, firewall rule, config) needed before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants