Skip to content

fix(devin): bind tenant credentials and bound reset refusals - #5541

Closed
luvs01 wants to merge 8 commits into
devfrom
stack/devin-runtime-boundaries
Closed

luvs01 wants to merge 8 commits into
devfrom
stack/devin-runtime-boundaries

Conversation

@luvs01

@luvs01 luvs01 commented Sep 22, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Consolidate #5143, #5455, and the alias credential/tenant binding in 9b54aebb8ce1dbad8a49000e6cc7f002ab71d3ae around one Devin turn boundary: bind the selected credential to its tenant, preserve safe reset timing, and release active-turn capacity immediately on a reset refusal.

  • A configured alias uses a recorded tenant only when that record owns the exact selected credential; unrelated saved aliases cannot redirect the turn.
  • Connect trailer diagnostics keep allowlisted codes, trace IDs and typed retry timing while withholding upstream text that can reflect credentials.
  • The adapter makes no reset wait while owning shared turn capacity. The common helper's explicit bounded-wait behavior remains available to callers that opt into it.
  • Preserve seven source commits and attribution. An integration follow-up reconciles the two source tests' former verbatim-error expectations with sanitized retry diagnostics, adds an alias→EU tenant→429 regression, and corrects the corresponding contracts. The unrelated common role-fixture fix was already present on dev and is not duplicated.

Verification

  • Base: dev 7bd9985963fd344a2c512fa5cf3c0f57f6ba83bd. Head: 6f2901b117b6d8a9a7eae503e795b74749b6e7e0.
  • Direct Bun across 14 Devin test files: 252 passed, 0 failed. Layout/tooling: 18 passed. Isolated file-size ratchet: 9 passed.
  • bun run typecheck, bun run structure:check, bun run privacy:scan, and base-to-head git diff --check passed.
  • Integration regressions reproduced two failures before the follow-up; the corrected three-case run passed with 13 assertions, checking 429 classification, exact reset seconds, credential non-disclosure and one physical send.
  • Initial concurrent ratchet scan exceeded the default 5-second test budget. It passed unchanged in isolation (scan 1.085 seconds); no timeout, cap or baseline was raised.
  • Full suite, Linux/macOS execution, exact-head hosted CI and independent security review remain pending. No live paid-provider request or dependency installation was performed. This PR stays draft.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

luvs01 and others added 8 commits September 22, 2026 20:38
Connect-RPC EOS trailers propagated upstream-controlled error.message text into adapter error events, API responses, and diagnostic logs, and that text can reflect the credential carried by the request. Parse trailers with an allowlist for known Connect codes, keep the raw message inside the parser for quota/opaque-denial detection, and throw content-free errors carrying only the allowlisted code and hex trace id.

(cherry picked from commit 50eab5a)
The trailer suppression left the thrown CloudChatError without the upstream "limit will reset in N" sentence that streamChatEventsWithResetRetry scraped for its cooldown, so a stated-reset 429 failed immediately instead of waiting out the quota window. Parse the delay inside the trailer parser while the raw text still exists and carry it as a typed retryAfterSeconds field; the retry prefers that field and keeps the message scrape for errors whose text still carries a hint. Adds a regression test that drives the real Connect EOS trailer path.

(cherry picked from commit 7b7a8b8)
The module-mock spy leaked into sibling test files in the same bun test process, so devin-stated-reset-retry and devin-stated-reset-hardening saw a stubbed helper. Drive the real runTurn with a stubbed fetch returning a Connect EOS-trailer refusal instead: the asserted signals are the surfaced error text and the physical GetChatMessage count, which fail fast under the 5s abort signal if the adapter's maxWaitMs: 0 pin is removed or weakened. Register the file in the test layout map and fixture.

(cherry picked from commit 1abc035)
Reconcile #5455 zero-wait coverage with #5143 content-free Connect diagnostics: retain 429 and generated retry timing while proving reflected credentials do not escape and only one inference send occurs. Exercise a matching deprecated alias credential through tenant dispatch and the same refusal path. Preserve the zero wait pin, host allowlist, credential binding, and typed reset field. Document the safe typed retry delay and opt-in helper replay contract.
@coderabbitai

coderabbitai Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Sep 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Deterministic PR hygiene checks passed.

@lidge-jun

Copy link
Copy Markdown
Owner

리뷰 · 우선순위 61 / 80

이 PR은 Devin 턴 한 번의 경계를 세 갈래로 단단히 묶습니다. 첫째, deprecated devin-cli 별칭 슬롯에 저장된 테넌트(호스트)는, 그 슬롯이 지금 보내는 자격증명과 같은 키를 가질 때만 빌려 씁니다. 둘째, Connect EOS 트레일러의 업스트림 문장(자격증명이 비칠 수 있음)은 밖으로 내보내지 않고, 허용된 코드·트레이스 ID·숫자로 뽑은 retryAfterSeconds만 남깁니다. 셋째, 어댑터는 maxWaitMs: 0으로 두어, 이미 자리를 잡은 턴이 429 대기 동안 공유 용량을 붙잡지 않게 합니다. 헬퍼 자체는 예전처럼 기다렸다가 다시 보낼 수 있고, 어댑터만 기다리지 않습니다. #5143·#5455(및 포크 쪽 관련 작업)를 한 줄기로 모은 통합안이며, 테스트·structure 문서도 같이 맞춰 두었습니다.

라인 - src/oauth/devin.ts resolveDevinApiServer: 별칭(alias) 폴백만 apiKey와 credential.access가 같을 때 호스트를 씁니다. 그런데 리터럴 슬롯(getCredential(providerId))에 apiBaseUrl이 있으면, 지금 보내는 키와 달라도 그 호스트를 그대로 반환합니다. 별칭만 조인 보안 구멍이고 리터럴은 예전 동작이라는 뜻이면 괜찮지만, “보내는 키 ↔ 기록된 테넌트”를 한 규칙으로 보고 싶다면 여기가 비대칭입니다.
라인 - src/adapters/devin.ts maxWaitMs: 0: 동작이 바뀝니다. 예전에는 어댑터가 “limit will reset in N초”를 읽고 잠깐 기다렸다가 같은 요청을 다시 보냈을 수 있고, 지금은 거절을 바로 밖으로 보냅니다. 의도(공유 턴 용량 즉시 반납)는 문서·테스트와 맞지만, 상위 재시도/페일오버가 그 숫자를 읽고 다시 돌릴 준비가 되어 있는지 확인이 필요합니다.
라인 - src/adapters/devin/cloud-direct/chat.ts permission_denied(+도구) 경로: (cloud message: …)를 빼서 자격증명 반사는 막았지만, 블록리스트 문구 매칭을 추적할 때 쓸 원문이 사라집니다. 보안 우선이면 맞고, 운영 디버그는 트레이스 ID만으로 충분한지 판단이 필요합니다.
라인 - PR 본문 Checklist의 “Security-sensitive … reviewed”가 아직 비어 있고, 전체 스위트·호스트 CI·독립 보안 리뷰도 pending입니다. 로컬 Devin 14파일 252통과는 강하지만, draft 머지 전 게이트로는 아직 한 칸 남습니다.

메인테이너의 판단이 필요한 지점

리터럴 슬롯에도 “전송 키 == credential.access” 검사를 같은 수준으로 넣을지, 아니면 별칭 재키(rekey) 창만의 문제로 두고 문서에 “리터럴은 예외”라고 못 박을지. 또 maxWaitMs: 0이 Devin 사용자 체감(자동 대기 없음)으로 받아들여질지, 상위 레이어 재시도에 맡기는 계약이 맞는지.

너의 추천

보안·용량 쪽 방향은 맞고 테스트도 핵심 계약(자격증명 비노출, 1회 전송, typed retry, 별칭 바인딩)을 잘 잡고 있습니다. draft에서 보안 체크·관련 CI를 채운 뒤 ready로 올리고, 이미 닫힌 #5143/#5455와 겹치는 열려 있는 중복/무효 PR이 있으면 이 브랜치 기준으로 닫는 편이 좋습니다. 리터럴 슬롯 비대칭만 한 줄 정책으로 확정하면 머지 리스크가 더 줄어듭니다.

이 댓글은 grok-bot이 작성했습니다

@lidge-jun

Copy link
Copy Markdown
Owner

Thank you, @luvs01. All eight commits of this PR landed unchanged, with your authorship, in #5574 (squash 7841946), together with one follow-up that applies the same key-ownership rule to the literal credential slot. Closing as landed via #5574.

@lidge-jun lidge-jun closed this Sep 22, 2026
@lidge-jun
lidge-jun deleted the stack/devin-runtime-boundaries branch September 26, 2026 01:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants