テイント (taint)

使用方法

taint オプションを有効にすると、オブジェクトや値にテイントを付与する実験的な React API が使用可能になります。この機能は、機密データが誤ってクライアントに渡されるのを防ぐのに役立ちます。有効にすると、以下の API が使用できます:

補足: このフラグを有効にすると、app ディレクトリに対して React の experimental チャンネルも有効になります。

import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    taint: true,
  },
}

export default nextConfig

警告: 機密データがクライアントに露出するのを防ぐ唯一の手段としてテイント API に依存しないでください。セキュリティに関する推奨事項を参照してください。

テイント API を使用すると、宣言的かつ明示的にサーバー-クライアント境界を越えて渡すことが許可されていないデータをマークすることで、防御的なプログラミングが可能になります。オブジェクトや値がサーバー-クライアント境界を越えて渡されようとすると、React はエラーをスローします。

この機能は以下のようなケースで役立ちます:

  • データを読み取る方法を制御できない場合
  • 自分で定義していない機密データ構造を扱わなければならない場合
  • サーバーコンポーネントのレンダリング中に機密データにアクセスする場合

データや API を設計する際は、機密データが必要とされないコンテキストに返されないようにすることを推奨します。

注意点

  • テイントは参照によってのみオブジェクトを追跡できます。オブジェクトをコピーするとテイントが解除され、API が提供するすべての保証が失われます。コピーにもテイントを付与する必要があります。
  • テイントはテイントされた値から派生したデータを追跡できません。派生した値にもテイントを付与する必要があります。
  • 値は、その参照のライフタイムがスコープ内にある間のみテイントが維持されます。詳細は experimental_taintUniqueValue のパラメータリファレンスを参照してください。

オブジェクト参照にテイントを付与

この例では、getUserDetails 関数が指定されたユーザーのデータを返します。ユーザーオブジェクト参照にテイントを付与することで、サーバー-クライアント境界を越えて渡せないようにします。例えば、UserCard がクライアントコンポーネントであると仮定します。

import { experimental_taintObjectReference } from 'react'

function getUserDetails(id: string): UserDetails {
  const user = await db.queryUserById(id)

  experimental_taintObjectReference(
    'ユーザー情報オブジェクト全体を使用しないでください。代わりに必要なフィールドのみを選択してください。',
    user
  )

  return user
}
import { experimental_taintObjectReference } from 'react'

function getUserDetails(id) {
  const user = await db.queryUserById(id)

  experimental_taintObjectReference(
    'ユーザー情報オブジェクト全体を使用しないでください。代わりに必要なフィールドのみを選択してください。',
    user
  )

  return user
}

テイントされた userDetails オブジェクトから個々のフィールドにアクセスすることは可能です。

export async function ContactPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const { id } = await params
  const userDetails = await getUserDetails(id)

  return (
    <UserCard
      firstName={userDetails.firstName}
      lastName={userDetails.lastName}
    />
  )
}
export async function ContactPage({ params }) {
  const { id } = await params
  const userDetails = await getUserDetails(id)

  return (
    <UserCard
      firstName={userDetails.firstName}
      lastName={userDetails.lastName}
    />
  )
}

しかし、オブジェクト全体をクライアントコンポーネントに渡すとエラーがスローされます。

export async function ContactPage({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const userDetails = await getUserDetails(id)

  // エラーがスローされます
  return <UserCard user={userDetails} />
}
export async function ContactPage({ params }) {
  const { id } = await params
  const userDetails = await getUserDetails(id)

  // エラーがスローされます
  return <UserCard user={userDetails} />
}

ユニークな値にテイントを付与

この例では、config.getConfigDetails を呼び出すことでサーバー設定にアクセスできます。しかし、システム設定にはクライアントに公開したくない SERVICE_API_KEY が含まれています。

config.SERVICE_API_KEY の値にテイントを付与できます。

import { experimental_taintUniqueValue } from 'react'

function getSystemConfig(): SystemConfig {
  const config = await config.getConfigDetails()

  experimental_taintUniqueValue(
    '設定トークンをクライアントに渡さないでください',
    config,
    config.SERVICE_API_KEY
  )

  return config
}
import { experimental_taintUniqueValue } from 'react'

function getSystemConfig() {
  const config = await config.getConfigDetails()

  experimental_taintUniqueValue(
    '設定トークンをクライアントに渡さないでください',
    config,
    config.SERVICE_API_KEY
  )

  return config
}

systemConfig オブジェクトの他のプロパティには引き続きアクセスできます。

export async function Dashboard() {
  const systemConfig = await getSystemConfig()

  return <ClientDashboard version={systemConfig.SERVICE_API_VERSION} />
}

しかし、SERVICE_API_KEYClientDashboard に渡すとエラーがスローされます。

export async function Dashboard() {
  const systemConfig = await getSystemConfig()
  // PR で誰かがミスをした場合
  const version = systemConfig.SERVICE_API_KEY

  return <ClientDashboard version={version} />
}

systemConfig.SERVICE_API_KEY が新しい変数に再代入されていても、クライアントコンポーネントに渡すとエラーがスローされることに注意してください。

一方、テイントされたユニークな値から派生した値はクライアントに公開されます。

export async function Dashboard() {
  const systemConfig = await getSystemConfig()
  // PR で誰かがミスをした場合
  const version = `version::${systemConfig.SERVICE_API_KEY}`

  return <ClientDashboard version={version} />
}

より良いアプローチは、getSystemConfig が返すデータから SERVICE_API_KEY を削除することです。

On this page