experimental_taintUniqueValue - This feature is available in the latest Experimental version of React

Experimental Feature

此 API 属于实验性功能,尚未在稳定版本的 React 中提供。

你可以通过将 React 包升级到最新的实验版本来尝试:

🌐 You can try it by upgrading React packages to the most recent experimental version:

  • react@experimental
  • react-dom@experimental
  • eslint-plugin-react-hooks@experimental

React 的实验版本可能包含错误。不要在生产环境中使用它们。

🌐 Experimental versions of React may contain bugs. Don’t use them in production.

此 API 仅在 React 服务器组件 内可用。

🌐 This API is only available inside React Server Components.

taintUniqueValue 让你防止将唯一值传递给客户端组件,例如密码、密钥或令牌。

taintUniqueValue(errMessage, lifetime, value)

要防止传递包含敏感数据的对象,请参阅 taintObjectReference

🌐 To prevent passing an object containing sensitive data, see taintObjectReference.


参考

🌐 Reference

taintUniqueValue(message, lifetime, value)

使用密码、令牌、密钥或哈希调用 taintUniqueValue,将其注册到 React 中,作为不应直接传递给客户端的内容:

🌐 Call taintUniqueValue with a password, token, key or hash to register it with React as something that should not be allowed to be passed to the Client as is:

import {experimental_taintUniqueValue} from 'react';

experimental_taintUniqueValue(
'Do not pass secret keys to the client.',
process,
process.env.SECRET_KEY
);

查看更多示例。

参数

🌐 Parameters

  • message:如果将 value 传递给客户端组件时要显示的消息。 如果将 value 传递给客户端组件时,将会作为抛出的错误的一部分显示此消息。
  • lifetime:任何指示 value 应该被污染多长时间的对象。在此对象仍然存在期间,value 将被阻止发送到任何客户端组件。例如,传递 globalThis 会阻止该值在应用的整个生命周期内使用。lifetime 通常是一个其属性包含 value 的对象。
  • value:一个字符串、bigint 或 TypedArray。value 必须是具有高熵的唯一字符或字节序列,例如加密令牌、私钥、哈希或一个长密码。value 将被阻止发送到任何客户端组件。

返回

🌐 Returns

experimental_taintUniqueValue 返回 undefined

注意事项

🌐 Caveats

  • 从受污染的值派生新值可能会破坏污染保护。由将受污染值转换为大写、将受污染字符串值连接成更大的字符串、将受污染值转换为 base64、对受污染值进行子串操作以及其他类似的转换所创建的新值,除非你显式地对这些新创建的值调用 taintUniqueValue,否则不会被标记为受污染。
  • 不要使用 taintUniqueValue 来保护低熵值,例如 PIN 码或电话号码。如果请求中的任何值受到攻击者控制,他们可以通过枚举秘密的所有可能值来推断出哪个值被污染。

用法

🌐 Usage

防止令牌传递给客户端组件

🌐 Prevent a token from being passed to Client Components

为了确保密码、会话令牌或其他唯一值等敏感信息不会意外传递给客户端组件,taintUniqueValue 函数提供了一层保护。当一个值受到污染时,任何尝试将其传递给客户端组件的行为都会导致错误。

🌐 To ensure that sensitive information such as passwords, session tokens, or other unique values do not inadvertently get passed to Client Components, the taintUniqueValue function provides a layer of protection. When a value is tainted, any attempt to pass it to a Client Component will result in an error.

lifetime 参数定义了值保持污染状态的持续时间。对于应该无限期保持污染的值,像 globalThisprocess 这样的对象可以用作 lifetime 参数。这些对象的生命周期贯穿应用的整个执行期间。

🌐 The lifetime argument defines the duration for which the value remains tainted. For values that should remain tainted indefinitely, objects like globalThis or process can serve as the lifetime argument. These objects have a lifespan that spans the entire duration of your app’s execution.

import {experimental_taintUniqueValue} from 'react';

experimental_taintUniqueValue(
'Do not pass a user password to the client.',
globalThis,
process.env.SECRET_KEY
);

如果被污染值的生命周期与某个对象绑定,lifetime 应该是封装该值的对象。这确保了被污染的值在封装对象的生命周期内保持受保护状态。

🌐 If the tainted value’s lifespan is tied to a object, the lifetime should be the object that encapsulates the value. This ensures the tainted value remains protected for the lifetime of the encapsulating object.

import {experimental_taintUniqueValue} from 'react';

export async function getUser(id) {
const user = await db`SELECT * FROM users WHERE id = ${id}`;
experimental_taintUniqueValue(
'Do not pass a user session token to the client.',
user,
user.session.token
);
return user;
}

在此示例中,user 对象充当 lifetime 参数。如果此对象被存储在全局缓存中或被另一个请求访问,会话令牌将保持受污染状态。

🌐 In this example, the user object serves as the lifetime argument. If this object gets stored in a global cache or is accessible by another request, the session token remains tainted.

易犯错误

不要仅依赖污点进行安全防护。 污点化一个值并不能阻止所有可能的派生值。例如,通过将一个带污点的字符串转换为大写创建的新值不会被标记为污点。

import {experimental_taintUniqueValue} from 'react';

const password = 'correct horse battery staple';

experimental_taintUniqueValue(
'Do not pass the password to the client.',
globalThis,
password
);

const uppercasePassword = password.toUpperCase() // `uppercasePassword` is not tainted

在这个例子中,常量 password 被污染。然后使用 password 通过在 password 上调用 toUpperCase 方法来创建一个新值 uppercasePassword。新创建的 uppercasePassword 没有被污染。

🌐 In this example, the constant password is tainted. Then password is used to create a new value uppercasePassword by calling the toUpperCase method on password. The newly created uppercasePassword is not tainted.

从受污染的值派生新值的其他类似方法,例如将其连接成更大的字符串、将其转换为 Base64 或返回子字符串创建未受污染的值。

🌐 Other similar ways of deriving new values from tainted values like concatenating it into a larger string, converting it to base64, or returning a substring create untained values.

污染仅能防止诸如将机密值明确传递给客户端之类的简单错误。在调用 taintUniqueValue 时出现的错误,例如在 React 之外使用全局存储而没有相应的生命周期对象,可能会导致被污染的值变得不再被污染。污染是一种保护层;一个安全的应用会有多层保护、设计良好的 API 以及隔离模式。

🌐 Tainting only protects against simple mistakes like explicitly passing secret values to the client. Mistakes in calling the taintUniqueValue like using a global store outside of React, without the corresponding lifetime object, can cause the tainted value to become untainted. Tainting is a layer of protection; a secure app will have multiple layers of protection, well designed APIs, and isolation patterns.

深入研究

使用 server-onlytaintUniqueValue 防止泄露秘密

🌐 Using server-only and taintUniqueValue to prevent leaking secrets

如果你运行的服务器组件环境可以访问私钥或密码(例如数据库密码),则必须小心不要将其传递给客户端组件。

🌐 If you’re running a Server Components environment that has access to private keys or passwords such as database passwords, you have to be careful not to pass that to a Client Component.

export async function Dashboard(props) {
// DO NOT DO THIS
return <Overview password={process.env.API_PASSWORD} />;
}
"use client";

import {useEffect} from '...'

export async function Overview({ password }) {
useEffect(() => {
const headers = { Authorization: password };
fetch(url, { headers }).then(...);
}, [password]);
...
}

这个示例会将秘密的 API 令牌泄露给客户端。如果这个 API 令牌可以用来访问该用户不应该有权访问的数据,可能会导致数据泄露。

🌐 This example would leak the secret API token to the client. If this API token can be used to access data this particular user shouldn’t have access to, it could lead to a data breach.

理想情况下,这样的秘密会被抽象到一个单独的辅助文件中,该文件只能被服务器上的受信任数据工具导入。这个辅助文件甚至可以使用 server-only 标记,以确保该文件不会在客户端被导入。

🌐 Ideally, secrets like this are abstracted into a single helper file that can only be imported by trusted data utilities on the server. The helper can even be tagged with server-only to ensure that this file isn’t imported on the client.

import "server-only";

export function fetchAPI(url) {
const headers = { Authorization: process.env.API_PASSWORD };
return fetch(url, { headers });
}

有时候在重构过程中会发生错误,并且并非所有同事都可能知道这一点。为了防止将来发生这些错误,我们可以“污染”实际的密码:

🌐 Sometimes mistakes happen during refactoring and not all of your colleagues might know about this. To protect against this mistakes happening down the line we can “taint” the actual password:

import "server-only";
import {experimental_taintUniqueValue} from 'react';

experimental_taintUniqueValue(
'Do not pass the API token password to the client. ' +
'Instead do all fetches on the server.'
process,
process.env.API_PASSWORD
);

现在,每当有人尝试将此密码传递给客户端组件,或通过服务器函数将密码发送给客户端组件时,将抛出你在调用 taintUniqueValue 时定义的消息的错误。

🌐 Now whenever anyone tries to pass this password to a Client Component, or send the password to a Client Component with a Server Function, an error will be thrown with message you defined when you called taintUniqueValue.