fix(sdk): Teams adapter token security + migration guide#906
fix(sdk): Teams adapter token security + migration guide#906bradygaster merged 3 commits intodevfrom
Conversation
🟡 Impact Analysis — PR #906Risk tier: 🟡 MEDIUM 📊 Summary
🎯 Risk Factors
📦 Modules Affecteddocs (1 file)
root (1 file)
squad-sdk (2 files)
tests (1 file)
This report is generated automatically for every PR. See #733 for details. |
There was a problem hiding this comment.
Pull request overview
This PR hardens the Teams (teams-graph) communication adapter’s credential handling and documents the breaking async factory change introduced in PR #768, focusing on multi-tenant safety and explicit logout semantics.
Changes:
- Add tenant-scoped token storage (hashed per-tenant filename), revocation support, and device-code timeout/polling bounds to the Teams adapter.
- Extend the
CommunicationAdapterinterface with an optionalrevoke()method for local credential purge. - Add a migration guide and a dedicated security test suite; include a changeset for the SDK patch release.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/comms-teams-security.test.ts | Adds security-focused tests for tenant isolation, revocation, migration, and device-code guard constants. |
| packages/squad-sdk/src/platform/types.ts | Adds optional revoke?(): Promise<void> to CommunicationAdapter. |
| packages/squad-sdk/src/platform/comms-teams.ts | Implements tenant-scoped token paths, migration, revoke(), device-code timeout guard, and permanent auth error handling. |
| docs/migration/teams-async-adapter.md | Documents the async createCommunicationAdapter migration and token security changes. |
| .changeset/teams-adapter-security.md | Declares a patch changeset for the SDK release notes. |
0335a57 to
032df44
Compare
🛫 PR Readiness Check
PR Scope: 📦🔧 Mixed (product + infrastructure)
|
| Status | Check | Details |
|---|---|---|
| ✅ | Single commit | 1 commit — clean history |
| ✅ | Not in draft | Ready for review |
| ✅ | Branch up to date | Up to date with dev |
| ❌ | Copilot review | No Copilot review yet — it may still be processing |
| ✅ | Changeset present | Changeset file found |
| ✅ | Scope clean | No .squad/ or docs/proposals/ files |
| ✅ | No merge conflicts | No merge conflicts |
| ❌ | Copilot threads resolved | 1 unresolved Copilot thread(s) — fix and resolve before merging |
| ❌ | CI passing | 1 check(s) failing: test |
| ✅ | Issue linked | Issue reference found |
| ✅ | Protected files | No protected bootstrap files changed |
Files Changed (5 files, +680 −40)
| File | +/− |
|---|---|
.changeset/teams-adapter-security.md |
+5 −0 |
docs/migration/teams-async-adapter.md |
+129 −0 |
packages/squad-sdk/src/platform/comms-teams.ts |
+176 −40 |
packages/squad-sdk/src/platform/types.ts |
+7 −0 |
test/comms-teams-security.test.ts |
+363 −0 |
Total: +680 −40
This check runs automatically on every push. Fix any ❌ items and push again.
See CONTRIBUTING.md and PR Requirements for details.
032df44 to
b759ada
Compare
b759ada to
37e811d
Compare
🔍 Squad Review — Kaylee (Engineering)
Verdict: ✅ Ready to merge Review by Squad AI team (Kaylee — Engineering) · requested by Dina Berry |
diberry
left a comment
There was a problem hiding this comment.
Review findings for PR #906
🔴 required: loadTokens validates accessToken and expiresAt shape but skips refreshToken — a cached file with a null/missing refresh token passes the guard, gets returned to ensureAuthenticated, and triggers a wasted AAD call that always fails before falling back to re-auth. One-liner fix: add && typeof parsed.refreshToken === 'string' to the shape check.
🔴 required: The key behavioral invariant — "permanent error clears disk tokens; transient error preserves them" — isn't exercised through ensureAuthenticated. A mock test for refresh failure routing would catch regressions in that if/else branch.
🟡 suggestion: Changeset description says "explicit revoke() for logout" but the method was renamed to logout(). This text becomes the changelog entry — worth fixing before merge.
🟡 suggestion: resetIdentityCaches() clears resolvedChatId on every successful token refresh. For adapters relying on dynamic resolution, this means one extra Graph API call per hour. Consider only clearing if the authenticated oid actually changes.
Add typeof refreshToken === 'string' to shape check in loadTokens to prevent wasted AAD round-trips on incomplete cached tokens. Fix changeset to say logout() instead of revoke(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
✅ CI validation passed on fork: diberry#142 — 8/8 checks green |
The Copilot CLI uses `-p` for non-interactive prompts, not `--message`. Also switches from `gh copilot` to `copilot` directly to avoid Windows console window issues with .cmd batch files. Files changed (8 source + 1 test + 1 pre-existing fix): - watch/capabilities/execute.ts - watch/capabilities/decision-hygiene.ts - watch/capabilities/monitor-email.ts - watch/capabilities/monitor-teams.ts - watch/capabilities/retro.ts - watch/capabilities/wave-dispatch.ts - watch/index.ts - loop.ts - comms-teams.ts (pre-existing TOKEN_PATH typo from #906) - test/cli/watch-execute.test.ts Fixes #980 Closes #874 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Copilot CLI uses -p for non-interactive prompts, not --message. Uses copilot directly on Windows to avoid .cmd console window issues, falls back to gh copilot on Linux/macOS for compatibility. Changes: - 8 source files: --message to -p, platform-aware cmd selection - loop.ts: aligned preflight check with actual invocation command - comms-teams.ts: fix pre-existing TOKEN_PATH typo from #906 - 2 test files updated: watch-execute + watch-capabilities - Added changeset for squad-cli + squad-sdk patch bumps All 67 tests pass (watch-execute: 16, watch-capabilities: 51). Fixes #980 Closes #874 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Copilot CLI uses -p for non-interactive prompts, not --message. Uses copilot directly on Windows to avoid .cmd console window issues, falls back to gh copilot on Linux/macOS for compatibility. Changes: - 8 source files: --message to -p, platform-aware cmd selection - loop.ts: aligned preflight check with actual invocation command - comms-teams.ts: fix pre-existing TOKEN_PATH typo from #906 - 2 test files updated: watch-execute + watch-capabilities - Added changeset for squad-cli + squad-sdk patch bumps All 67 tests pass (watch-execute: 16, watch-capabilities: 51). Fixes #980 Closes #874 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Copilot CLI uses -p for non-interactive prompts, not --message. Switches default invocation from gh copilot to copilot directly (the standalone binary available on all platforms). Changes: - 8 source files: --message to -p, cmd changed to copilot - loop.ts: aligned preflight to check copilot --version - comms-teams.ts: fix pre-existing TOKEN_PATH typo from #906 - 2 test files updated (watch-execute + watch-capabilities) - Changeset added for squad-cli + squad-sdk patch bumps Fixes #980 Closes #874 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
) The Copilot CLI uses -p for non-interactive prompts, not --message. Switches default invocation from gh copilot to copilot directly (the standalone binary available on all platforms). Changes: - 8 source files: --message to -p, cmd changed to copilot - loop.ts: aligned preflight to check copilot --version - comms-teams.ts: fix pre-existing TOKEN_PATH typo from #906 - 2 test files updated (watch-execute + watch-capabilities) - Changeset added for squad-cli + squad-sdk patch bumps Fixes #980 Closes #874 Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(sdk): Teams adapter token security + migration guide * fix(sdk): add refreshToken validation to loadTokens + fix changeset text Add typeof refreshToken === 'string' to shape check in loadTokens to prevent wasted AAD round-trips on incomplete cached tokens. Fix changeset to say logout() instead of revoke(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Dina Berry <diberry@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Brady Gaster <41929050+bradygaster@users.noreply.github.com>
) The Copilot CLI uses -p for non-interactive prompts, not --message. Switches default invocation from gh copilot to copilot directly (the standalone binary available on all platforms). Changes: - 8 source files: --message to -p, cmd changed to copilot - loop.ts: aligned preflight to check copilot --version - comms-teams.ts: fix pre-existing TOKEN_PATH typo from #906 - 2 test files updated (watch-execute + watch-capabilities) - Changeset added for squad-cli + squad-sdk patch bumps Fixes #980 Closes #874 Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
What
Identity-scoped token cache, logout mechanism, device-code timeout guard, JWT claim extraction, and migration guide for async adapter change.
Why
PR #768 introduced a breaking async change with no migration path. Token cache had security gaps: no tenant/app isolation, no revocation, no timeout guard, stale tokens reused on error.
How
evoke()\ for honest naming (public AAD clients can't revoke server-side). Local credential purge: clears memory + disk + identity caches.
esetIdentityCaches()\ called on every token change (load, refresh, re-auth, logout).
Design decisions
evoke()* — public-client AAD refresh tokens can't be reliably revoked server-side.
Tests
37 new security tests (72 total) covering:
Note on PR #883
PR #883 (channelId encoding, icacls warning, integration tests) is a separate set of fixes with no overlap.
Closes #862
Related: #895 (SDK Hardening PRD)