taint

Использование

Опция taint включает поддержку экспериментальных API React для пометки объектов и значений. Эта функция помогает предотвратить случайную передачу конфиденциальных данных клиенту. При включении вы можете использовать:

Полезно знать: Активация этого флага также включает экспериментальный канал React для директории app.

import type { NextConfig } from 'next'

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

export default nextConfig
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    taint: true,
  },
}

module.exports = 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} />
}

Лучший подход — удалить SERVICE_API_KEY из данных, возвращаемых getSystemConfig.