@@ -260,14 +260,15 @@ export function ExternalDbSettings() {
-
+
- Только эти таблицы доступны через read-only шлюз. Пусто = шлюз закрыт.
+ Опционально: ограничьте список, если не хотите давать доступ ко всей схеме.
+ Основную отсечку делайте правами пользователя БД (GRANT SELECT).
diff --git a/server/routes/external-db.routes.ts b/server/routes/external-db.routes.ts
index d8e40fc..0bd7261 100644
--- a/server/routes/external-db.routes.ts
+++ b/server/routes/external-db.routes.ts
@@ -4,11 +4,11 @@
//
// Безопасность:
// - пароль шифруется AES-256-GCM, по API не отдаётся (только маска);
-// - шлюз: только SELECT, без мульти-выражений, allowlist таблиц на подключение,
-// принудительный LIMIT (обёртка подзапросом), statement_timeout 10 с;
+// - шлюз: только SELECT, без мульти-выражений, принудительный LIMIT (обёртка подзапросом),
+// statement_timeout 10 с; опциональный allowlist таблиц (пустой = все таблицы);
// - каждый запрос пишется в external_db_query_log (кто/когда/SQL/строки/мс);
-// - рекомендуется заводить во внешней БД пользователя с реальным GRANT SELECT
-// только на нужные таблицы (defence in depth — шлюз не заменяет права БД).
+// - основной рубеж — пользователь внешней БД с реальным GRANT SELECT ( defence in depth:
+// шлюз не заменяет права БД).
import type { Express, Response } from "express";
import { Router } from "express";
@@ -270,13 +270,14 @@ export function registerExternalDbRoutes(app: Express): void {
const cleaned = validateReadOnly(sqlText);
const allowed = (conn.allowed_tables ?? []).map((t) => String(t).toLowerCase());
- if (allowed.length === 0) {
- return res.status(403).json({ error: "Для подключения не задан список разрешённых таблиц" });
- }
- const usedTables = extractTableNames(stripStringLiterals(cleaned));
- const denied = usedTables.filter((t) => !allowed.includes(t));
- if (denied.length > 0) {
- return res.status(403).json({ error: `Таблицы не в allowlist: ${denied.join(", ")}` });
+ // Пустой список = доступ ко ВСЕМ таблицам (ограничение — опционально;
+ // основной рубеж — права самого пользователя внешней БД, GRANT SELECT).
+ if (allowed.length > 0) {
+ const usedTables = extractTableNames(stripStringLiterals(cleaned));
+ const denied = usedTables.filter((t) => !allowed.includes(t));
+ if (denied.length > 0) {
+ return res.status(403).json({ error: `Таблицы не в allowlist: ${denied.join(", ")}` });
+ }
}
const wrapped = `SELECT * FROM (${cleaned}) AS __gateway_q LIMIT ${MAX_ROWS}`;