Skip to content

feat: ADR-0011 PR-E1 — OAuth schema + token store の土台 (issue #28)#29

Merged
shomatan merged 2 commits into
mainfrom
feat/oauth-flow-tally-managed
May 2, 2026
Merged

feat: ADR-0011 PR-E1 — OAuth schema + token store の土台 (issue #28)#29
shomatan merged 2 commits into
mainfrom
feat/oauth-flow-tally-managed

Conversation

@shomatan
Copy link
Copy Markdown
Contributor

@shomatan shomatan commented May 2, 2026

Summary

ADR-0011 (Tally 側で OAuth 2.1 フローを管理する) の実装段階 1。issue #28 への対応として、外部 MCP server の access token を Tally で保持して SDK の `headers` に inject する設計の土台を作る。

本 PR だけでは OAuth フローは動かない。PR-E2 (OAuthClient + LoopbackCallbackServer) 以降の前提となる schema と storage を切り出した最小単位の PR。

ADR-0011

docs/adr/0011-tally-managed-oauth-flow.md で設計判断と PR 分割計画 (E1 〜 E5) を記録:

  • 後方互換は不要 (PoC 段階)
  • oauth フィールドは PR-E1 では optional・PR-E4 で旧 auth_request 経路の削除と同時に required 化
  • token は MVP では平文 YAML + 0o600 で保持 (暗号化は ADR-0012 で別途)

変更内容

packages/core

  • McpOAuthConfigSchema (新規): clientId 必須、scopes optional
  • McpServerConfigSchema.oauth: optional で追加
  • McpOAuthTokenSchema (新規): token store の YAML スキーマ。acquiredAt / expiresAt は ISO 8601 datetime、tokenType default 'Bearer'
  • src/oauth/atlassian.ts (新規): Atlassian Cloud OAuth 2.1 endpoint registry + OAuthProviderConfig interface

packages/storage

  • ProjectPaths.oauthDir: <projectDir>/oauth/ を追加
  • FileSystemOAuthStore (新規): read/write/delete/list、TOCTOU フリー write (tmp を 0o600 で open → rename)
  • clearProject: oauth/ も削除対象に追加 (removedOAuthTokens 追跡)

codex セカンドオピニオン対応 (push 直前 review)

重要度 対応
HIGH 2 TOCTOU 解消 (tmp を 0o600 で open → rename) / clearProjectoauth/ 削除
MEDIUM 3 acquiredAt/expiresAtz.iso.datetime() / fs.mkdir 二重を解消 / ADR の Confluence scope 例を Jira 系に修正
LOW 3 mcpServerId regex のエラーメッセージ統一 / it.skipIf で Windows skip / OAuthProviderConfig 型注釈

Test plan

  • pnpm typecheck (4/4 PASS)
  • pnpm test (core 99 + storage 95 + ai-engine 213 + frontend 273 = 680 PASS)
  • pnpm lint (exit 0)
  • PR-E2 で OAuthClient を実装するときに OAuthProviderConfig interface が使えるか確認 (本 PR 範囲外)

Related

Summary by CodeRabbit

リリースノート

  • New Features

    • OAuth 2.1 認証フローを外部 MCP サーバー(Atlassian など)向けに実装しました。トークンの安全な保管と自動更新に対応しています。
  • Infrastructure

    • OAuth トークン保存の仕組みとスキーマ定義を追加しました。

ADR-0011 (Tally 側で OAuth 2.1 フローを管理する) の実装段階 1。
issue #28 への対応として、外部 MCP server の access token を Tally で
保持して SDK の \`headers\` に inject するための型定義 / 永続化層を作る。

PR-E2 (OAuthClient + LoopbackCallbackServer) 以降の前提となる schema と
storage を切り出した最小単位の PR。本 PR だけでは OAuth フローは動かない。

## docs/adr/0011-tally-managed-oauth-flow.md

設計判断と PR 分割計画 (E1 〜 E5) を記録。後方互換は不要 (PoC 段階)、
oauth フィールドは PR-E1 では optional・PR-E4 で旧 auth_request 経路の
削除と同時に required 化、token は MVP では平文 YAML + 0o600 で保持
(暗号化は ADR-0012 で別途検討) と明記。

## packages/core

- McpOAuthConfigSchema (新規): McpServerConfig に紐付く client 設定。
  clientId 必須、scopes optional (kind ごとの default 適用)。
- McpServerConfigSchema.oauth: optional で追加。PR-E4 で required 化予定。
- McpOAuthTokenSchema (新規): token store の YAML スキーマ。accessToken
  必須、refreshToken / expiresAt / scopes optional、tokenType は default
  'Bearer'。mcpServerId は McpServerIdRegex で charset 制限。
- src/oauth/atlassian.ts (新規): Atlassian Cloud OAuth 2.1 endpoint
  (authorize / token / audience / default scopes)。kind ごとの registry
  として OAUTH_REGISTRY も export。
- index.ts: 上記の公開 export を追加。

## packages/storage

- ProjectPaths.oauthDir (project-dir.ts): \`<projectDir>/oauth/\` を追加。
- FileSystemOAuthStore (oauth-store.ts, 新規): read/write/delete/list の
  4 操作。1 server 1 ファイルで個別 IO が O(1)。write は mode 0o600 を
  強制 (Windows では chmod no-op で許容)。read は YamlValidationError
  のみ warn + null、FS 系エラーは再 throw (silent fail で原因隠蔽を防ぐ)。
- index.ts: OAuthStore type と FileSystemOAuthStore class を export。

## テスト

- core/oauth/atlassian.test.ts: endpoint と registry の存在確認、
  default scopes に offline_access (refresh_token に必須) が含まれる確認。
- core/schema.test.ts: McpServerConfigSchema.oauth の round-trip / 空
  clientId reject、McpOAuthTokenSchema の最小フィールド + default
  tokenType / mcpServerId charset / accessToken 空チェック。
- storage/oauth-store.test.ts: read/write/list/delete の round-trip、
  ファイル mode 0o600 確認、破損 YAML の warn + null、複数 server の
  list がソート順、未存在 delete が no-op。

pnpm typecheck 4/4 PASS / pnpm test core 100 + storage 95 全 PASS /
pnpm lint exit 0。
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 2, 2026

Warning

Rate limit exceeded

@shomatan has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 48 minutes and 11 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 776209cc-42e8-4207-974e-6443fa48b9f1

📥 Commits

Reviewing files that changed from the base of the PR and between 5c1b9b1 and ac593a1.

📒 Files selected for processing (6)
  • packages/core/src/index.ts
  • packages/core/src/schema.ts
  • packages/storage/src/clear-project.test.ts
  • packages/storage/src/clear-project.ts
  • packages/storage/src/oauth-store.test.ts
  • packages/storage/src/oauth-store.ts
📝 Walkthrough

Walkthrough

このプルリクエストは、外部 MCP サーバー向けの OAuth 2.1 トークン管理インフラストラクチャを導入します。OAuth プロバイダー設定、トークンストレージシステム、スキーマ拡張、および関連テストを追加し、ADR-0011 で定義された Tally 管理型 OAuth フローの実装基盤を構築します。

Changes

OAuth プロバイダー設定とスキーマ基盤

Layer / File(s) Summary
OAuth プロバイダーレジストリ
packages/core/src/oauth/atlassian.ts, packages/core/src/oauth/atlassian.test.ts
OAuthProviderConfig インターフェースを定義し、Atlassian Cloud OAuth(authorization/token endpoint、audience、デフォルトスコープ)を登録。OAUTH_REGISTRY で kind ごとのプロバイダー設定をマッピング。
OAuth スキーマ定義
packages/core/src/schema.ts, packages/core/src/schema.test.ts
McpOAuthConfigSchema(clientId、任意 scopes)と McpOAuthTokenSchema(mcpServerId、accessToken、refreshToken、acquiredAt/expiresAt、scopes、tokenType)を追加。McpServerConfigSchemaoauth フィールドを拡張。
モジュールエクスポート
packages/core/src/oauth/index.ts, packages/core/src/index.ts
OAuth プロバイダー設定、スキーマ、型を public re-export。

OAuth トークンストレージシステム

Layer / File(s) Summary
ストレージインターフェースと実装
packages/storage/src/oauth-store.ts, packages/storage/src/oauth-store.test.ts
OAuthStore インターフェース定義と FileSystemOAuthStore 実装。read/write/delete/list メソッドで YAML ベースのトークン永続化を提供。Write時に secure permissions (0o600) でファイル作成。YAML 検証エラーは log & null、その他エラーは再スロー。
ストレージエクスポート
packages/storage/src/index.ts
OAuthStore 型と FileSystemOAuthStore クラスを public export。

プロジェクトディレクトリとクリアプロセスの統合

Layer / File(s) Summary
プロジェクトパス拡張
packages/storage/src/project-dir.ts
ProjectPaths インターフェースに oauthDir: string フィールドを追加。resolveProjectPaths()<root>/oauth として計算。
クリアプロセス統合
packages/storage/src/clear-project.ts, packages/storage/src/clear-project.test.ts
clearProject() が OAuth ディレクトリを削除対象に追加。ClearProjectResultremovedOAuthTokens: number フィールドを追加し、削除数を報告。

ドキュメント

Layer / File(s) Summary
アーキテクチャ決定記録
docs/adr/0011-tally-managed-oauth-flow.md
Tally 側完結型 OAuth フロー、McpServerConfig 拡張、トークンストレージ仕様、UI/ChatRunner の削除対象、PR 分割計画(PR-E1~E5)、後方互換性の廃止方針を定義。

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PRのタイトルは「feat: ADR-0011 PR-E1 — OAuth schema + token store の土台」で、プルリクエストの主要な変更を明確に要約している。スキーマ追加とトークンストレージの実装がADR-0011のPR-E1段階の基礎的な機能追加であることが簡潔に伝わる。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/oauth-flow-tally-managed

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
Review rate limit: 0/1 reviews remaining, refill in 48 minutes and 11 seconds.

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
packages/core/src/schema.test.ts (1)

789-835: ⚡ Quick win

acquiredAt / expiresAt の不正 datetime 文字列を弾くテストが欠けている

schema.ts のコメント(line 315)には「z.string() のままだと 'not-a-date' 等が通り、PR-E2 の expiry 判定が境界バグを起こす」という Codex P2 指摘への対応として .datetime() を採用したと明記されています。ところがその検証テストがないため、将来 .datetime()z.string() に戻すリグレッションを防げません。

✅ 追加案
+  it('acquiredAt が ISO 8601 形式でないなら fail (PR-E2 expiry 判定の境界バグ防止)', () => {
+    expect(() =>
+      McpOAuthTokenSchema.parse({
+        mcpServerId: 'atlassian',
+        accessToken: 'a-tok',
+        acquiredAt: 'not-a-date',
+      }),
+    ).toThrow();
+  });
+
+  it('expiresAt が ISO 8601 形式でないなら fail', () => {
+    expect(() =>
+      McpOAuthTokenSchema.parse({
+        mcpServerId: 'atlassian',
+        accessToken: 'a-tok',
+        acquiredAt: '2026-05-02T10:00:00Z',
+        expiresAt: '2026/05/02 11:00',
+      }),
+    ).toThrow();
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/schema.test.ts` around lines 789 - 835, Add negative tests
for McpOAuthTokenSchema to ensure acquiredAt and expiresAt reject invalid
datetime strings: call McpOAuthTokenSchema.parse with acquiredAt: 'not-a-date'
(and separately with a valid acquiredAt but expiresAt: 'not-a-date') and assert
that parse throws; also include a positive check that a valid ISO datetime
(e.g., '2026-05-02T10:00:00Z') still parses to guard behavior. These tests will
prevent regressing the .datetime() validation described in schema.ts and should
reference the existing McpOAuthTokenSchema test suite.
packages/core/src/oauth/index.ts (1)

1-1: ⚡ Quick win

OAuthProviderConfig が public API として再エクスポートされていない

PR-E2 で実装される OAuthClient / LoopbackCallbackServer はこのインターフェースを引数型として使う想定ですが、oauth/index.ts および packages/core/src/index.ts に再エクスポートが無いため、下流パッケージが内部パスを直接 import する必要が生じます。

♻️ 修正案
-export { ATLASSIAN_CLOUD_OAUTH, OAUTH_REGISTRY, type OAuthKind } from './atlassian';
+export { ATLASSIAN_CLOUD_OAUTH, OAUTH_REGISTRY, type OAuthKind, type OAuthProviderConfig } from './atlassian';

さらに packages/core/src/index.ts の型エクスポート行にも追加:

-export { ATLASSIAN_CLOUD_OAUTH, OAUTH_REGISTRY, type OAuthKind } from './oauth';
+export { ATLASSIAN_CLOUD_OAUTH, OAUTH_REGISTRY, type OAuthKind, type OAuthProviderConfig } from './oauth';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/oauth/index.ts` at line 1, OAuthProviderConfig が public API
として再エクスポートされていないため下流が内部パスを直接参照してしまう問題があります。oauth のエントリで OAuthProviderConfig
をエクスポートするように (現在エクスポートしている ATLASSIAN_CLOUD_OAUTH / OAUTH_REGISTRY / OAuthKind
と同じ場所で) 追記し、さらにコアのインデックスの型エクスポート行にも OAuthProviderConfig を追加して public API
に含めてください。対象となる識別子: OAuthProviderConfig, ATLASSIAN_CLOUD_OAUTH, OAUTH_REGISTRY,
OAuthKind, およびコアの型エクスポートセクション(packages core の index 型エクスポート行)を更新してください。
packages/core/src/oauth/atlassian.ts (1)

38-42: ⚡ Quick win

OAuthKind'atlassian' ではなく string に解決される

OAUTH_REGISTRYReadonly<Record<string, OAuthProviderConfig>> という明示的な型アノテーションが付いているため、typeof OAUTH_REGISTRY の key 型は string に widening されます。その結果 OAuthKind = string となり、PR-E2 で OAuthClientkind: OAuthKind を引数に受けても、コンパイル時に無効な kind を検出できなくなります。

Record<string, ...> を使うと keyof typeofstring になり型が広がることは既知の挙動です。satisfies 演算子はアノテーションとして式の型を変えずに検証するため、以下のように修正すると OAuthKind = 'atlassian' が得られます。

♻️ 修正案
-export const OAUTH_REGISTRY: Readonly<Record<string, OAuthProviderConfig>> = {
-  atlassian: ATLASSIAN_CLOUD_OAUTH,
-};
+export const OAUTH_REGISTRY = {
+  atlassian: ATLASSIAN_CLOUD_OAUTH,
+} satisfies Record<string, OAuthProviderConfig>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/oauth/atlassian.ts` around lines 38 - 42, The
OAUTH_REGISTRY declaration is annotated as Readonly<Record<string,
OAuthProviderConfig>>, which widens its keys to string so OAuthKind becomes
string; remove the explicit broad type annotation and instead keep the object
literal and use the TypeScript "satisfies" operator (e.g. have OAUTH_REGISTRY
satisfy Readonly<Record<string, OAuthProviderConfig>>) so the property keys stay
literal and OAuthKind = keyof typeof OAUTH_REGISTRY resolves to the exact union
('atlassian'); ensure ATLASSIAN_CLOUD_OAUTH still conforms to
OAuthProviderConfig so the satisfies check passes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/schema.ts`:
- Line 312: refreshToken is defined as z.string().optional() allowing empty
strings; update its schema to require non-empty when present by changing the
validator to z.string().min(1).optional() so it mirrors accessToken's
z.string().min(1) behavior; locate the refreshToken symbol in the schema
(packages/core/src/schema.ts) and replace its definition accordingly.

In `@packages/storage/src/clear-project.ts`:
- Around line 23-27: clearProject() currently calls clearDir() which only
removes *.yaml|*.yml and so leaves tmp token files (written as .tmp.<pid> in
oauth-store.ts) behind; update the logic handling paths.oauthDir (in
clear-project.ts) to fully remove and recreate the oauth directory or otherwise
delete all files inside it regardless of extension before writing edges, e.g.
remove paths.oauthDir recursively (fs.rm(..., { recursive:true, force:true }))
and then fs.mkdir(paths.oauthDir, { recursive: true }) so any .tmp.<pid> or
other leftover files are removed; ensure references to clearDir() behavior are
preserved for other dirs or add an option to clearDir() to delete all files for
oauth specifically.

In `@packages/storage/src/oauth-store.test.ts`:
- Around line 115-123: The test currently writes an explicit token with
tokenType: 'Bearer' (via makeToken) so it cannot catch regressions in the schema
default; change the test to create a stored token that omits tokenType (e.g.,
write a YAML token file into the test project directory without a tokenType
field rather than calling makeToken({ tokenType: 'Bearer' })), then instantiate
FileSystemOAuthStore and call read('atlassian') and assert that the returned
token's tokenType equals 'Bearer' (verifying McpOAuthTokenSchema default
behavior).

In `@packages/storage/src/oauth-store.ts`:
- Around line 67-68: The tmp filename `${filePath}.tmp.${process.pid}` can
collide for concurrent writes; modify the write routine (the tmpPath/fd creation
in the write function in oauth-store.ts) to append a per-write unique suffix
(for example use crypto.randomUUID() or Date.now()+a per-process incrementing
counter or crypto.randomBytes) so each tmp file is unique per write; replace the
current tmpPath construction with
`${filePath}.tmp.${process.pid}.${uniqueSuffix}` (and keep the subsequent
fs.open/rename logic unchanged) so concurrent write() calls cannot clobber each
other.
- Around line 41-42: Validate and constrain the mcpServerId inside filePath
before using it to build a filesystem path: reject or sanitize any input
containing path separators or traversal patterns (e.g. '..' or '/'/backslash) by
applying a shared regex/schema (e.g. allow only [A-Za-z0-9._-]) or by
canonicalizing with path.basename, then use the validated value when
constructing the final path (return path.join(this.oauthDir,
`${validatedMcpServerId}.yaml`)); ensure this check lives in this class
(filePath or a small helper) and throw a clear error if validation fails so
callers cannot escape oauthDir or overwrite files outside it.

---

Nitpick comments:
In `@packages/core/src/oauth/atlassian.ts`:
- Around line 38-42: The OAUTH_REGISTRY declaration is annotated as
Readonly<Record<string, OAuthProviderConfig>>, which widens its keys to string
so OAuthKind becomes string; remove the explicit broad type annotation and
instead keep the object literal and use the TypeScript "satisfies" operator
(e.g. have OAUTH_REGISTRY satisfy Readonly<Record<string, OAuthProviderConfig>>)
so the property keys stay literal and OAuthKind = keyof typeof OAUTH_REGISTRY
resolves to the exact union ('atlassian'); ensure ATLASSIAN_CLOUD_OAUTH still
conforms to OAuthProviderConfig so the satisfies check passes.

In `@packages/core/src/oauth/index.ts`:
- Line 1: OAuthProviderConfig が public API
として再エクスポートされていないため下流が内部パスを直接参照してしまう問題があります。oauth のエントリで OAuthProviderConfig
をエクスポートするように (現在エクスポートしている ATLASSIAN_CLOUD_OAUTH / OAUTH_REGISTRY / OAuthKind
と同じ場所で) 追記し、さらにコアのインデックスの型エクスポート行にも OAuthProviderConfig を追加して public API
に含めてください。対象となる識別子: OAuthProviderConfig, ATLASSIAN_CLOUD_OAUTH, OAUTH_REGISTRY,
OAuthKind, およびコアの型エクスポートセクション(packages core の index 型エクスポート行)を更新してください。

In `@packages/core/src/schema.test.ts`:
- Around line 789-835: Add negative tests for McpOAuthTokenSchema to ensure
acquiredAt and expiresAt reject invalid datetime strings: call
McpOAuthTokenSchema.parse with acquiredAt: 'not-a-date' (and separately with a
valid acquiredAt but expiresAt: 'not-a-date') and assert that parse throws; also
include a positive check that a valid ISO datetime (e.g.,
'2026-05-02T10:00:00Z') still parses to guard behavior. These tests will prevent
regressing the .datetime() validation described in schema.ts and should
reference the existing McpOAuthTokenSchema test suite.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 768f0694-86a5-4715-92fa-ac9ecc24a082

📥 Commits

Reviewing files that changed from the base of the PR and between 3bfaac6 and 5c1b9b1.

⛔ Files ignored due to path filters (1)
  • .claude/scheduled_tasks.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • docs/adr/0011-tally-managed-oauth-flow.md
  • packages/core/src/index.ts
  • packages/core/src/oauth/atlassian.test.ts
  • packages/core/src/oauth/atlassian.ts
  • packages/core/src/oauth/index.ts
  • packages/core/src/schema.test.ts
  • packages/core/src/schema.ts
  • packages/storage/src/clear-project.test.ts
  • packages/storage/src/clear-project.ts
  • packages/storage/src/index.ts
  • packages/storage/src/oauth-store.test.ts
  • packages/storage/src/oauth-store.ts
  • packages/storage/src/project-dir.ts

Comment thread packages/core/src/schema.ts Outdated
Comment thread packages/storage/src/clear-project.ts Outdated
Comment thread packages/storage/src/oauth-store.test.ts Outdated
Comment thread packages/storage/src/oauth-store.ts
Comment thread packages/storage/src/oauth-store.ts Outdated
CodeRabbit (PR #29) 1 周目への対応。すべて Quick win。

[Minor / schema.ts:312] refreshToken に min(1) 制約を追加
provider が誤って空文字を返した場合のサイレント障害を schema で検出する。

[Major / clear-project.ts:27] oauth/ の tmp 残骸を含めて全削除
oauth-store の `*.tmp.<pid>.<uuid>` 中間ファイル (write 中断時) が clearDir
の YAML フィルタに引っかからず残留する問題を修正。clearOAuthDir helper を
新設し、ディレクトリ配下を ext 不問で全削除する。removedOAuthTokens は
YAML の数だけカウント (tmp は noise なので集計外)。

[Minor / oauth-store.test.ts:123] tokenType default 経路の test 改修
makeToken({ tokenType: 'Bearer' }) を直接 write していたため schema default
が壊れても通る test だった。tokenType を欠いた YAML を直接書き、read 時に
McpOAuthTokenSchema の default が適用されることを確認する形に変更。

[Major / oauth-store.ts:42] mcpServerId のストレージ境界検証
`path.join(oauthDir, ${mcpServerId}.yaml)` で `../etc/passwd` のような値が
渡ると oauth/ の外を読み書きできる問題があった。filePath() 内で
McpServerIdRegex (core から export) を通して validate し、違反なら明示的に
throw する自己防衛ロジックを追加。read/write/delete 全経路で path traversal
を弾くテストも追加。

[Major / oauth-store.ts:68] tmp ファイル名を per-write で unique 化
`${filePath}.tmp.${process.pid}` だと同 mcpServerId への並行 write で衝突し、
内容が混ざる + 後続 rename が ENOENT で落ちる race があった。crypto.randomUUID()
を suffix に付与して 1 書き込みごとに固有名にする。

## 副次変更

- core: McpServerIdRegex を schema.ts から export し、core/index.ts でも
  公開。storage の oauth-store がこれを使って path traversal を validate。
- clear-project.test.ts: tmp 残骸も削除されることを確認する test 追加。

pnpm typecheck 4/4 PASS / pnpm test core 99 + storage 97 + ai-engine 213
+ frontend 273 = 682 PASS / pnpm lint exit 0。
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.

1 participant