跳到主要内容

taint

用法

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

提示:激活此标志还会为 app 目录启用 React experimental 通道。

next.config.ts
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_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 被重新分配给新变量,将其传递给客户端组件仍然会抛出错误。

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

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

return <ClientDashboard version={version} />
}

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