应禁用SHOW DATABASES权限并配合视图重写或RLS策略;Redis需禁用KEYS改用SCAN+ACL前缀控制;SDK须配置跳过库查询异常。
MySQL 多实例中如何避免
暴露敏感库名
默认情况下,所有 mysql 实例都会把本地数据库名(包括业务库、中间件库、测试库)统一列在
里,只要用户有
权限就能查到。这不是“显示不显示”的前端问题,而是权限与视图机制的底层行为。
真正可控的点只有两个:权限隔离 + 视图重写。不能靠应用层过滤,因为 DBA、运维、甚至某些监控脚本直连时都会绕过。
给非 DBA 用户分配账号时,**禁止授予
权限**——这是最直接的开关,缺省即关闭;
若必须保留该权限(如某些 ORM 初始化逻辑依赖),则需配合
+ 自定义视图替代原
查询路径;
注意:MySQL 8.0+ 的
或
不会自动同步此限制,仍需单独处理。
PostgreSQL 中用
实现分级可见性
PostgreSQL 没有全局“隐藏数据库”开关,它的库级隔离靠的是连接粒度——每个连接只属于一个数据库。但
表本身对所有用户可读,所以关键不是“藏”,而是“让不该看的人查不到有效数据”。
典型做法是:用
策略锁住
,再结合角色继承链做动态过滤。
创建只读角色
,不赋予
或
到系统库的权限;
在
上启用 RLS,并添加策略:
;
注意:RLS 对超级用户无效,所以 DBA 账号必须严格管控,且不能用
临时切换成普通角色来绕过;
某些客户端(如
)会绕过 RLS 直接调用内部函数,此时需配合
的
或
做连接层兜底。
Redis 多租户场景下如何防止
泄露库结构
Redis 本身没有“数据库分级”概念,
到
是平级编号,
默认扫当前 db,但很多运维误以为“不同 db 就天然隔离”。实际风险在于:一旦拿到任意 db 的访问权,就可能通过
推出总数量,再轮询所有 db 扫 key。
禁用
命令(在
中加
),改用
+ 游标,且要求客户端必须带
前缀约束;
为每个租户分配独立 Redis 实例或使用 Redis Modules(如 RedisJSON + ACL),而不是靠 db 编号区分;
ACL 规则示例:
—— 注意
后面的 pattern 必须显式限定 key 前缀,否则
等同于开放全部;
Redis 7.0+ 支持
,建议开启并对接 SIEM,因为 ACL 误配比命令禁用更容易被忽略。
跨语言 SDK 连接池中过滤掉系统库名的通用技巧
很多语言的数据库驱动(如 Python 的
、Go 的
)在初始化时会自动执行
或等效查询来构建元数据缓存。如果后端已做权限控制,但 SDK 仍报错或返回空列表,大概率是驱动没适配新权限模型。
Python:
可避免因
权限不足导致 connect() 报
;
Go:
中去掉末尾的
,否则驱动会尝试
并触发权限检查;
Java JDBC:
这类参数不影响权限,但
若配置不当,可能让驱动在初始化阶段因类型转换失败而静默跳过库列表获取;
所有 SDK 都应设置
和
,否则连接池复用旧连接时,可能缓存了上一个用户看到的库列表。
系统数据库不是靠“前端不展示”来隐藏的,而是靠连接建立那一刻的权限边界、协议层响应截断、以及 SDK 对异常的容忍策略共同决定的。最容易被漏掉的是连接池生命周期和驱动初始化顺序——它们往往在业务代码之前就完成了元数据拉取。
information_schemainformation_schema.schemataselectSHOW DATABASESsql_mode=STRICT_TRANS_TABLESinformation_schema.schemataadmin_portperformance_schemapg_catalogpg_databaserow level security (RLS)pg_databaseapp_readerCREATEDBCONNECTpg_databaseUSING (datname IN ('prod', 'staging'))SET ROLEpsql \lpg_hba.confhostnosslclientcert=verify-fullKEYS *SELECT 0SELECT 15KEYS *CONFIG GET databasesKEYSredis.confrename-command KEYS ""SCANMATCHACL SETUSER tenant_a on >pwd ~tenant_a:* allcommands~~*ACL LOGmysql-connector-pythondatabase/sqlSHOW DATABASESmysql.connector.connect(..., get_warnings=False, raise_on_warnings=False)SHOW DATABASESAccess deniedsql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/?multiStatements=true")/dbnameUSE dbnameuseSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTCzeroDateTimeBehavior=CONVERT_TO_NULLmaxIdleTimeconnectionTimeout