commit
d1b0ddee4e
@ -6,6 +6,13 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
## [0.18.1] - 2026-02-18
|
||||
|
||||
### Added
|
||||
|
||||
- Added multi-tenant data isolation section and authorization-resolver consistency code examples to security knowledge
|
||||
- Added "prefer project scripts" rule to coding policy — detects direct tool invocation (e.g., `npx vitest`) when equivalent npm scripts exist
|
||||
|
||||
## [0.18.0] - 2026-02-17
|
||||
|
||||
### Added
|
||||
|
||||
@ -148,6 +148,61 @@ if (!safePath.startsWith(path.resolve(baseDir))) {
|
||||
- Resource exhaustion attack possibility → Warning
|
||||
- Infinite loop possibility → REJECT
|
||||
|
||||
## Multi-Tenant Data Isolation
|
||||
|
||||
Prevent data access across tenant boundaries. Authorization (who can operate) and scoping (which tenant's data) are separate concerns.
|
||||
|
||||
| Criteria | Verdict |
|
||||
|----------|---------|
|
||||
| Reads are tenant-scoped but writes are not | REJECT |
|
||||
| Write operations use client-provided tenant ID | REJECT |
|
||||
| Endpoint using tenant resolver has no authorization control | REJECT |
|
||||
| Some paths in role-based branching don't account for tenant resolution | REJECT |
|
||||
|
||||
### Read-Write Consistency
|
||||
|
||||
Apply tenant scoping to both reads and writes. Scoping only one side creates a state where data cannot be viewed but can be modified.
|
||||
|
||||
When adding a tenant filter to reads, always add tenant verification to corresponding writes.
|
||||
|
||||
### Write-Side Tenant Verification
|
||||
|
||||
For write operations, use the tenant ID resolved from the authenticated user, not from the request body.
|
||||
|
||||
```kotlin
|
||||
// NG - Trusting client-provided tenant ID
|
||||
fun create(request: CreateRequest) {
|
||||
service.create(request.tenantId, request.data)
|
||||
}
|
||||
|
||||
// OK - Resolve tenant from authentication
|
||||
fun create(request: CreateRequest) {
|
||||
val tenantId = tenantResolver.resolve()
|
||||
service.create(tenantId, request.data)
|
||||
}
|
||||
```
|
||||
|
||||
### Authorization-Resolver Alignment
|
||||
|
||||
When a tenant resolver assumes a specific role (e.g., staff), the endpoint must have corresponding authorization controls. Without authorization, unexpected roles can access the endpoint and cause the resolver to fail.
|
||||
|
||||
```kotlin
|
||||
// NG - Resolver assumes STAFF but no authorization control
|
||||
fun getSettings(): SettingsResponse {
|
||||
val tenantId = tenantResolver.resolve() // Fails for non-STAFF
|
||||
return settingsService.getByTenant(tenantId)
|
||||
}
|
||||
|
||||
// OK - Authorization ensures correct role
|
||||
@Authorized(roles = ["STAFF"])
|
||||
fun getSettings(): SettingsResponse {
|
||||
val tenantId = tenantResolver.resolve()
|
||||
return settingsService.getByTenant(tenantId)
|
||||
}
|
||||
```
|
||||
|
||||
For endpoints with role-based branching, verify that tenant resolution succeeds on all paths.
|
||||
|
||||
## OWASP Top 10 Checklist
|
||||
|
||||
| Category | Check Items |
|
||||
|
||||
@ -13,6 +13,7 @@ Prioritize correctness over speed, and code accuracy over ease of implementation
|
||||
| File size | ~300 lines as a guideline. Be flexible depending on the task |
|
||||
| Boy Scout | Leave touched areas a little better than you found them |
|
||||
| Fail Fast | Detect errors early. Never swallow them |
|
||||
| Project scripts first | Use project-defined scripts for tool execution. Direct invocation is a last resort |
|
||||
|
||||
## No Fallbacks or Default Arguments
|
||||
|
||||
@ -288,3 +289,4 @@ function formatPercentage(value: number): string { ... }
|
||||
- **Internal implementation exported from public API** - Only export domain-level functions and types. Do not export infrastructure functions or internal classes
|
||||
- **Replaced code surviving after refactoring** - Remove replaced code and exports. Do not keep unless explicitly told to
|
||||
- **Workarounds that bypass safety mechanisms** - If the root fix is correct, no additional bypass is needed
|
||||
- **Direct tool execution bypassing project scripts** - `npx tool` and similar bypass the lockfile, causing version mismatches. Look for project-defined scripts (npm scripts, Makefile, etc.) first. Only consider direct execution when no script exists
|
||||
|
||||
@ -148,6 +148,61 @@ if (!safePath.startsWith(path.resolve(baseDir))) {
|
||||
- リソース枯渇攻撃の可能性 → 警告
|
||||
- 無限ループの可能性 → REJECT
|
||||
|
||||
## マルチテナントデータ分離
|
||||
|
||||
テナント境界を超えたデータアクセスを防ぐ。認可(誰が操作できるか)とスコーピング(どのテナントのデータか)は別の関心事。
|
||||
|
||||
| 基準 | 判定 |
|
||||
|------|------|
|
||||
| 読み取りはテナントスコープだが書き込みはスコープなし | REJECT |
|
||||
| 書き込み操作でクライアント提供のテナントIDを使用 | REJECT |
|
||||
| テナントリゾルバーを使うエンドポイントに認可制御がない | REJECT |
|
||||
| ロール分岐の一部パスでテナント解決が未考慮 | REJECT |
|
||||
|
||||
### 読み書きの一貫性
|
||||
|
||||
テナントスコーピングは読み取りと書き込みの両方に適用する。片方だけでは、参照できないが変更できる状態が生まれる。
|
||||
|
||||
読み取りにテナントフィルタを追加したら、対応する書き込みも必ずテナント検証する。
|
||||
|
||||
### 書き込みのテナント検証
|
||||
|
||||
書き込み操作では、リクエストボディのテナントIDではなく認証済みユーザーから解決したテナントIDを使う。
|
||||
|
||||
```kotlin
|
||||
// NG - クライアント提供のテナントIDを信頼
|
||||
fun create(request: CreateRequest) {
|
||||
service.create(request.tenantId, request.data)
|
||||
}
|
||||
|
||||
// OK - 認証情報からテナントを解決
|
||||
fun create(request: CreateRequest) {
|
||||
val tenantId = tenantResolver.resolve()
|
||||
service.create(tenantId, request.data)
|
||||
}
|
||||
```
|
||||
|
||||
### 認可とリゾルバーの整合性
|
||||
|
||||
テナントリゾルバーが特定ロール(例: スタッフ)を前提とする場合、エンドポイントに対応する認可制御が必要。認可なしだと、前提外のロールがアクセスしてリゾルバーが失敗する。
|
||||
|
||||
```kotlin
|
||||
// NG - リゾルバーが STAFF を前提とするが認可制御なし
|
||||
fun getSettings(): SettingsResponse {
|
||||
val tenantId = tenantResolver.resolve() // STAFF 以外で失敗
|
||||
return settingsService.getByTenant(tenantId)
|
||||
}
|
||||
|
||||
// OK - 認可制御でロールを保証
|
||||
@Authorized(roles = ["STAFF"])
|
||||
fun getSettings(): SettingsResponse {
|
||||
val tenantId = tenantResolver.resolve()
|
||||
return settingsService.getByTenant(tenantId)
|
||||
}
|
||||
```
|
||||
|
||||
ロール分岐があるエンドポイントでは、全パスでテナント解決が成功するか検証する。
|
||||
|
||||
## OWASP Top 10 チェックリスト
|
||||
|
||||
| カテゴリ | 確認事項 |
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
| ファイルサイズ | 目安として300行。タスクに応じて柔軟に |
|
||||
| ボーイスカウト | 触った箇所は少し改善して去る |
|
||||
| Fail Fast | エラーは早期に検出。握りつぶさない |
|
||||
| プロジェクトスクリプト優先 | ツール実行はプロジェクト定義のスクリプトを使う。直接実行は最後の手段 |
|
||||
|
||||
## フォールバック・デフォルト引数の禁止
|
||||
|
||||
@ -288,3 +289,4 @@ function formatPercentage(value: number): string { ... }
|
||||
- **内部実装のパブリック API エクスポート** - 公開するのはドメイン操作の関数・型のみ。インフラ層の関数や内部クラスをエクスポートしない
|
||||
- **リファクタリング後の旧コード残存** - 置き換えたコード・エクスポートは削除する。明示的に残すよう指示されない限り残さない
|
||||
- **安全機構を迂回するワークアラウンド** - 根本修正が正しいなら追加の迂回は不要
|
||||
- **プロジェクトスクリプトを迂回するツール直接実行** - `npx tool` 等の直接実行は lockfile を迂回しバージョン不一致を起こす。プロジェクトが定義したスクリプト(npm scripts, Makefile 等)を探して使う。見つからない場合のみ直接実行を検討する
|
||||
|
||||
@ -6,6 +6,13 @@
|
||||
|
||||
フォーマットは [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) に基づいています。
|
||||
|
||||
## [0.18.1] - 2026-02-18
|
||||
|
||||
### Added
|
||||
|
||||
- セキュリティナレッジにマルチテナントデータ分離セクションと認可・リゾルバー整合性のコード例を追加
|
||||
- コーディングポリシーに「プロジェクトスクリプト優先」ルールを追加 — npm スクリプトが存在するのに直接ツール呼び出し(例: `npx vitest`)を検出
|
||||
|
||||
## [0.18.0] - 2026-02-17
|
||||
|
||||
### Added
|
||||
|
||||
139
package-lock.json
generated
139
package-lock.json
generated
@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "takt",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "takt",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.37",
|
||||
"@openai/codex-sdk": "^0.98.0",
|
||||
"@openai/codex-sdk": "^0.103.0",
|
||||
"@opencode-ai/sdk": "^1.1.53",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
@ -928,15 +928,140 @@
|
||||
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@openai/codex-sdk": {
|
||||
"version": "0.98.0",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.98.0.tgz",
|
||||
"integrity": "sha512-TbPgrBpuSNMJyOXys0HNsh6UoP5VIHu1fVh2KDdACi5XyB0vuPtzBZC+qOsxHz7WXEQPFlomPLyxS6JnE5Okmg==",
|
||||
"node_modules/@openai/codex": {
|
||||
"version": "0.103.0",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0.tgz",
|
||||
"integrity": "sha512-kf7sytd/2mMUKYK8eKgNrwujgNjWrfWFMFfRCOVIFBeByQXmtxe2giVEo44paKDapaS7dQnxqwcUY2OOYrdfWA==",
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"codex": "bin/codex.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@openai/codex-darwin-arm64": "npm:@openai/codex@0.103.0-darwin-arm64",
|
||||
"@openai/codex-darwin-x64": "npm:@openai/codex@0.103.0-darwin-x64",
|
||||
"@openai/codex-linux-arm64": "npm:@openai/codex@0.103.0-linux-arm64",
|
||||
"@openai/codex-linux-x64": "npm:@openai/codex@0.103.0-linux-x64",
|
||||
"@openai/codex-win32-arm64": "npm:@openai/codex@0.103.0-win32-arm64",
|
||||
"@openai/codex-win32-x64": "npm:@openai/codex@0.103.0-win32-x64"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-darwin-arm64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.103.0-darwin-arm64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-darwin-arm64.tgz",
|
||||
"integrity": "sha512-TGMMiB/A8CxG7ghhimEBUCk8nTEXef9cCSh+wUuq3dizAS/PuuHHf6m8LUeZCtkl/XMUxxDqEv3+i/jcB0Jo4A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-darwin-x64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.103.0-darwin-x64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-darwin-x64.tgz",
|
||||
"integrity": "sha512-1aJnXu6dYsu25fBX+76I150gh1BaKoni9wq6QWEPQ3x+FDqsCgGWG5G6G0v4uVMvTgXB0A6IhwPj88ZYi0BsWw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-linux-arm64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.103.0-linux-arm64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-linux-arm64.tgz",
|
||||
"integrity": "sha512-LdrBhfhV2ZiJBof5h1zxKgmEdt2vm5m8/xEtZShiqSDkgfsTUHNoOAb0WmgITF8+4kyWZtpMjBLw3XjkmNYQJw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-linux-x64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.103.0-linux-x64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-linux-x64.tgz",
|
||||
"integrity": "sha512-wRjcldkssdCUFQ/YoHRAbgQhszhGyt14Qa/V5ouQp4i3j5dPItVwl/y64u2Fe6fc3OAJkw6W3MJCSO1F323y3g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-sdk": {
|
||||
"version": "0.103.0",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex-sdk/-/codex-sdk-0.103.0.tgz",
|
||||
"integrity": "sha512-wMuYUIs5ajZsE2mnacYQKubt61cykMCAyNhPHBINpf9AJkiuvpAHtuz7npXzk9z4wmfqXPMXIQ3x/v1QMmd0cQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@openai/codex": "0.103.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-win32-arm64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.103.0-win32-arm64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-win32-arm64.tgz",
|
||||
"integrity": "sha512-d5yXLt9m4/3Wf3hiUcygr7voPXgLrvTgbtoYiAqZl/rtoNVRhChjt09EJKRlFQusq3aUGyQduqTLND2uF5dwBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@openai/codex-win32-x64": {
|
||||
"name": "@openai/codex",
|
||||
"version": "0.103.0-win32-x64",
|
||||
"resolved": "https://registry.npmjs.org/@openai/codex/-/codex-0.103.0-win32-x64.tgz",
|
||||
"integrity": "sha512-WtR5y3KeaGjLeYWQnRhMYBptm+eRMvwVlHJpIlFSw7DzD1idvL6ALZ4+kBL1rXBZMJzzpXomax7vMNTlXFjCOg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@opencode-ai/sdk": {
|
||||
"version": "1.1.53",
|
||||
"resolved": "https://registry.npmjs.org/@opencode-ai/sdk/-/sdk-1.1.53.tgz",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "takt",
|
||||
"version": "0.18.0",
|
||||
"version": "0.18.1",
|
||||
"description": "TAKT: TAKT Agent Koordination Topology - AI Agent Piece Orchestration",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
@ -60,7 +60,7 @@
|
||||
],
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.37",
|
||||
"@openai/codex-sdk": "^0.98.0",
|
||||
"@openai/codex-sdk": "^0.103.0",
|
||||
"@opencode-ai/sdk": "^1.1.53",
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user