Menu

taint

用法

taint 选项启用对实验性 React API 的支持,用于污染标记对象和值。此功能有助于防止敏感数据意外传递到客户端。启用后,你可以使用:

值得注意的是:激活此标志还会为 app 目录启用 React experimental 频道。

next.config.ts
TypeScript
import type { NextConfig } from 'next'
 
const nextConfig: NextConfig = {
  experimental: {
    taint: true,
  },
}
 
export default nextConfig

警告: 不要仅依赖 taint API 作为防止敏感数据暴露给客户端的唯一机制。请查看我们的安全建议

taint API 允许你通过声明式地、显式地标记不允许通过服务器-客户端边界传递的数据来进行防御。当对象或值通过服务器-客户端边界传递时,React 会抛出错误。

这在以下情况下很有帮助:

  • 读取数据的方法不在你的控制范围内
  • 你必须处理非你定义的敏感数据结构
  • 在 Server Component 渲染期间访问敏感数据

建议对你的数据和 API 进行建模,使敏感数据不会返回到不需要它的上下文中。

注意事项

  • 污染标记只能通过引用跟踪对象。复制对象会创建一个未被污染标记的版本,从而失去 API 提供的所有保证。你需要对副本进行污染标记。
  • 污染标记无法跟踪从已污染标记值派生的数据。你还需要对派生值进行污染标记。
  • 值在其生命周期引用处于作用域内时会被污染标记。有关更多信息,请参阅 experimental_taintUniqueValue 参数参考

示例

污染标记对象引用

在这种情况下,getUserDetails 函数返回关于给定用户的数据。我们对用户对象引用进行污染标记,使其无法跨越服务器-客户端边界。例如,假设 UserCard 是一个 Client Component。

import { experimental_taintObjectReference } from 'react'
 
function getUserDetails(id: string): UserDetails {
  const user = await db.queryUserById(id)
 
  experimental_taintObjectReference(
    'Do not use the entire user info object. Instead, select only the fields you need.',
    user
  )
 
  return user
}
import { experimental_taintObjectReference } from 'react'
 
function getUserDetails(id) {
  const user = await db.queryUserById(id)
 
  experimental_taintObjectReference(
    'Do not use the entire user info object. Instead, select only the fields you need.',
    user
  )
 
  return user
}

我们仍然可以访问已污染标记的 userDetails 对象的各个字段。

app/contact/page.tsx
TypeScript
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}
    />
  )
}

现在,将整个对象传递给 Client Component 将抛出错误。

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(
    'Do not pass configuration tokens to the client',
    config,
    config.SERVICE_API_KEY
  )
 
  return config
}
import { experimental_taintUniqueValue } from 'react'
 
function getSystemConfig() {
  const config = await config.getConfigDetails()
 
  experimental_taintUniqueValue(
    'Do not pass configuration tokens to the client',
    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_KEY 传递给 ClientDashboard 会抛出错误。

export async function Dashboard() {
  const systemConfig = await getSystemConfig()
  // 有人在 PR 中犯了错误
  const version = systemConfig.SERVICE_API_KEY
 
  return <ClientDashboard version={version} />
}

请注意,即使 systemConfig.SERVICE_API_KEY 被重新赋值给一个新变量,将其传递给 Client Component 仍然会抛出错误。

然而,从已污染标记的唯一值派生的值将暴露给客户端。

export async function Dashboard() {
  const systemConfig = await getSystemConfig()
  // 有人在 PR 中犯了错误
  const version = `version::${systemConfig.SERVICE_API_KEY}`
 
  return <ClientDashboard version={version} />
}

更好的方法是从 getSystemConfig 返回的数据中移除 SERVICE_API_KEY