gdp-ts について
gdp-ts は、認可チェックを機密関数が扱う正確な値に結び付ける TypeScript ライブラリとワークフローです。名前付きの値と偽造できない証明型により、チェックの欠落や不一致をコンパイル時エラーにし、追加の lint ルールも提供します。
主な機能
- 01認可証明を名前付きの値に結び付け
- 02機密関数のシグネチャで証明を必須化
- 03証明の作成を信頼されたモジュールに限定
- 04ESLint と Oxlint のルールを提供
- 05コンパイル時の回帰チェックを支援
活用シーン
- 01認可チェックが別のリソースに適用されるのを防止
- 02認可証明の欠落をコンパイル時エラー化
- 03TypeScript コードベースに証明ベースのアクセス制御を導入
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rauchg/ gdp-ts --skill gdp-tsインストール後、Claude Code で/gdp-tsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/errors.mdlimits.mdpatterns.mdrecipe.mdwhere-to-stop.md詳細
gdp-ts
機密性の高い関数(読み取りや変更の対象に、一部の呼び出し元しかアクセスできないもの)は、正確な引数に関する 証明 を受け取ります。そのため、チェックなしで呼び出したり、別の値に関するチェックを渡したりすると、コンパイルできません。
- 値に名前を付けます。
name(viewer.id, projectId, async (user, project) => ...)を使うと、各値にコンパイル時のみ有効な名前が付けられ、コールバック内で使えます。 - 名前に関する事実を証明します。
proofs/内の小さな信頼できるモジュールがチェックを実行し、UserIsProjectAdmin<U, P>などの証明、またはnullを返します。この証明を生成できるのはそのモジュールだけです。 - 証明を要求します。 機密性の高い関数は、
project: Named<P, ProjectId>と_proof: UserIsProjectAdmin<U, P>を受け取ります。間違った証明、別の値に関する証明、生の ID、または証明なしの場合はコンパイルエラーになります。
これは定理証明器ではありません。証明がないとインシデントにつながる事実を証明し、それ以上は行わないでください(どこまで行うか)。
ワークフロー
このパターンを使うコードベースで認可を追加または変更する場合:
- ID にブランド型が付いていますか?付いていない場合は、まずブランド型を付けます。
proofs/に必要な事実を証明するものがありますか?ある場合は再利用します。なければ、ファイルを 1 つ追加します。内容は、非公開のdefineProof、エクスポートするinterface X<...> extends Proof<"X", [...]> {}、エクスポートするasync function x(...named): Promise<X<...> | null>のみです。それ以外は追加しません。- 機密性の高い関数は、名前付き引数に関する証明を受け取りますか?生の ID を受け取る場合は、シグネチャを変更します。修正すべき呼び出し箇所はコンパイラに列挙させます。
- リクエストに、処理が信頼するほかの値(Cookie 内のトークン、本文内の ID など)が含まれていますか?信頼できるモジュール内で、それらの値がアクセスを許可するとされているリソースと照合します(
TokenUnlocksUrl<H>)。リソース間やテナント間のバグは、ここで発生します。 - ユーザー以外に関する前提条件(プラン、スコープ、状態)がありますか?それらも証明にします(
PlanIncludesPasswordProtection<P>)。 - ハンドラー本体(ミドルウェアではありません)で、
name(...)を呼び出し、ポリシー関数を呼び出し、nullを適切な HTTP レスポンスに変換し、機密性の高い処理をすべてコールバック内で行います。そのブロックが多くの場所から呼び出される可能性が高い場合は、共有関数への移動を検討します(レシピのステップ 5)。 - 証明や
Named型を満たすためにasを絶対に使わないでください。必要だと感じる場合は、シグネチャが間違っているか、proofs/の外にいます。コードベースに gdp-ts の lint プリセットがまだない場合は追加します(レシピのステップ 6)。すでにある場合は、無効化するのではなく lint エラーを修正します。 - 証明関数をエクスポートしないでください。
proofs/の外でdefineProofを呼び出さないでください。name()コールバックからNamedを返したり、そのコールバックの名前に言及する証明を返したりしないでください。 - もう少しで犯しそうになった新たな誤りを、
mistakes.ts(実行されない、型チェック用のファイル)の// @ts-expect-errorの下に 1 行で追加します。これは作成できる最も低コストの回帰テストです(詳細)。 - 追加した証明関数をテストします。実行時に誤りがあり得るのはそれだけです。
レシピでは、各ステップを詳しく説明しています。
参考資料
必要なものを読んでください。
| ファイル | 読むタイミング |
|---|---|
references/recipe.md | 証明、ポリシー、機密性の高い関数を追加するとき、またはこのパターンを導入するとき。6 つのステップ:ID のブランド型化、信頼できるモジュール、共用体としてのポリシー、機密性の高い関数、ハンドラー内での命名、lint ルール。 |
references/patterns.md | 証明の再利用、強い証明から弱い証明を導く方法、証拠を保持する証明、ユーザーに関するものではない前提条件について。 |
references/errors.md | 型エラーに Named<...>、証明型、または「この名前の型が 2 つあります」と表示される場合。 |
references/limits.md | 型が保証することと保証しないこと(偽造、古い証明、古い TypeScript)の判断について。 |
references/where-to-stop.md | どこまで証明するかを決めるとき。 |
API
すべて @gdp-ts/core からエクスポートされます。
| エクスポート | 内容 |
|---|---|
Named<N, A> | コンパイル時の名前 N を持つ型 A の値です。.value で読み取れます。N は不変です。 |
name(a, k) / name(a, b, k) / name(a, b, c, k) | 1〜3 個の値に、スコープを k とする新しい名前を付けます。k の戻り値(Promise を含む)を返します。 |
Proof<Kind, About> | 名前のタプル About について、事実 Kind が成り立つことを示す証拠です。実行時には kind: Kind を持ちます。両方の型パラメーターは不変です。 |
defineProof(kind) | Prover({ kind, prove(...named) })を返します。prove は引数から About を推論します。チェックを行うモジュール内でのみ使用してください。 |
NameOf<T> | Named<N, A> の N です。必要になることはほとんどありません。 |
NamesOf<S> | Named 値のタプルの名前です。prove のシグネチャで使用されます。 |
型システムでは防げない抜け穴に対応するリントプリセット(レシピのステップ 6):
| インポート | 内容 |
|---|---|
@gdp-ts/core/lint/eslint | gdp(options?):ESLint のフラット設定エントリーです。typescript-eslint の設定の後に展開してください。 |
@gdp-ts/core/lint/oxlint | gdp(options?):Oxlint の設定です(TypeScript 7 で動作します)。 |
@gdp-ts/core/lint/plugin | カスタム設定向けのルール本体(gdp-ts/no-define-proof、no-exported-prover、no-proof-assertion、no-type-assertion、no-any)です。 |
オプション:proofs(信頼するモジュールの glob。デフォルトは **/proofs/**)、strict
(それらの外ではすべての as と any も禁止します。デフォルトは false)、
allowAssertions(strict モードの例外。デフォルトは **/lib/ids.ts)、
proofImports、files。
このライブラリで証明型をアサートする箇所は prove の内部だけなので、これを使うコードでアサーションは不要です。実行時のフットプリントは、defineProof の呼び出しごとに 1 つ作られる凍結済みの { kind } オブジェクトです。
prove は毎回同じオブジェクトを返し、name は各値を凍結済みの { value } でラップします。