secret: true と宣言された入力は、FlowSpec、Journal、ログ行、公開された Pack には一切現れません。Flow の中では不透明な参照としてのみ移動し、実際のバイト列として具体化されるのは一度だけです。それは、secrets capability を宣言した単一の Tool の内部で、実行時に、worker 上で起きます。コンパイル時の taint check は、Run が開始する前にこの境界が保たれていることを証明します。
このページでは一貫して1つの例を使います。daily-price-watch Flow です。ブラウザー Tool search-price@1.2.0 が価格を検索し、transform 手順がしきい値を超えたかどうかを判断し、条件付きの http.post がシークレットの webhook URL へ通知します。
値ではなく境界
入力スロットは、Tool または Flow の contract 上でsecret: true と宣言されます。InputDecl contract では、secret は必須の boolean です。つまり、すべてのスロットが境界の内側にあるのか外側にあるのかを明示的に決めます。
secret: true の入力は次のように扱われます。
- FlowSpec、Journal、ログ行、公開された Pack には一切現れません。
secretscapability を持つ Tool が、注入されたSecretsprovider を通じて解決する瞬間まで、参照、つまり\{ slot, refers_to \}としてのみ運ばれます。- 漏えいし得るすべての場所(run inspector、構造化ログ、エラーフレーム)で
maskInputsによってマスクされます。
${{ secret.WEBHOOK_URL }} と書きます。その値は、作成された Flow、コンパイル済み FlowSpec、Run の記録のどこにも存在しません。secrets capability を宣言した手順の内部でのみ、実行時にだけ具体化されます。
secrets capability は、ToolContract 上で宣言される effect の1つです(net、fs、browser、clock、random、env、spawn、secrets)。すべての capability と同じく、Rider は宣言された effect だけを注入し、それ以上は注入しません。secrets を列挙していない Tool には Secrets provider が渡されないため、仮に参照がそこへ到達しても、その Tool には参照を解決する手段がありません。
taint rule(静的に強制)
secret. 参照は不透明です。コンパイラーはそれを汚染されたトークンとして扱います。このトークンは、Tool 入力への引数として、かつその Tool が secrets capability を宣言している場合に限り、全体が未変換のままバインドできます。
このルールは純粋に構造的なので、機械的に判定できます。シークレット参照は、secrets 対応 Tool 上のスロットの全体をバインドしているか、secret_leak であるかのどちらかです。第三のケースはありません。
| 記述 | 判定 | 理由 |
|---|---|---|
http.post(secrets を宣言)上の url: ${{ secret.WEBHOOK_URL }} | 許可 | 全体が未変換のまま、secrets 対応 Tool へ渡される |
secrets なしの Tool 上の url: ${{ secret.WEBHOOK_URL }} | secret_leak | 対象 Tool はシークレットを解決できない |
auth: "Bearer " + secret.TOKEN | secret_leak | 演算子を通過する |
host: ${{ secret.WEBHOOK_URL.host }} | secret_leak | シークレット上のメンバーアクセス |
url: ${{ default(secret.WEBHOOK_URL, x) }} | secret_leak | 組み込み関数を通過する |
output: ${{ secret.WEBHOOK_URL }} | secret_leak | シークレットは output に現れてはならない |
漏えいさせずに effect を記録する
Rider は、Run を hermetic にリプレイできるよう、すべての外部 effect を Journal へ記録します。リクエストとレスポンスは content-addressed で保存されます。ここで明らかな緊張が生じます。シークレットがリクエストの一部である場合、それを記録してもなぜシークレットが漏えいしないのでしょうか。 taint rule がこの緊張を解消します。シークレットは解決する Tool の内部でのみ具体化されるため、その Tool こそが effect を記録する層です。したがって、どのバイト列がシークレット由来なのかを正確に知っています。recorder はそれらをredactします。シークレットを含むヘッダー、クエリパラメーター、body フィールドは、値そのものではなく、安定したプレースホルダー\{secret:<slot>\} として journal されます。
解決
daily-price-watch の
notify 手順が実行されます。http.post が secrets を宣言しているため、Rider は Secrets provider を注入し、Tool は WEBHOOK_URL を実際の URL へ解決します。これはメモリ上で、Tool 本体の内部でのみ行われます。記録、redact済み
recorder は、シークレット由来の URL を安定したプレースホルダー
\{secret:WEBHOOK_URL\} に置き換えてリクエストを journal します。記録されたレスポンスは、redact 済みのリクエスト形状と冪等性キーによって key 付けされます。secrets が各 Run で新たに解決され、決して記録されない唯一の capability として示されています。一方、net、clock、random、fs、browser はすべて、リプレイ時に Journal によって裏付けられます。シークレットは意図的な例外です。決して永続化されません。
大きな body やバイナリ body はインラインではなく、blob store 内に digest で保存され、Journal は digest だけを保持します。redaction は、何かが Journal や blob store へ到達する前に行われます。そのため、payload のサイズにかかわらず、どちらもシークレット由来のバイト列を保持しません。
各 Run で新たに再解決される
シークレットは永続的なものに焼き込まれることがないため、ローテーションは透過的です。ローテーションされたシークレットは、次の Run で単に新しい値へ解決されます。再ビルド、再デプロイ、再 pin は不要です。シークレットはそもそも FlowSpec の一部ではなかったため、シークレットがローテーションされても FlowSpec digest は変わりません。 この鮮度のルールは、Run が保存済みの値に手を伸ばし得る次の2つの場所にも及びます。- 再開。 クラッシュ、デプロイ、
SIGTERMによって中断された Run は、Journal から再開されます。完了済みの手順は記録済みの output を返し、記録済みの effect は記録済みの結果を返すため、外部のものが再度発火することはありません。シークレットは唯一の例外です。シークレットは Journal から読み取られることはなく、再開時に live で再解決されます。そもそも Journal に書き込まれていないためです。 - Replay
--rerun。 Hermetic なbrowserflow replayとbrowserflow replay --verifyには、シークレットがまったく不要です(記録されたレスポンスが Journal から返されます)。opt-in のbrowserflow replay <run-id> --rerunは effect を live で再実行します。つまりhttp.postが実際にもう一度発火します。そしてこのモードでは、関連するシークレットが実行時に再解決され、Journal から読み取られることはありません。
サービスのシークレットブローカー
インフラストラクチャがない場合、RunDeps 内の Secrets dependency は launcher が構築したものになります。たとえば、ローカルの env-backed provider です。任意のコントロールプレーンは、管理されたシークレットブローカーを追加します。これにより、FlowSpec、Journal、ログにシークレットを保存することなく、fleet 全体で同じ境界の不変条件を保ちます。
仕組みはエンジンと完全に一致します。Flow の作成者は secret.WEBHOOK_URL を参照します。plane の Secrets provider は値をworker 上で、実行時に解決し、secrets capability を宣言した手順にだけ注入します。コントロールプレーンは Run をスケジュールし、観測しますが、実行はしません。そのため、シークレット値が解決済みバイト列として API tier を通過することはありません。
| プロパティ | 振る舞い |
|---|---|
| 解決される場所 | worker 上、実行時。コントロールプレーン API 内では決して解決されない |
| 注入先 | secrets capability を宣言した Tool の手順のみ |
| Backend | Pluggable: plane 自身の暗号化 store、Vault、クラウド secret manager |
| ローテーション | 透過的。各 Run で再解決され、焼き込まれることはない |
| 露出 | RBAC role にかかわらず、シークレットの値は run inspector に表示されない |
関連
secret_leak diagnostic
taint check が拒否する
secret_leak のすべての形を含む、完全な diagnostic catalog です。シークレットブローカー
worker 上での管理された pluggable なシークレット解決です。Vault、クラウド manager、または plane の store を利用できます。
実行とリプレイ
effect がどのように記録・redact されるか、そして hermetic リプレイにシークレットが不要な理由を説明します。