Ограничение доступа к файловой системе через server.fs

Параметр server.fs в конфигурации Vite управляет доступом dev-сервера к файловой системе. Он определяет:

  • какие директории разрешено читать;
  • какие каталоги считаются безопасными;
  • как Vite обрабатывает запросы к файлам за пределами корня проекта;
  • может ли браузер получить доступ к файлам вне рабочей директории.

Механизм особенно важен в monorepo-структурах, при использовании симлинков, локальных пакетов, общих библиотек и нестандартной архитектуры проекта.

Конфигурация располагается внутри раздела server:

import { defineConfig } from 'vite'

export default defineConfig({
  server: {
    fs: {

    }
  }
})

Причины существования ограничений

Во время разработки Vite предоставляет браузеру доступ к исходным файлам напрямую. Без ограничений это создавало бы серьёзные проблемы безопасности:

  • возможность чтения файлов вне проекта;
  • утечка конфигураций;
  • доступ к .env;
  • чтение SSH-ключей;
  • случайная публикация системных файлов.

По этой причине Vite ограничивает область доступа файловой системы.

Если dev-сервер получает запрос к файлу вне разрешённой области, возвращается ошибка:

403 Restricted

или:

The request url is outside of Vite serving allow list

Структура server.fs

Основные параметры:

server: {
  fs: {
    strict: true,
    allow: [],
    deny: []
  }
}

strict

Включает строгую проверку доступа к файловой системе.

allow

Список директорий, доступ к которым разрешён.

deny

Список запрещённых файлов и шаблонов.


Параметр server.fs.strict

Назначение

Опция определяет, должен ли Vite блокировать доступ к файлам вне рабочей области проекта.

Пример:

export default defineConfig({
  server: {
    fs: {
      strict: true
    }
  }
})

Поведение при strict: true

Это значение используется по умолчанию.

Vite разрешает доступ только:

  • к корню проекта;
  • к внутренним каталогам Vite;
  • к директориям из allow.

Попытка получить файл вне разрешённых путей приводит к ошибке.

Например:

http://localhost:5173/@fs/C:/secret/config.txt

будет заблокирован.


Поведение при strict: false

export default defineConfig({
  server: {
    fs: {
      strict: false
    }
  }
})

В этом режиме Vite перестаёт ограничивать файловую систему.

Dev-сервер получает доступ практически ко всем файлам, доступным процессу Node.js.

Это удобно:

  • в сложных monorepo;
  • при локальной разработке пакетов;
  • при нестандартной инфраструктуре.

Но подобная конфигурация считается небезопасной.


Риски отключения strict-режима

При strict: false становятся доступны:

  • системные файлы;
  • домашняя директория пользователя;
  • приватные ключи;
  • конфигурации IDE;
  • переменные окружения;
  • сторонние проекты.

Если dev-сервер случайно окажется доступным из сети, последствия могут быть критическими.

Поэтому отключение строгого режима допустимо только в контролируемой среде разработки.


Параметр server.fs.allow

Назначение

Свойство allow задаёт список директорий, доступ к которым разрешён дополнительно.

Пример:

export default defineConfig({
  server: {
    fs: {
      allow: ['..']
    }
  }
})

Разрешение доступа к родительской директории

Частая ситуация — использование общей папки с библиотеками:

workspace/
├── shared/
├── frontend/
└── backend/

Если Vite запускается внутри frontend, доступ к shared будет запрещён.

Решение:

import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  server: {
    fs: {
      allow: [
        path.resolve(__dirname, '../shared')
      ]
    }
  }
})

Использование нескольких директорий

server: {
  fs: {
    allow: [
      '/packages',
      '/shared',
      '/configs'
    ]
  }
}

Использование абсолютных путей

Наиболее надёжный вариант:

import path from 'path'

server: {
  fs: {
    allow: [
      path.resolve(__dirname, '../shared')
    ]
  }
}

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

Vite предоставляет встроенную функцию определения workspace-корня.

Пример:

import { defineConfig, searchForWorkspaceRoot } from 'vite'

export default defineConfig({
  server: {
    fs: {
      allow: [
        searchForWorkspaceRoot(process.cwd())
      ]
    }
  }
})

Как работает searchForWorkspaceRoot

Функция ищет признаки workspace:

  • package.json;
  • pnpm-workspace.yaml;
  • .git;
  • lerna.json.

После нахождения корневой директории она автоматически разрешается для доступа.

Это особенно полезно для:

  • monorepo;
  • Turborepo;
  • Nx;
  • pnpm workspace;
  • Yarn workspace.

Работа с monorepo

Типичная структура

repo/
├── apps/
│   └── frontend/
├── packages/
│   ├── ui/
│   └── core/
└── package.json

Если Vite работает внутри apps/frontend, пакеты из packages могут блокироваться.


Правильная настройка

import { defineConfig } from 'vite'
import path from 'path'

export default defineConfig({
  server: {
    fs: {
      allow: [
        path.resolve(__dirname, '../. ./packages')
      ]
    }
  }
})

Альтернатива через workspace root

import { defineConfig, searchForWorkspaceRoot } from 'vite'

export default defineConfig({
  server: {
    fs: {
      allow: [
        searchForWorkspaceRoot(process.cwd())
      ]
    }
  }
})

Параметр server.fs.deny

Назначение

Опция deny запрещает доступ к определённым файлам и шаблонам.

Даже если каталог разрешён через allow, файлы из deny останутся недоступными.


Пример

export default defineConfig({
  server: {
    fs: {
      deny: ['.env', '.env.*', '*.{crt,pem}']
    }
  }
})

Что обычно блокируется

По умолчанию Vite защищает:

  • .env;
  • .env.*;
  • *.crt;
  • *.pem.

Это предотвращает утечку:

  • сертификатов;
  • приватных ключей;
  • секретов;
  • API-токенов.

Добавление собственных ограничений

server: {
  fs: {
    deny: [
      '.secrets',
      '*.key',
      '*.sqlite'
    ]
  }
}

Доступ через /@fs/

Что такое /@fs/

Vite использует специальный префикс:

/@fs/

Он позволяет обращаться к файлам напрямую через абсолютный путь.

Пример:

/@fs/C:/projects/shared/file.js

или:

/@fs/home/user/shared/file.js

Как Vite проверяет запрос

При обращении через /@fs/ выполняются проверки:

  1. находится ли файл внутри разрешённой области;
  2. не входит ли файл в deny;
  3. включён ли strict-режим;
  4. существует ли файл.

Пример блокировки

Если файл расположен вне allow-области:

403 Restricted

Влияние симлинков

Node.js и Vite могут по-разному интерпретировать пути через symlink.

Например:

project/
└── node_modules/
    └── shared -> ../. ./shared

Физически папка находится вне проекта.

Из-за этого Vite способен заблокировать доступ.


Решение

Нужно явно разрешить физическую директорию:

server: {
  fs: {
    allow: ['../. ./shared']
  }
}

Взаимодействие с resolve.preserveSymlinks

При использовании симлинков иногда включают:

resolve: {
  preserveSymlinks: true
}

Это меняет способ обработки путей модулей.

В сочетании с server.fs.allow позволяет корректно подключать локальные пакеты.


Безопасность dev-сервера

Опасность использования host: true

Конфигурация:

server: {
  host: true
}

делает dev-сервер доступным из локальной сети.

Если одновременно используется:

fs: {
  strict: false
}

появляется риск удалённого доступа к файловой системе.


Потенциальная уязвимость

При неправильной настройке становится возможным:

http://ip:5173/@fs/C:/Users/admin/.ssh/id_rsa

или:

http://ip:5173/@fs/etc/passwd

Безопасная конфигурация

export default defineConfig({
  server: {
    host: 'localhost',
    fs: {
      strict: true
    }
  }
})

Практические сценарии

Подключение общей библиотеки

import path from 'path'

export default defineConfig({
  server: {
    fs: {
      allow: [
        path.resolve(__dirname, '../shared-ui')
      ]
    }
  }
})

Разрешение workspace-корня

import { searchForWorkspaceRoot } from 'vite'

export default defineConfig({
  server: {
    fs: {
      allow: [
        searchForWorkspaceRoot(process.cwd())
      ]
    }
  }
})

Ограничение доступа к секретам

export default defineConfig({
  server: {
    fs: {
      deny: [
        '.env',
        '.env.production',
        '*.pem',
        '*.key'
      ]
    }
  }
})

Типичные ошибки

Использование относительных путей без resolve

Плохо:

allow: ['../shared']

Лучше:

allow: [
  path.resolve(__dirname, '../shared')
]

Полное отключение strict-режима

Опасно:

fs: {
  strict: false
}

Особенно при:

host: true

Разрешение слишком широких директорий

Плохо:

allow: ['/']

или:

allow: ['C:/']

Это открывает доступ ко всему диску.


Игнорирование deny-списка

Если проект содержит:

  • сертификаты;
  • резервные копии;
  • SQLite-базы;
  • приватные ключи;

их необходимо явно блокировать.


Рекомендации по организации проекта

Для обычного проекта

Оптимально оставить настройки по умолчанию:

server: {
  fs: {
    strict: true
  }
}

Для monorepo

Использовать:

searchForWorkspaceRoot(process.cwd())

или точечный allow.


Для локальных пакетов

Разрешать только конкретные директории:

allow: [
  path.resolve(__dirname, '../packages/ui')
]

Для командной разработки

Дополнительно ограничивать:

deny: [
  '*.pem',
  '*.key',
  '.env*'
]

Полный пример конфигурации

import { defineConfig, searchForWorkspaceRoot } from 'vite'
import path from 'path'

export default defineConfig({
  server: {
    host: 'localhost',

    fs: {
      strict: true,

      allow: [
        searchForWorkspaceRoot(process.cwd()),
        path.resolve(__dirname, '../shared')
      ],

      deny: [
        '.env',
        '.env.*',
        '*.pem',
        '*.crt',
        '*.key'
      ]
    }
  }
})