diff --git a/CHANGELOG.md b/CHANGELOG.md index 42dc1c3..0401910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## [v2.3.0] + +- Verify SQL Server 2025 Developer on one isolated Linux x86_64 instance with strict certificate validation, a login granted only target-table SELECT and database SHOWPLAN, real HTTP MCP masking and R010 denial, durable audit, and Inspector/Dify calls. +- Stop static SQL Server R010 denials before database SHOWPLAN so a reader without access to the denied table receives the policy decision without touching that table. Exercise SELECT/SHOWPLAN revocation, mismatched CA and certificate name, SQL Server outage, and independent audit-store outage. +- Keep complex T-SQL, writes, B2/B5, other server versions, Windows/Always On, production certificate rotation, and full production support outside the tested boundary. See the [candidate acceptance record](docs/v2.3.0-acceptance.md). + ## [v2.2.0] - Connect the existing Oracle Free native driver and narrow SELECT parser to the HTTP MCP authorization, masking, and durable audit path on one named `FREEPDB1` instance. Verify a least-privilege account, Inspector/Dify calls, policy and database privilege revocation, plus business and independent audit database outages. diff --git a/Dockerfile b/Dockerfile index 7eea9e3..bcfb3a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM golang:1.26.9-bookworm AS build -ARG VERSION=v2.2.0 +ARG VERSION=v2.3.0 # Overridable module proxy for restricted networks, e.g. # `docker build --build-arg GOPROXY=https://goproxy.cn,direct`. ARG GOPROXY=https://proxy.golang.org,direct @@ -48,7 +48,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build mkdir -p /out \ FROM debian:bookworm-slim AS runtime -ARG VERSION=v2.2.0 +ARG VERSION=v2.3.0 ARG REVISION LABEL org.opencontainers.image.title="AgentSQL" \ org.opencontainers.image.source="https://github.com/cuipengdba/agentsql" \ diff --git a/Makefile b/Makefile index adc1267..e2ac752 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ BINARY_DIR := bin -VERSION ?= v2.2.0 +VERSION ?= v2.3.0 GO_VERSION ?= 1.26.9 GOPROXY ?= https://goproxy.cn,direct SOURCE_DATE_EPOCH ?= 1704067200 diff --git a/README.md b/README.md index b6f16df..636f310 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # AgentSQL -![Version v2.2.0](https://img.shields.io/badge/Version-v2.2.0-blue) +![Version v2.3.0](https://img.shields.io/badge/Version-v2.3.0-blue) [![Go](https://img.shields.io/badge/Go-1.26-00ADD8?logo=go)](go.mod) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue)](LICENSE) [![Commercial License](https://img.shields.io/badge/License-Commercial-orange)](COMMERCIAL-LICENSE.md) @@ -19,7 +19,9 @@ v2.1.0 在 YashanDB 23.4.1.109 指定实例、独立客户端 23.4.7.100 和受 v2.2.0 在 Oracle AI Database 26ai Free、`FREEPDB1`、服务端 `23.26.3.0.0` 指定合成实例中,把原生驱动的窄 SELECT 路径接入真实 HTTP MCP、最小权限账号、R010、脱敏和持久审计;双客户端、权限撤销及业务库/独立审计库断线恢复也通过。运行账号未验证 `PLAN_TABLE` 写权限,本版 Oracle 路径不预执行计划;商业版、TCPS、主备及完整生产支持未验证,见[发布说明](docs/release-notes-v2.2.0.md)与[候选验收](docs/v2.2.0-acceptance.md)。 -**验证范围:**v1.0.0 的列策略、人员委托及真实客户端结果见[验收记录](docs/v1.0-acceptance.md);v1.1.0 的审批与 B5 双身份结果见[验收记录](docs/v1.1.0-acceptance.md);v1.2.0 的 OAuth 互通结果见[验收记录](docs/v1.2.0-acceptance.md);v1.3.0 的 MySQL 列授权边界见[验收记录](docs/v1.3.0-acceptance.md);v1.4.0 的共享登录预算与双节点恢复见[验收记录](docs/v1.4.0-acceptance.md);v1.5.0 的三款国产数据库指定子集深度验证见[验收记录](docs/v1.5.0-acceptance.md);v1.6.0 的故障关闭与恢复证据见[验收记录](docs/v1.6.0-acceptance.md);v1.7.0 的瀚高和金仓证据见[正式发布验收](docs/v1.7.0-release-evidence.md);v1.8.0 的 Vastbase TLS 范围见[本版验收](docs/v1.8.0-acceptance.md);v1.9.0 接入向导改进见[本版验收](docs/v1.9.0-acceptance.md);v2.0.0 双网关与管理端列登记见[本版验收](docs/v2.0.0-acceptance.md);v2.1.0 崖山指定实例和入口拒绝审计见[本版验收](docs/v2.1.0-acceptance.md);v2.2.0 Oracle Free 指定实例见[本版验收](docs/v2.2.0-acceptance.md)。厂商结论只覆盖指定实例、模式及合成数据。以下命令须以 [GitHub Release](https://github.com/cuipengdba/agentsql/releases/tag/v2.2.0)、签名及镜像匿名拉取验收为准。 +v2.3.0 在 SQL Server 2025 Developer 指定 Linux x86_64 合成实例中,使用校验证书的严格 TLS 和仅获目标表 `SELECT`、数据库级 `SHOWPLAN` 的临时账号,完成真实 HTTP MCP、R010、手机号脱敏、持久审计、双客户端及撤权和断线恢复验证。其他 SQL Server 版本、Windows、Always On、生产证书轮换、复杂 T-SQL 和完整生产支持未验证;公开 Live Demo 仍使用合成 PostgreSQL 数据。见[发布说明](docs/release-notes-v2.3.0.md)与[候选验收](docs/v2.3.0-acceptance.md)。 + +**验证范围:**v1.0.0 的列策略、人员委托及真实客户端结果见[验收记录](docs/v1.0-acceptance.md);v1.1.0 的审批与 B5 双身份结果见[验收记录](docs/v1.1.0-acceptance.md);v1.2.0 的 OAuth 互通结果见[验收记录](docs/v1.2.0-acceptance.md);v1.3.0 的 MySQL 列授权边界见[验收记录](docs/v1.3.0-acceptance.md);v1.4.0 的共享登录预算与双节点恢复见[验收记录](docs/v1.4.0-acceptance.md);v1.5.0 的三款国产数据库指定子集深度验证见[验收记录](docs/v1.5.0-acceptance.md);v1.6.0 的故障关闭与恢复证据见[验收记录](docs/v1.6.0-acceptance.md);v1.7.0 的瀚高和金仓证据见[正式发布验收](docs/v1.7.0-release-evidence.md);v1.8.0 的 Vastbase TLS 范围见[本版验收](docs/v1.8.0-acceptance.md);v1.9.0 接入向导改进见[本版验收](docs/v1.9.0-acceptance.md);v2.0.0 双网关与管理端列登记见[本版验收](docs/v2.0.0-acceptance.md);v2.1.0 崖山指定实例和入口拒绝审计见[本版验收](docs/v2.1.0-acceptance.md);v2.2.0 Oracle Free 指定实例见[验收记录](docs/v2.2.0-acceptance.md);v2.3.0 SQL Server 2025 指定实例见[本版验收](docs/v2.3.0-acceptance.md)。厂商结论只覆盖指定实例、模式及合成数据。以下命令须以 [GitHub Release](https://github.com/cuipengdba/agentsql/releases/tag/v2.3.0)、签名及镜像匿名拉取验收为准。 ```text AI Agent → LLM / MCP Client → AgentSQL 网关 → PostgreSQL / MySQL @@ -45,6 +47,7 @@ AI Agent → LLM / MCP Client → AgentSQL 网关 → PostgreSQL / MySQL - v2.0.0 固定双网关部署:两个 Kubernetes 网关共享 PostgreSQL metadata 与独立审计库,分开保存 B2 实例标识;无状态 MCP 只读请求通过 Service 转发,升级与回退需维护窗口。管理端可对 PostgreSQL 闭合绑定模式的单张普通表登记列策略。公开 Live Demo 仍采用单网关合成数据环境。 - v2.1.0 崖山指定实例深度验证:临时最小权限账号在真实 MCP 中完成窄 SELECT、R010、手机号脱敏及持久审计;数据库权限撤销、业务库和独立审计库断线均按无原始结果交付验收。已认证的 SQL 工具对不可见数据源的预检拒绝也会记录无原始 SQL 的审计。 - v2.2.0 Oracle Free 指定实例受控只读:临时最小权限账号经真实 MCP 和 Inspector/Dify 执行窄 SELECT、R010 与手机号脱敏;数据库权限撤销、业务库及独立审计库断线按无原始结果交付验收。此路径不预执行 `EXPLAIN PLAN`。 +- v2.3.0 SQL Server 2025 Developer 指定实例受控只读:严格 TLS、最小权限账号、真实 MCP、R010、脱敏、审计、双客户端和两类故障关闭已用合成数据验证;不覆盖其他版本或完整生产支持。 ## 生态与兼容 @@ -86,17 +89,17 @@ AgentSQL 作为数据库中立的安全网关,当前原生支持 PostgreSQL v0.4.0 起提供 `linux/amd64` 与 `linux/arm64` 两种原生 glibc 包:`uname -m` 为 `x86_64` / `amd64` 时使用 amd64 包,为 `aarch64` / `arm64` 时使用 arm64 包。两者均要求 glibc 2.28+ 且 systemd 为 PID 1;安装器会自动选择本机架构、校验发布包外层与包内哈希,并安装为 systemd 服务。musl / Alpine 与 CentOS 7 仍不支持: ```bash -curl -fsSL https://github.com/cuipengdba/agentsql/releases/download/v2.2.0/install.sh | sudo sh -s -- install --version v2.2.0 +curl -fsSL https://github.com/cuipengdba/agentsql/releases/download/v2.3.0/install.sh | sudo sh -s -- install --version v2.3.0 ``` 管道和 CI 的非 TTY 输出不会显示自动生成的管理员密码;root 可在 `/etc/agentsql/agentsql.env` 查看。交互终端会在首次创建凭据时显示一次,也可显式加 `--show-password`。离线环境同时取得 tarball 与同名 `.sha256` 后,校验、解压并从包根安装: -以下是 v2.2.0 的 amd64 资产名示例;v0.4.0 起 arm64 主机使用同版本的 `-linux-arm64` 资产名。 +以下是 v2.3.0 的 amd64 资产名示例;v0.4.0 起 arm64 主机使用同版本的 `-linux-arm64` 资产名。 ```bash -sha256sum -c agentsql-v2.2.0-linux-amd64.tar.gz.sha256 -tar -xzf agentsql-v2.2.0-linux-amd64.tar.gz -cd agentsql-v2.2.0-linux-amd64 +sha256sum -c agentsql-v2.3.0-linux-amd64.tar.gz.sha256 +tar -xzf agentsql-v2.3.0-linux-amd64.tar.gz +cd agentsql-v2.3.0-linux-amd64 sudo ./install.sh install ``` @@ -105,20 +108,20 @@ sudo ./install.sh install 脚本默认拉取 GitHub 最新稳定 Release 对应的精确 GHCR tag;该 tag 是 `linux/amd64` + `linux/arm64` 多架构镜像,Docker 会自动匹配运行架构。脚本生成权限为 `0600` 的 `.env`,使用 `agentsql-data` 命名卷,并固定绑定回环地址: ```bash -curl -fsSL https://raw.githubusercontent.com/cuipengdba/agentsql/v2.2.0/scripts/quickstart.sh -o quickstart.sh && sh quickstart.sh --version v2.2.0 +curl -fsSL https://raw.githubusercontent.com/cuipengdba/agentsql/v2.3.0/scripts/quickstart.sh -o quickstart.sh && sh quickstart.sh --version v2.3.0 ``` -GHCR 包必须由发布者设为 public,以上命令才能在未登录环境匿名拉取。先核对 `v2.2.0` 精确 tag 的公开状态与双架构 manifest;Docker 会从 manifest list 自动选择 `linux/amd64` 或 `linux/arm64`: +GHCR 包必须由发布者设为 public,以上命令才能在未登录环境匿名拉取。先核对 `v2.3.0` 精确 tag 的公开状态与双架构 manifest;Docker 会从 manifest list 自动选择 `linux/amd64` 或 `linux/arm64`: ```bash -docker pull ghcr.io/cuipengdba/agentsql:v2.2.0 +docker pull ghcr.io/cuipengdba/agentsql:v2.3.0 ``` 等价的单条 `docker run` 如下;随机值只通过当前 shell 环境传入,不写进命令行参数: ```bash export AGENTSQL_SECRET="$(openssl rand -base64 24)" AGENTSQL_ADMIN_USER=admin AGENTSQL_ADMIN_PASSWORD="$(openssl rand -base64 18)" -docker run -d --name agentsql --restart unless-stopped --security-opt no-new-privileges:true -p 127.0.0.1:7780:7780 -e AGENTSQL_SECRET -e AGENTSQL_ADMIN_USER -e AGENTSQL_ADMIN_PASSWORD -v agentsql-data:/var/lib/agentsql ghcr.io/cuipengdba/agentsql:v2.2.0 +docker run -d --name agentsql --restart unless-stopped --security-opt no-new-privileges:true -p 127.0.0.1:7780:7780 -e AGENTSQL_SECRET -e AGENTSQL_ADMIN_USER -e AGENTSQL_ADMIN_PASSWORD -v agentsql-data:/var/lib/agentsql ghcr.io/cuipengdba/agentsql:v2.3.0 ``` ### 3. 本地 Live Demo(一条命令,无需源码) @@ -127,7 +130,7 @@ docker run -d --name agentsql --restart unless-stopped --security-opt no-new-pri ```bash mkdir agentsql-demo && cd agentsql-demo -curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.2.0/deploy/quickstart/docker-compose.yml +curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.3.0/deploy/quickstart/docker-compose.yml docker compose up -d ``` @@ -170,7 +173,7 @@ docker compose --profile observability up -d --build 以下源码构建示例仅生成本机可执行文件。自行构建的文件没有经过正式发布资产的签名与分发验收: ```bash -make build VERSION=v2.2.0 +make build VERSION=v2.3.0 ./bin/agentsqlctl init-config -o config.yaml export AGENTSQL_SECRET="$(openssl rand -base64 24)" export AGENTSQL_ADMIN_USER='admin' @@ -275,7 +278,7 @@ v0.5 审计报表支持 CSV、JSONL、纯 Go 生成的 PDF,以及包含四类 ## 数据库兼容矩阵 -v2.2.0 代码的注册表覆盖 **8 大类、29 款数据库**(8 款关系型 + 21 款 NoSQL / 向量)。瀚高 HighGo 已作为独立的限定只读类型计入;金仓 KingbaseES V9 另列为指定实例 PG 协议路径,不计入 29 款;分类表共 30 行。🟢 表示解析、授权、受控执行、脱敏、审计链路在所述范围通过;🔵 受控只读=连接、元数据、只读查询子集;🔷 连接级=ping、版本、Schema、只读预览;🟡 待验证=等厂商环境。指定实例闭环不代表完整生产支持。 +v2.3.0 代码的注册表覆盖 **8 大类、29 款数据库**(8 款关系型 + 21 款 NoSQL / 向量)。瀚高 HighGo 已作为独立的限定只读类型计入;金仓 KingbaseES V9 另列为指定实例 PG 协议路径,不计入 29 款;分类表共 30 行。🟢 表示解析、授权、受控执行、脱敏、审计链路在所述范围通过;🔵 受控只读=连接、元数据、只读查询子集;🔷 连接级=ping、版本、Schema、只读预览;🟡 待验证=等厂商环境。指定实例闭环不代表完整生产支持。 | 类别 | 数据库 | 适配状态 | 边界说明 | | --- | --- | --- | --- | @@ -284,7 +287,7 @@ v2.2.0 代码的注册表覆盖 **8 大类、29 款数据库**(8 款关系型 | | Oracle AI Database 26ai Free | 🔵 指定版本受控只读 | `FREEPDB1`、服务端 `23.26.3.0.0`:最小权限 HTTP MCP、R010、手机号脱敏、双客户端、权限撤销、独立审计故障及业务库断线恢复通过;`EXPLAIN PLAN` 不在网关账号路径,商业版及完整生产支持未验,见[验收记录](docs/v2.2.0-acceptance.md) | | | 达梦 DM8 | 🟢 指定场景闭环 | Pack3、COMPATIBLE_MODE=0:v1.5.0 完成客户端与权限撤销;v1.6.0 补齐审计故障、锁等待超时、取消与恢复复验;见[验收记录](docs/v1.6.0-acceptance.md) | | | 崖山 YashanDB | 🔵 已适配 · 指定版本受控只读 | 23.4.1.109 指定实例的最小权限 HTTP MCP、R010、脱敏、持久审计和故障关闭通过;其他版本及生产拓扑未验证 | -| | SQL Server 2025 | 🔵 已适配 · 受控只读 | 连接 / 元数据 / 只读查询子集 | +| | SQL Server 2025 Developer | 🔵 指定版本受控只读 | Linux x86_64 单实例合成数据:严格 TLS、最小权限 HTTP MCP、R010、脱敏、双客户端、SELECT/SHOWPLAN 撤权及故障关闭通过;其他版本、Windows/Always On、生产证书轮换和完整生产支持未验证,见[验收记录](docs/v2.3.0-acceptance.md) | | | Vastbase G100 | 🔵 指定版本受控只读 | V3.0 Build 9 PG 模式的有界 SELECT、表授权、脱敏、持久审计、客户端、权限撤销与停库恢复实测;v1.6.0 补齐故障关闭复验,v1.8.0 验证 `verify-full`、证书及凭据轮换和双客户端真调用;正式主备、其他 Build 与生产负载未测,见[验收记录](docs/v1.8.0-acceptance.md)。 | | | 瀚高 HighGo | 🔵 指定版本受控只读 | 独立 `highgo` 类型:9.0.10 企业版 PG 模式、单实例合成表的有界 SELECT、客户端、权限撤销、审计与故障关闭;见[验收记录](docs/v1.7.0-acceptance.md) | | | 金仓 KingbaseES V9(另列) | 🟢 指定场景闭环 | V9R1C10 PG 模式:v1.5.0 完成客户端、权限撤销与网络隔离恢复;v1.6.0 补齐故障关闭复验,不计入 29 | @@ -363,7 +366,7 @@ NoSQL / 向量目前是连接级、低阶支持;只读预览不代表完整查 ```bash go vet ./... go test -race -count=1 ./... -make build VERSION=v2.2.0 +make build VERSION=v2.3.0 ``` `pg_query_go` 要求 cgo;不要使用 `CGO_ENABLED=0` 或 Alpine/musl 构建。提交改动前请阅读 [SPEC](docs/SPEC.md),为行为变化补测试,并保持 `tests/corpus` 决策语料不被无意改写。 diff --git a/ROADMAP.md b/ROADMAP.md index 35ae8f2..23e73bb 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -32,6 +32,7 @@ AgentSQL 是面向 AI Agent 的数据库安全网关与 MCP Server。核心价 | v2.0.0 | 已发布([正式 Release](https://github.com/cuipengdba/agentsql/releases/tag/v2.0.0)) | 固定双网关 Kubernetes 部署及 PostgreSQL 单表列策略管理端登记;正式资产、双节点、升级恢复、GHCR、官网与 Demo 见[发布证据](docs/v2.0.0-release-evidence.md)。不承诺无停机滚动升级或任意水平扩容。 | | v2.1.0 | 已发布([正式 Release](https://github.com/cuipengdba/agentsql/releases/tag/v2.1.0)) | 崖山 23.4.1.109 指定实例的最小权限账号、真实 HTTP MCP、脱敏、R010、独立持久审计与故障关闭;范围和未验证项见[验收记录](docs/v2.1.0-acceptance.md),正式交付见[发布证据](docs/v2.1.0-release-evidence.md)。 | | v2.2.0 | 已发布([正式 Release](https://github.com/cuipengdba/agentsql/releases/tag/v2.2.0)) | Oracle AI Database 26ai Free、`FREEPDB1`、服务端 `23.26.3.0.0` 指定实例的受控只读 MCP、最小权限、脱敏、R010、持久审计和故障关闭;见[验收记录](docs/v2.2.0-acceptance.md)与[正式发布证据](docs/v2.2.0-release-evidence.md)。商业版、预执行计划和完整生产支持未验证。 | +| v2.3.0 | 开发中 | SQL Server 2025 Developer 指定 Linux x86_64 实例的严格 TLS、最小权限 HTTP MCP、窄 SELECT、R010、脱敏、持久审计、双客户端及故障关闭;候选结果见[验收记录](docs/v2.3.0-acceptance.md)。其他版本、Windows/Always On、生产证书轮换和完整生产支持未验证。 | ## v1.8.0–v3.0.0 候选开发顺序 diff --git a/SECURITY.md b/SECURITY.md index d52dd9c..99faecd 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -30,18 +30,20 @@ v0.6.1 至 v0.9.0 的发布状态以 [GitHub Releases](https://github.com/cuipen ## 数据库接入状态与安全边界(v0.5.0 GA) -以下分组保留 **AgentSQL v0.5.0 已发布代码**的历史接入范围与证据,不作为 v0.6.x 当前数据库支持矩阵,也不是数据库厂商的认证清单;当前版本的能力和限制须按对应发布说明核对。注册表共 8 类 27 款(6 款关系型 + 21 款 NoSQL / 向量);KingbaseES V9 已在下述 PG 协议基础闭环范围验证,仍不计入 27 款注册类型。 +以下分组按当前代码和对应版本验收记录陈述,不是数据库厂商的认证清单;具体能力仍须按相应发布说明核对。注册表共 8 类 29 款(8 款关系型 + 21 款 NoSQL / 向量);KingbaseES V9 在指定 PG 模式合成实例中另列验证,仍不计入 29 款注册类型。 档位:🟢 解析、授权、受控执行、脱敏、审计链路在所述范围通过;DM8 绿色仅限指定 Pack3 实例与用例,不代表完整方言兼容。HighGo 绿色仅限 9.0.10 企业版 PG 模式、单实例合成表用例,不代表完整方言兼容。金仓 V9 绿色仅限 V9R1C10、PG 模式、单实例合成表的只读受控基础闭环。🔵 受控只读=连接、元数据、只读查询子集;🔷 连接级=ping、版本、Schema、只读预览;🟡 待验证=等厂商环境。所有实测仅表示指定容器镜像、拓扑及合成用例通过,**非数据库厂商官方认证或生产支持承诺**。所有数据库必须经 AgentSQL 网关,使用专用最小权限账号;解析、目录、执行计划或授权事实不明时 fail-closed。协议兼容不等于安全语义相同;PostgreSQL B2 列级授权和 B5 受控事务不能因线协议相似而自动扩展到其他产品。 -### 关系型(6 款已注册 + 1 款额外验证) +### 关系型(8 款已注册 + 1 款额外验证) - 🟢 **PostgreSQL 14–18**:原生 `postgres` 路径;B2/B5 仍受自身版本、目录和能力检查约束。 - 🟢 **MySQL 8.0**:原生 `mysql` 路径;不继承 PostgreSQL B2/B5 证明。 - 🔵 **Oracle AI Database 26ai Free,服务端 23.26.3.0.0**:`FREEPDB1` 指定合成实例的临时最小权限账号经 HTTP MCP 完成窄 SELECT、R010、手机号脱敏、Inspector/Dify 调用与持久审计;数据库权限撤销、业务库和独立审计库故障均按无原始结果交付验证。运行账号未验证 `PLAN_TABLE` 写权限,本路径不预执行计划;商业版、TCPS、主备、复杂 SQL 与完整生产支持未验证,见 [v2.2.0 候选验收](docs/v2.2.0-acceptance.md)。 - 🟢 **达梦 DM8**:Pack3、`COMPATIBLE_MODE=0` 实例的只读 SELECT、列血缘、R010 表策略、审计和手机号脱敏实测通过;R006 未验证,多行分页/JOIN/函数/系统目录未测,写入未放开。 - 🔵 **崖山 YashanDB 23.4.1.109**:独立 `yashan` parser 与窄 SELECT 路径已接通;在 Linux x86_64、`COMPAT_VECTOR=yashan` 和独立 C 客户端 23.4.7.100 上,最小权限账号经真实 HTTP MCP 完成允许、R010 拒绝、手机号脱敏和持久审计,MCP Inspector/Dify 各完成允许与拒绝。数据库 SELECT 撤权、业务库断线及独立审计库停机均验证无原始行交付。仅为指定实例及合成数据的受控只读子集;TLS、取消/超时、主备、写入、事务和 EXPLAIN 未验证或关闭,见 [v2.1.0 候选验收](docs/v2.1.0-acceptance.md)。 -- 🔵 **SQL Server 2025(17.x)**:独立 `sqlserver` 严格只读子集;不提供完整 T-SQL、写入、B2/B5 或厂商认证。 +- 🔵 **SQL Server 2025 Developer(17.x)**:指定 Linux x86_64 单实例用本地测试 CA 严格校验证书,以仅获目标表 `SELECT` 和数据库级 `SHOWPLAN` 的账号经 HTTP MCP 验证窄 SELECT、R010、手机号脱敏、Inspector/Dify 和持久审计;SELECT/SHOWPLAN 撤权、业务库及独立审计库停机均按无原始结果交付验证。只覆盖合成数据;其他版本、Windows、Always On、生产证书轮换、完整 T-SQL、写入、B2/B5、厂商认证和完整生产支持未验证,见 [v2.3.0 候选验收](docs/v2.3.0-acceptance.md)。 +- 🔵 **Vastbase G100 V3.0 Build 9**:`vastbase` 独立类型在指定 PG 模式实例完成有界只读、权限、脱敏、审计与证书及凭据轮换验证;临时许可不代表正式许可,主备、生产负载和完整生产支持未验证,见 [v1.8.0 发布证据](docs/v1.8.0-release-evidence.md)。 +- 🔵 **瀚高 HighGo 9.0.10**:`highgo` 独立类型在指定企业版 PG 模式合成实例完成有界只读、权限、脱敏、客户端与故障链路;其他版本和完整生产支持未验证,见 [v1.7.0 发布证据](docs/v1.7.0-release-evidence.md)。 - 🟢 **金仓 KingbaseES V9(PG 模式、单实例)**:仅 V9R1C10 合成表的 PG 协议基础闭环;可写 license 卷下启动成功,ksql CRUD/分页/标识符、宿主机 pgx/v5 `$1` 参数绑定 + SCRAM、只读角色拒写 `42501`、AgentSQL 受控 SELECT allow + 手机号脱敏 + 列血缘 + R006 注释拒绝 + allow/deny 审计链路实测通过;复用 `db_type=postgres`。见[真库验证记录](docs/kingbase-v9-verification.md)。 > **金仓 V9 绿色边界:**实际 license 到期日未确认(服务器仅报告 180 天和含义未明的日期字段)。V9 Oracle 模式、原生 Kingbase 驱动、V8 未测;多行数据/深度分页、JOIN、聚合/函数、复杂表达式、类型 OID、系统目录、EXPLAIN 未测;TLS/加密传输、HA/故障切换、备份恢复、B2 列级授权、多角色并发、生产负载未测。显式 `GRANT CREATE SESSION` 语法被服务器拒绝;会话能力经 `LOGIN` + `CONNECT`、SCRAM 登录及查询验证。绿色仅指上述只读受控链路,不代表 V9 全模式、长期授权、厂商认证或生产支持承诺;未知能力继续 fail-closed。 diff --git a/deploy/kubernetes/README.md b/deploy/kubernetes/README.md index 607af65..db7a31d 100644 --- a/deploy/kubernetes/README.md +++ b/deploy/kubernetes/README.md @@ -1,6 +1,6 @@ -# AgentSQL v2.2.0 双网关 Kubernetes 部署 +# AgentSQL v2.3.0 双网关 Kubernetes 部署 -此清单只针对**两个节点、固定两个副本、无状态 MCP 只读请求**。运行配置把 HTTP 状态会话、事件续传和 B5 事务关闭;这不是已有单节点 B5 能力的迁移方案。双网关拓扑以[v2.0.0 设计与验收](../../docs/v2.0.0-design.md)为基线,v2.2.0 的数据库支持边界见[本版发布说明](../../docs/release-notes-v2.2.0.md)。 +此清单只针对**两个节点、固定两个副本、无状态 MCP 只读请求**。运行配置把 HTTP 状态会话、事件续传和 B5 事务关闭;这不是已有单节点 B5 能力的迁移方案。双网关拓扑以[v2.0.0 设计与验收](../../docs/v2.0.0-design.md)为基线,v2.3.0 的数据库支持边界见[本版发布说明](../../docs/release-notes-v2.3.0.md)。 ## 准备 @@ -20,7 +20,7 @@ kubectl -n agentsql apply -f deploy/kubernetes/configmap.yaml ## 首次安装或从 v2.0.0 升级 -先对两个 PostgreSQL 库做成套备份并记录旧镜像摘要。`migrate` 可能执行 DDL;即使本版 schema 未变,也要记录检查结果。Job 不在基础 `kustomization.yaml` 中,避免普通部署隐式迁移。**B2 对活跃网关的二进制摘要要求一致,v2.1.0 与 v2.2.0 不能混合提供服务;此部署使用 `OnDelete`,版本切换必须安排维护窗口。** +先对两个 PostgreSQL 库做成套备份并记录旧镜像摘要。`migrate` 可能执行 DDL;即使本版 schema 未变,也要记录检查结果。Job 不在基础 `kustomization.yaml` 中,避免普通部署隐式迁移。**B2 对活跃网关的二进制摘要要求一致,v2.2.0 与 v2.3.0 不能混合提供服务;此部署使用 `OnDelete`,版本切换必须安排维护窗口。** 从 v2.0.0 升级时,先执行下列维护窗口操作;首次安装跳过这一段。 diff --git a/deploy/kubernetes/jobs/migrate.yaml b/deploy/kubernetes/jobs/migrate.yaml index ee2d69c..152e8ef 100644 --- a/deploy/kubernetes/jobs/migrate.yaml +++ b/deploy/kubernetes/jobs/migrate.yaml @@ -21,7 +21,7 @@ spec: type: RuntimeDefault containers: - name: migrate - image: ghcr.io/cuipengdba/agentsql:v2.2.0 + image: ghcr.io/cuipengdba/agentsql:v2.3.0 command: - /usr/local/bin/agentsqlctl - migrate diff --git a/deploy/kubernetes/jobs/schema-check.yaml b/deploy/kubernetes/jobs/schema-check.yaml index ab44d33..90f873b 100644 --- a/deploy/kubernetes/jobs/schema-check.yaml +++ b/deploy/kubernetes/jobs/schema-check.yaml @@ -20,7 +20,7 @@ spec: type: RuntimeDefault containers: - name: schema-check - image: ghcr.io/cuipengdba/agentsql:v2.2.0 + image: ghcr.io/cuipengdba/agentsql:v2.3.0 command: - /usr/local/bin/agentsqlctl - migrate diff --git a/deploy/kubernetes/preflight.sh b/deploy/kubernetes/preflight.sh index 024d638..d0cde7a 100644 --- a/deploy/kubernetes/preflight.sh +++ b/deploy/kubernetes/preflight.sh @@ -25,14 +25,14 @@ done rendered="$(kubectl kustomize "$script_dir")" if ! grep -Fq 'replicas: 2' <<<"$rendered" || ! grep -Fq 'type: OnDelete' <<<"$rendered" || - ! grep -Fq 'image: ghcr.io/cuipengdba/agentsql:v2.2.0' <<<"$rendered" || + ! grep -Fq 'image: ghcr.io/cuipengdba/agentsql:v2.3.0' <<<"$rendered" || ! grep -Fq 'instance_id_file: "/var/lib/agentsql/instance-id"' <<<"$rendered" || [[ "$(grep -Fc 'driver: postgres' <<<"$rendered")" -lt 2 ]] || grep -Fq 'sqlite_path:' <<<"$rendered" || ! grep -Fq 'stateful: false' <<<"$rendered" || ! grep -Fq 'event_store_enabled: false' <<<"$rendered" || ! grep -Fq 'auto_migrate: false' <<<"$rendered"; then - echo 'preflight failed: expected fixed two-replica v2.2.0 safety profile is missing' >&2 + echo 'preflight failed: expected fixed two-replica v2.3.0 safety profile is missing' >&2 exit 1 fi diff --git a/deploy/kubernetes/statefulset.yaml b/deploy/kubernetes/statefulset.yaml index 124bbf8..1bac9ff 100644 --- a/deploy/kubernetes/statefulset.yaml +++ b/deploy/kubernetes/statefulset.yaml @@ -39,7 +39,7 @@ spec: topologyKey: kubernetes.io/hostname initContainers: - name: schema-check - image: ghcr.io/cuipengdba/agentsql:v2.2.0 + image: ghcr.io/cuipengdba/agentsql:v2.3.0 imagePullPolicy: IfNotPresent command: - /usr/local/bin/agentsqlctl @@ -66,7 +66,7 @@ spec: mountPath: /tmp containers: - name: gateway - image: ghcr.io/cuipengdba/agentsql:v2.2.0 + image: ghcr.io/cuipengdba/agentsql:v2.3.0 imagePullPolicy: IfNotPresent args: - serve diff --git a/deploy/quickstart/docker-compose.debug.yml b/deploy/quickstart/docker-compose.debug.yml index 11849cd..65f49c1 100644 --- a/deploy/quickstart/docker-compose.debug.yml +++ b/deploy/quickstart/docker-compose.debug.yml @@ -1,6 +1,6 @@ services: seed: - image: ghcr.io/cuipengdba/agentsql:v2.2.0-quickstart + image: ghcr.io/cuipengdba/agentsql:v2.3.0-quickstart gateway: image: agentsql-quickstart-gateway:debug volumes: diff --git a/deploy/quickstart/docker-compose.yml b/deploy/quickstart/docker-compose.yml index a43dcd1..59bed45 100644 --- a/deploy/quickstart/docker-compose.yml +++ b/deploy/quickstart/docker-compose.yml @@ -49,7 +49,7 @@ services: - demo-mysql seed: - image: ghcr.io/cuipengdba/agentsql:v2.2.0-quickstart + image: ghcr.io/cuipengdba/agentsql:v2.3.0-quickstart container_name: agentsql-quickstart-seed restart: "no" entrypoint: ["/usr/local/bin/agentsqlctl"] @@ -77,7 +77,7 @@ services: condition: service_healthy gateway: - image: ghcr.io/cuipengdba/agentsql:v2.2.0-quickstart + image: ghcr.io/cuipengdba/agentsql:v2.3.0-quickstart container_name: agentsql-quickstart-gateway restart: unless-stopped environment: diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 26a1a27..9128fae 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -2,7 +2,7 @@ AgentSQL 是面向 AI Agent 的数据库安全网关和生产级 MCP Server。元数据与审计记录默认保存在本地 SQLite,也可使用 PostgreSQL 控制面;业务数据库仍使用各自的 PostgreSQL 或 MySQL 连接。 -当前精确安装版本、下载链接和升级前检查见 [快速上手](GETTING_STARTED.md)与 [v2.2.0 发布说明](release-notes-v2.2.0.md)。固定双网关 Kubernetes 部署另见 [v2.2.0 操作指南](../deploy/kubernetes/README.md)。本文后部标有 v1.2.0 或其他旧版本号的发布命令与分阶段上线记录是历史示例;执行前须使用正式 Release 的同版资产、签名及镜像摘要。 +当前精确安装版本、下载链接和升级前检查见 [快速上手](GETTING_STARTED.md)与 [v2.3.0 发布说明](release-notes-v2.3.0.md)。固定双网关 Kubernetes 部署另见 [v2.3.0 操作指南](../deploy/kubernetes/README.md)。本文后部标有 v1.2.0 或其他旧版本号的发布命令与分阶段上线记录是历史示例;执行前须使用正式 Release 的同版资产、签名及镜像摘要。 ## 目录约定 diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index 1d7f755..2411f4d 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,6 +1,6 @@ # AgentSQL 快速上手 -> 本文说明 AgentSQL v2.2.0 的安装与使用。执行下列固定 v2.2.0 tag 的安装、Demo 和镜像命令前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产及签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 +> 本文说明 AgentSQL v2.3.0 的安装与使用。执行下列固定 v2.3.0 tag 的安装、Demo 和镜像命令前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产及签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 AgentSQL 是放在 AI Agent 与业务数据库之间的数据库安全网关和生产级 MCP Server,让每条 SQL 在执行前经过身份、权限、规则和决策检查,并在可审计的边界内执行。 @@ -31,15 +31,15 @@ AgentSQL 不是 BI、ORM 或 Text2SQL,不负责把自然语言转换成 SQL, | 范围 | 当前支持 | 说明 | | --- | --- | --- | | 一键安装宿主 | `linux/amd64`、`linux/arm64`;glibc 2.28+、systemd | v0.4.0 起提供 linux/arm64 原生 glibc 包;`x86_64` / `amd64` 对应 amd64,`aarch64` / `arm64` 对应 arm64 | -| 其他宿主 | Docker / Docker Compose | v2.2.0 GHCR tag 须经 `linux/amd64` + `linux/arm64` multi-arch manifest 与匿名拉取核验;musl / Alpine、CentOS 7 与上述两种架构之外的平台不提供原生包 | +| 其他宿主 | Docker / Docker Compose | v2.3.0 GHCR tag 须经 `linux/amd64` + `linux/arm64` multi-arch manifest 与匿名拉取核验;musl / Alpine、CentOS 7 与上述两种架构之外的平台不提供原生包 | | 业务数据库 | MySQL 8;PostgreSQL 14–18 | 为 AgentSQL 创建独立、最小权限运行账号 | | 控制面存储 | SQLite;PostgreSQL 15+ | SQLite 为默认;PostgreSQL 可分离 metadata 与 audit | | 浏览器控制台 | `http://127.0.0.1:7780` | 默认绑定回环地址 | -| 本地 Live Demo | Docker Engine / Docker Desktop、Docker Compose v2 | 使用 v2.2.0 精确 tag 的 Compose 和经核验的 GHCR 镜像;无需克隆源码或构建 | +| 本地 Live Demo | Docker Engine / Docker Desktop、Docker Compose v2 | 使用 v2.3.0 精确 tag 的 Compose 和经核验的 GHCR 镜像;无需克隆源码或构建 | ## 路径 A:5 分钟零配置看效果(一条命令) -这是 v2.2.0 专用 Demo 模式:使用合成数据和固定演示身份,无需克隆源码、无需编辑任何配置、无需构建镜像。执行前须核对 v2.2.0 Git tag 中的 Compose 文件与其引用的三个 GHCR 镜像均已公开且可匿名拉取;首次启动会拉取镜像并播种固定演示数据。 +这是 v2.3.0 专用 Demo 模式:使用合成数据和固定演示身份,无需克隆源码、无需编辑任何配置、无需构建镜像。执行前须核对 v2.3.0 Git tag 中的 Compose 文件与其引用的三个 GHCR 镜像均已公开且可匿名拉取;首次启动会拉取镜像并播种固定演示数据。 只需一个 `docker-compose.yml`(在任意空目录执行): @@ -48,7 +48,7 @@ AgentSQL 不是 BI、ORM 或 Text2SQL,不负责把自然语言转换成 SQL, mkdir agentsql-demo && cd agentsql-demo # 2) 下载自包含 compose(仅这一个文件,约 2KB) -curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.2.0/deploy/quickstart/docker-compose.yml +curl -fsSL -o docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.3.0/deploy/quickstart/docker-compose.yml # 3) 一条命令拉起全部容器(postgres + mysql + seed + gateway) docker compose up -d @@ -58,7 +58,7 @@ Windows(PowerShell)用等价命令: ```powershell New-Item -ItemType Directory -Force agentsql-demo | Out-Null; Set-Location agentsql-demo -Invoke-WebRequest -OutFile docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.2.0/deploy/quickstart/docker-compose.yml +Invoke-WebRequest -OutFile docker-compose.yml https://raw.githubusercontent.com/cuipengdba/agentsql/v2.3.0/deploy/quickstart/docker-compose.yml docker compose up -d ``` @@ -93,16 +93,16 @@ docker compose down -v **v0.4.0 起提供 linux/arm64 原生 glibc 包:**一键安装支持 `linux/amd64`(`uname -m` 为 `x86_64` / `amd64`)和 `linux/arm64`(`aarch64` / `arm64`),均要求 glibc 2.28+、systemd,且命令需要 root 权限: -以下示例固定 v2.2.0。执行前先确认正式 Release 提供该版本的 `install.sh`、`SHA256SUMS` 和签名;按 [发布资产说明](RELEASE_ASSETS.md) 使用独立核验的公钥验签清单,再用清单中的哈希核验安装器。 +以下示例固定 v2.3.0。执行前先确认正式 Release 提供该版本的 `install.sh`、`SHA256SUMS` 和签名;按 [发布资产说明](RELEASE_ASSETS.md) 使用独立核验的公钥验签清单,再用清单中的哈希核验安装器。 ```bash # 适用前提:linux/amd64 或 linux/arm64 + glibc 2.28+ + systemd -curl -fsSLo install.sh https://github.com/cuipengdba/agentsql/releases/download/v2.2.0/install.sh +curl -fsSLo install.sh https://github.com/cuipengdba/agentsql/releases/download/v2.3.0/install.sh # 先用已验签的 SHA256SUMS 核验 install.sh,再运行: -sudo sh ./install.sh install --version v2.2.0 +sudo sh ./install.sh install --version v2.3.0 ``` -除一键安装外,也可按 [部署指南](DEPLOY.md) 使用源码或 Docker Compose 构建。确认 v2.2.0 镜像已公开、双架构 manifest 和匿名拉取均通过后,运行 `docker pull ghcr.io/cuipengdba/agentsql:v2.2.0`;manifest 应匹配 `linux/amd64` / `linux/arm64`。发布状态和资产以 GitHub Releases 页面为准。 +除一键安装外,也可按 [部署指南](DEPLOY.md) 使用源码或 Docker Compose 构建。确认 v2.3.0 镜像已公开、双架构 manifest 和匿名拉取均通过后,运行 `docker pull ghcr.io/cuipengdba/agentsql:v2.3.0`;manifest 应匹配 `linux/amd64` / `linux/arm64`。发布状态和资产以 GitHub Releases 页面为准。 ### 2. 登录控制台 @@ -130,7 +130,7 @@ PostgreSQL 与 Vastbase G100 Build 9 PG 模式的指定实例可在数据源表 在「权限」中新建绑定该 Agent 与数据源的 `allow` 策略,从确实需要的单张表开始,例如 `public.customers` 或 `customers`。不要用 `*` 或 `schema.*` 做普通示例:两者虽然合法,却代表整表全列授权,会让列级白名单失效。 -安装 v2.2.0 后,控制台「接入向导」可复用同一个 Agent 和数据源选择做连接与权限预检、目标 SQL 离线解释,并生成 Dify 1.17.1 或 MCP Inspector 0.16.8 的无密钥接入说明。预检不执行 Agent SQL;策略数量大于零或离线结论为“未知”都不证明目标 SQL 已获授权。请在真实客户端分别验证允许与拒绝请求,再用响应中的审计 ID 跳转到审计详情核对。配置步骤与客户端可达地址的限制见[使用手册](USER_GUIDE.md#接入向导-onboardingv190)。 +安装 v2.3.0 后,控制台「接入向导」可复用同一个 Agent 和数据源选择做连接与权限预检、目标 SQL 离线解释,并生成 Dify 1.17.1 或 MCP Inspector 0.16.8 的无密钥接入说明。预检不执行 Agent SQL;策略数量大于零或离线结论为“未知”都不证明目标 SQL 已获授权。请在真实客户端分别验证允许与拒绝请求,再用响应中的审计 ID 跳转到审计详情核对。配置步骤与客户端可达地址的限制见[使用手册](USER_GUIDE.md#接入向导-onboardingv190)。 ### 6. 接入 MCP 客户端 @@ -185,9 +185,9 @@ curl -fsS http://127.0.0.1:7780/healthz curl -fsS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:7780/readyz ``` -## v2.2.0:升级前只读检查 +## v2.3.0:升级前只读检查 -正式发布并取得 v2.2.0 `agentsqlctl` 后,使用目标环境的配置先检查控制面 schema 版本。v2.2.0 不增加 schema 迁移;从 v2.1.0 升级仍须成套备份、只读检查并在隔离环境演练恢复。双网关 Kubernetes 部署另须按[维护窗口操作指南](../deploy/kubernetes/README.md)停止旧版双副本、等待 B2 租约到期后再启动新版,不支持新旧二进制同时活跃: +正式发布并取得 v2.3.0 `agentsqlctl` 后,使用目标环境的配置先检查控制面 schema 版本。v2.3.0 不增加 schema 迁移;从 v2.2.0 升级仍须成套备份、只读检查并在隔离环境演练恢复。双网关 Kubernetes 部署另须按[维护窗口操作指南](../deploy/kubernetes/README.md)停止旧版双副本、等待 B2 租约到期后再启动新版,不支持新旧二进制同时活跃: ```bash agentsqlctl migrate --check --config /etc/agentsql/config.yaml diff --git a/docs/INTEGRATIONS.md b/docs/INTEGRATIONS.md index 7f724d6..c91f36e 100644 --- a/docs/INTEGRATIONS.md +++ b/docs/INTEGRATIONS.md @@ -1,6 +1,6 @@ # AgentSQL MCP 接入指南 -> 本文说明 AgentSQL v2.2.0 的 MCP 接入。安装或拉取 v2.2.0 前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产与签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 +> 本文说明 AgentSQL v2.3.0 的 MCP 接入。安装或拉取 v2.3.0 前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产与签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 本文说明如何把 MCP 客户端接到 AgentSQL。身份、授权、规则、审批与能力边界见 [使用手册](USER_GUIDE.md),首次部署见 [快速上手](GETTING_STARTED.md);把决策事件外发到 Webhook、告警群或 Syslog 见 [通知外发指南](NOTIFICATIONS.md)。 diff --git a/docs/SPEC.md b/docs/SPEC.md index becb861..b966f7c 100644 --- a/docs/SPEC.md +++ b/docs/SPEC.md @@ -175,7 +175,7 @@ redaction: ### 6.2 核心枚举与模型 ```go -type DBDialect string // 业务库类型标识;注册表共 27 款,能进入 NewParser 的范围另受方言入口限制 +type DBDialect string // 业务库类型标识;注册表共 29 款,能进入 NewParser 的范围另受方言入口限制 type StmtType string // SELECT / INSERT / UPDATE / DELETE / DDL / ADMIN / UNKNOWN type Decision string // allow / warn / approve / deny type RiskLevel int // 1 拒绝 / 2 审批 / 3 告警 / 4 提示 @@ -350,9 +350,9 @@ PostgreSQL B5 默认开启时另外注册 `open_session`、`close_session`、`ge | 维度 | 支持情况 | |---|---| | 完整防护业务库(🟢) | MySQL 8.0;PostgreSQL 14 / 15 / 16 / 17 / **18** | -| 受控只读注册项(🔵) | `oracle`、`dm`、`yashan`、`sqlserver`;连接/元数据及各自限定的只读子集,不继承 PostgreSQL/MySQL 完整防护闭环 | +| 受控只读注册项(🔵) | `oracle`、`dm`、`yashan`、`sqlserver`、`vastbase`、`highgo`;连接/元数据及各自限定的只读子集,不继承 PostgreSQL/MySQL 完整防护闭环 | | 连接级注册项(🔷) | 7 类 21 款 NoSQL / 向量数据源;ping、版本、Schema 与只读预览按适配器有界提供,不承诺完整检索或写入 | -| 待验证候选(🟡) | KingbaseES V9,等厂商环境;不计入 27 款注册表 | +| 另列指定实例(不计入注册表) | KingbaseES V9R1C10 PG 模式已按指定合成场景验收;其他版本、模式与完整生产支持未验证 | | 控制面 / 元数据库 | SQLite(默认);PostgreSQL 15+,基准与推荐 **PG18**(开源免费) | | 审计库 | 随元数据库,或独立 PostgreSQL(最小权限仅 INSERT/SELECT) | | MCP 传输 | stdio;Streamable HTTP `/mcp`(MCP 2025-06-18) | @@ -362,7 +362,7 @@ PostgreSQL B5 默认开启时另外注册 `open_session`、`close_session`、`ge | 演示 | 一键自托管 Live Demo(只读、每日重置、六剧本) | | 列级脱敏 | 支持全局列、表.列、模式.表.列三档作用域,唯一归属时精确匹配,未解析且可能涉及受保护表时固定阻断 `***`;九类型能力矩阵:六类支持 `mask` / `hash` / `block`,`generic` 支持 `hash` / `block`,`number` / `date` 支持 `hash` / `block` / `range`;无 key 时 `mask` / `block` / `range` 正常运行;discovery 只生成六类 `mask` table-only disabled 草稿 | -注册表的 27 款类型、类别与默认端口以 `internal/model/datasource_types.go` 为准;21 款 NoSQL / 向量的逐项对照及 HBase/Couchbase 端口例外见 [NoSQL 支持范围](nosql-support.md)。DM8 Pack3、`COMPATIBLE_MODE=0` 的只读 SELECT、列血缘、R010、审计及手机号脱敏已在批六十五真库链路通过;R006、多行分页/JOIN/函数/系统目录仍未验证,写入未放开。Oracle 的完整网关授权/脱敏闭环仍未证明。YashanDB 标准 `NewParser` 与窄 SELECT 路径已接通;本轮独立客户端直连真库参数绑定和只读写入拒绝通过,另以真实数据库执行器和测试夹具审计端口验证 SELECT、列血缘、R010、手机号脱敏及三条审计。HTTP/MCP、持久化审计及最小权限账号与流水线的组合未测。SQL Server、达梦、Oracle、YashanDB 及其他协议候选的完整方言或商业版认证均未宣称;各自证据边界见 [Release Notes](release-notes-v0.5.md) 与 [SECURITY.md](../SECURITY.md)。 +注册表的 29 款类型、类别与默认端口以 `internal/model/datasource_types.go` 为准;21 款 NoSQL / 向量的逐项对照及 HBase/Couchbase 端口例外见 [NoSQL 支持范围](nosql-support.md)。DM8、金仓、Vastbase、瀚高、崖山、Oracle Free 和 SQL Server 2025 的指定实例证据范围各不相同,分别见对应版本的[路线图与验收链接](../ROADMAP.md)和 [SECURITY.md](../SECURITY.md)。其中 v2.3.0 SQL Server 2025 Developer 的最小权限、严格 TLS、HTTP MCP、R010、脱敏、持久审计和故障关闭只覆盖隔离 x86_64 合成实例与窄 SELECT;不继承 PostgreSQL 的 B2/B5,也不表示厂商认证或完整生产支持。无法证明的方言和拓扑继续失败关闭。 --- diff --git a/docs/USER_GUIDE.md b/docs/USER_GUIDE.md index 31bd82f..a03834c 100644 --- a/docs/USER_GUIDE.md +++ b/docs/USER_GUIDE.md @@ -1,8 +1,8 @@ # AgentSQL 使用手册 -> 本文说明 AgentSQL v2.2.0 的功能与操作。安装或拉取 v2.2.0 前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产与签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 +> 本文说明 AgentSQL v2.3.0 的功能与操作。安装或拉取 v2.3.0 前,先在 [GitHub Releases](https://github.com/cuipengdba/agentsql/releases) 核对正式资产与签名,并确认对应 GHCR 镜像可以匿名拉取;源码仓库为 `github.com/cuipengdba/agentsql`。 -本手册按控制台主要菜单说明 v2.2.0 的能力和操作。首次使用请先完成 [快速上手](GETTING_STARTED.md);MCP 客户端配置见 [接入指南](INTEGRATIONS.md);版本范围与验收状态见 [v2.2.0 发布说明](release-notes-v2.2.0.md)和[验收记录](v2.2.0-acceptance.md);「设置与集成 → 通知设置」的 Webhook、Syslog 与安全边界见 [通知外发指南](NOTIFICATIONS.md)。 +本手册按控制台主要菜单说明 v2.3.0 的能力和操作。首次使用请先完成 [快速上手](GETTING_STARTED.md);MCP 客户端配置见 [接入指南](INTEGRATIONS.md);版本范围与验收状态见 [v2.3.0 发布说明](release-notes-v2.3.0.md)和[验收记录](v2.3.0-acceptance.md);「设置与集成 → 通知设置」的 Webhook、Syslog 与安全边界见 [通知外发指南](NOTIFICATIONS.md)。 ## 1. 总览 `/` @@ -35,6 +35,10 @@ v1.9.0 在同一页面增加 Dify 1.17.1 / MCP Inspector 0.16.8 的接入说明 离线解释只返回“明确拒绝”或“尚不能确认允许”,并可列出对象、匹配策略 ID 与静态规则;它不连接业务库,也不执行 SQL。最后在真实客户端分别发起允许与拒绝请求,将响应的正整数审计 ID 填入向导直达审计详情,核对 Agent、数据源、决策、规则及脱敏。没有审计 ID 时按错误码先查认证、协议或依赖故障。向导不能替代逐对象授权、执行链路或生产支持验收。 +## v2.3.0:SQL Server 2025 指定实例受控只读验证 + +本版在 SQL Server 2025 Developer x86_64 指定合成实例上,用校验本地测试 CA 的严格 TLS、只获目标表 `SELECT` 与数据库级 `SHOWPLAN` 的临时账号,验证真实 HTTP MCP、表策略 R010、手机号脱敏与持久审计。Inspector 与 Dify 完成允许/拒绝调用;撤销 SELECT、撤销 SHOWPLAN、业务库和独立审计库停机均按不交付原始结果验收。创建数据源时请使用可验证的服务器证书和专用最小权限账号;无权表的静态 R010 在网关终止,不以数据库权限错误替代。此结果不覆盖 Windows、Always On、其他版本、生产证书轮换、复杂 SQL、写入、B2/B5 或完整生产支持。详见[候选验收](v2.3.0-acceptance.md)。 + ## v2.2.0:Oracle Free 指定实例受控只读验证 本版在 Oracle AI Database 26ai Free、`FREEPDB1`、服务端 `23.26.3.0.0` 的隔离实例中,用只获 `CREATE SESSION` 和目标表 `SELECT` 的临时账号验证真实 HTTP MCP、表策略 R010、手机号脱敏与持久审计。Inspector 与 Dify 的允许/拒绝调用、数据库 SELECT 撤权、业务库及独立审计库停机均用合成数据复验。Oracle 运行账号未验证 `PLAN_TABLE` 写权限,网关不预执行 `EXPLAIN PLAN`;商业版、TCPS、主备、复杂 SQL、写入与完整生产支持不在本版声明中。详见[候选验收](v2.2.0-acceptance.md)。 @@ -779,7 +783,7 @@ SQLite → PostgreSQL 控制面迁移要点:停止旧服务写入;备份 SQL - PostgreSQL 的 B2 列级授权、B5 跨请求逻辑会话与计划事务均出厂默认开启;MySQL 不进入 B2 PostgreSQL 路径,且不支持 B5 跨请求事务。每个 operation 仍只允许一条顶层 SQL,并受预检计划和会话安全边界约束。 - 完整原生路径仍以 PostgreSQL 14–18 与 MySQL 8 为主。DM、Oracle、YashanDB 的独立最小 dialect,以及 OpenTenBase、PolarDB 等协议兼容路径均受严格边界约束;达梦 DM8 Pack3、金仓 V9R1C10 PG 模式、Vastbase G100 Build 9 PG 模式在 v1.5.0 完成指定实例的真实客户端、权限撤销和断库恢复复验,v1.6.0 在相同范围内通过独立审计库故障、真实锁等待超时、客户端取消、数据库端终止和恢复后脱敏复验。v1.7.0 的独立 HighGo 类型仅限 9.0.10 企业版 PG 模式有界 SELECT;金仓 TLS 仅限 V9R1C10 PG 模式指定实例。范围见[瀚高验收](v1.7.0-acceptance.md)与[金仓 TLS 验收](v1.7.0-dm-kingbase-depth-acceptance.md),不得视为完整方言支持或厂商认证。 - v1.0.0 的 `/mcp/2026-07-28` 是独立无状态 HTTP 入口:每次 POST 都重新验证 Agent Key、由 Key 确定租户并与旧入口共用 Agent QPS;版本 Header、`Mcp-Method`、适用时的 `Mcp-Name` 必须与请求体匹配。旧 `/mcp` 与 stdio 继续服务旧协议;新入口没有 B5 跨请求逻辑会话或计划事务工具。客户端配置和请求示例见 [MCP 接入指南](INTEGRATIONS.md)。 -- v0.4.0 起提供 linux/arm64 原生 glibc 包(`aarch64` / `arm64`),并继续提供 linux/amd64 包(`x86_64` / `amd64`);v2.2.0 GHCR tag 须在公开发布并通过双架构 manifest 与匿名拉取验收后使用。v0.5.0 已提供可选的 TOTP MFA、OIDC 和 LDAP/AD 人员认证;配置与限制见 [身份认证指南](AUTHENTICATION.md)。v2.2.0 的 Kubernetes 清单限固定双网关与指定无状态只读路径;PostgreSQL 自身 HA、法规级 WORM、SIEM、musl / Alpine 或 CentOS 7 原生一键安装仍不支持,RBAC / 多租户能力仍是 MVP。 +- v0.4.0 起提供 linux/arm64 原生 glibc 包(`aarch64` / `arm64`),并继续提供 linux/amd64 包(`x86_64` / `amd64`);v2.3.0 GHCR tag 须在公开发布并通过双架构 manifest 与匿名拉取验收后使用。v0.5.0 已提供可选的 TOTP MFA、OIDC 和 LDAP/AD 人员认证;配置与限制见 [身份认证指南](AUTHENTICATION.md)。v2.3.0 的 Kubernetes 清单限固定双网关与指定无状态只读路径;PostgreSQL 自身 HA、法规级 WORM、SIEM、musl / Alpine 或 CentOS 7 原生一键安装仍不支持,RBAC / 多租户能力仍是 MVP。 ## 在线安装包升级预演(与 schema 检查不同) 准备可信渠道提供的 HTTPS 清单,JSON 字段为 `version`、同源 HTTPS `url` 和下载内容的 `sha256`。当前仅支持版本检查和只读预演: diff --git a/docs/nosql-support.md b/docs/nosql-support.md index 2e077f5..2bf9335 100644 --- a/docs/nosql-support.md +++ b/docs/nosql-support.md @@ -1,6 +1,6 @@ # NoSQL 数据源支持范围 -注册表 `internal/model/datasource_types.go` 共登记 8 类 27 款:关系型 6 款,以及下表 7 类 21 款 NoSQL / 向量数据源。后者提供**连接级/低阶支持,非完整检索/管理能力**。适配器实现了连接、健康检查、元数据枚举和受控命令子集;各能力受服务版本、凭证权限、请求边界及适配器白名单限制。这里的“支持”不代表所有原生命令、检索语法、管理操作或厂商认证均可用。 +注册表 `internal/model/datasource_types.go` 共登记 8 类 29 款:关系型 8 款,以及下表 7 类 21 款 NoSQL / 向量数据源。后者提供**连接级/低阶支持,非完整检索/管理能力**。适配器实现了连接、健康检查、元数据枚举和受控命令子集;各能力受服务版本、凭证权限、请求边界及适配器白名单限制。这里的“支持”不代表所有原生命令、检索语法、管理操作或厂商认证均可用。 | 类别 | 数据源 | 适配器协议 | 默认端口 | 低阶能力 | | --- | --- | --- | ---: | --- | @@ -26,7 +26,7 @@ | 向量 | Qdrant | Qdrant 客户端协议 | 6334 | 同上 | | 向量 | Weaviate | HTTP API | 8080 | 同上 | -上表 21 个名称、类别和数字端口已逐项对照 `internal/model/datasource_types.go`。另 6 个关系型注册项为 `postgres:5432`、`mysql:3306`、`dm:5236`、`oracle:1521`、`yashan:1688`、`sqlserver:1433`;合计 27 款。`timescaledb` 在注册表中归于时序类,仍使用 PostgreSQL 协议。注册端口是默认配置值,不保证该值就是适配器实际连接的端口: +上表 21 个名称、类别和数字端口已逐项对照 `internal/model/datasource_types.go`。另 8 个关系型注册项为 `postgres:5432`、`vastbase:5432`、`highgo:5866`、`mysql:3306`、`dm:5236`、`oracle:1521`、`yashan:1688`、`sqlserver:1433`;合计 29 款。`timescaledb` 在注册表中归于时序类,仍使用 PostgreSQL 协议。注册端口是默认配置值,不保证该值就是适配器实际连接的端口: - HBase 注册端口为 `16020`,但当前 `gohbase` 适配器将它作为 region-server 端口拒绝;需显式配置 ZooKeeper bootstrap 端口(代码提示通常为 `2181`),不能直接用注册默认值完成连接。 - Couchbase 注册端口为 `11210`;适配器在该默认值下分别使用 HTTP 管理端口 `8091` 和查询端口 `8093`,配置 TLS 时改用 `18091` / `18093`。覆盖注册端口时按 `couchbaseURLs` 的映射规则计算,不能把 `11210` 当成 HTTP 端口。 @@ -39,7 +39,7 @@ ## 控制台与 HTTP API -- `GET /api/v1/datasource-types` 返回 27 款类型及能力档位;NoSQL 为 `nosql-connect`。 +- `GET /api/v1/datasource-types` 返回 29 款类型及能力档位;NoSQL 为 `nosql-connect`。 - 保存 NoSQL 数据源后,可用 `POST /api/v1/datasources/{id}/native-ping` 测试连接。响应提供成功状态、耗时;连接成功且适配器能读取版本时,`version` 填入服务端版本,否则为空。 - `GET /api/v1/datasources/{id}/native-schema` 通过只读网关调用适配器的 `Discover`,最多返回 100 个命名空间,并限制字段与响应字节数;截断时 `truncated=true`。无该能力返回 `NATIVE_SCHEMA_UNSUPPORTED`,连接或枚举失败返回 `NATIVE_SCHEMA_FAILED`。 - `POST /api/v1/datasources/{id}/native-query` 只开放适配器白名单内的只读命令,默认 `read_only=true`。预览最多返回 100 行、64 KiB 数据;截断时 `truncated=true`。写命令和 `read_only=false` 均返回 403 并写入管理审计;未知命令、超限参数在执行前拒绝。适配器仍执行自己的参数及语法校验。查询审计只记录命令和决定,不保存键、参数值或凭证。 diff --git a/docs/release-notes-v2.3.0.md b/docs/release-notes-v2.3.0.md new file mode 100644 index 0000000..49da3f6 --- /dev/null +++ b/docs/release-notes-v2.3.0.md @@ -0,0 +1,13 @@ +# AgentSQL v2.3.0 发布说明 + +v2.3.0 深化 SQL Server 2025 Developer 的受控只读能力。已有 TDS 连接、元数据和窄 SELECT 执行器,本版用指定 Linux x86_64 真库把严格证书校验、最小权限账号、Agent 表授权、执行计划、脱敏和持久审计接成一条可复核的 HTTP MCP 链路。无权表的静态 R010 在网关终止,避免最小权限账号对无权表执行 `SHOWPLAN` 时先产生数据库权限错误。 + +候选环境使用官方 SQL Server 2025 Developer 镜像、服务日志 build `17.00.5015`、本地测试 CA 签署的服务端证书和随机合成数据。读账号只获目标表 `SELECT` 与所在数据库 `SHOWPLAN`,SQL Server 服务端拒绝写入。真实 HTTP MCP 的允许查询返回遮蔽手机号;无权表 R010、SELECT/SHOWPLAN 撤权、独立审计库停机、SQL Server 停机均不交付原始结果。MCP Inspector 0.16.8 与 Dify MCPClient 1.17.1 分别完成允许及拒绝调用;错误 CA 和不匹配的证书主机名被拒绝。具体证据和失败后重跑过程见[候选验收](v2.3.0-acceptance.md)。 + +## 安全与兼容边界 + +- 只开放 parser、对象授权、计划与 executor 都确认的窄 SELECT;写入、批处理、存储过程、动态来源、复杂 T-SQL、系统目录和未确认结构保持拒绝。SQL Server 的 `SHOWPLAN` 是数据库级权限,本版对无权表 R010 先静态终止,不用 SA 管理账号替代运行账号。 +- B2 列级授权与 B5 会话不扩展到 SQL Server。本版未改变 metadata/audit schema;从 v2.2.0 升级仍须先对 SQLite 或 PostgreSQL metadata/audit 分库做成套备份、只读检查和隔离恢复演练。固定双网关 Kubernetes 部署仍需维护窗口,不能混跑新旧镜像。 +- 单个 Developer 容器的合成测试不等于其他 SQL Server 版本、Windows、Always On、生产证书生命周期、长期负载或客户生产部署认证;不声明完整生产支持或厂商认证。官网 Live Demo 继续使用固定合成 PostgreSQL 数据,不展示 SQL Server 真库实例。 + +正式发行 CI、安装包、签名、GHCR、官网和公网 Live Demo 的发布后证据将在封版后另行记录。 diff --git a/docs/v2.3.0-acceptance.md b/docs/v2.3.0-acceptance.md new file mode 100644 index 0000000..4d055af --- /dev/null +++ b/docs/v2.3.0-acceptance.md @@ -0,0 +1,24 @@ +# AgentSQL v2.3.0 SQL Server 指定实例候选验收 + +状态:SQL Server 2025 Developer 指定容器的真实 HTTP MCP、严格证书校验、最小权限、双客户端、两类故障关闭,以及源码候选全包回归已通过。正式包升级、合并提交 CI、发行资产、GHCR、官网与公网 Live Demo 仍须独立验收;本文件不将候选测试视为正式发布。 + +测试实例为官方 SQL Server 2025 Linux Developer x86_64 镜像 `mcr.microsoft.com/mssql/server@sha256:742481d1b1186cccc81220a1eaacef244ca8d0f7d19c8e5c70df8ae541ffbab2`,服务日志 build `17.00.5015`。服务端加载本地测试 CA 签署的证书,仅向主机回环地址开放端口。测试全部使用随机生成的合成数据库、表、登录和手机号;测试后数据库、登录和独立审计测试库剩余数均为 0。 + +| 项目 | 实际结果 | +| --- | --- | +| TLS 与版本 | `TestSQLServer2025ExecutorE2E` 通过真实 TDS 连接与 schema/sample 查询。`TestSQLServer2025StrictTLSRejectsWrongAuthorityAndNameE2E` 证实正确 CA 与服务器名称可连接;错误 CA、错误证书名称分别拒绝。测试记录在 `D:\CodexLS\outputs\agentsql-v230\preflight.md`、`tls-negative-final.log`。该证书只供隔离测试,不代表生产证书生命周期已验收。 | +| 最小权限 | 每轮创建随机 `agsql_v23_*` 测试数据库及登录,读账号仅获目标表 `SELECT` 和所在数据库 `SHOWPLAN`,未加入 `db_owner`、`db_datareader` 或 `db_datawriter`。直连允许表返回合成手机号;SQL Server 服务端拒绝 INSERT。测试结束清理登录和数据库。 | +| HTTP MCP 授权与脱敏 | `TestSQLServerHTTPMCPRealE2E` 经独立 Agent Key、真实 TDS 驱动和 `/mcp/2026-07-28` 返回允许查询及 `138****5678`,无权表返回 R010 且不触及数据库;注释、多语句、过程、OPENROWSET 与写入均不得返回允许或原始值。撤销目标表 SELECT 和数据库 SHOWPLAN 分别失败关闭;恢复后再次返回脱敏数据。持久审计存储重新打开后数量与最新 ID 一致。补齐多语句用例后的最终真库复验记录在 `D:\CodexLS\outputs\agentsql-v230\mcp-real-e2e-final.log`。 | +| 真实客户端 | MCP Inspector 0.16.8 与 Dify MCPClient 1.17.1 各完成允许与 R010 拒绝调用;允许结果含脱敏值,审计 ID 为 11/12 和 13/14。`scripts/v2.3-sqlserver-clients.ps1` 退出 0;记录在 `D:\CodexLS\outputs\agentsql-v230\clients-initial.log`。 | +| 独立审计库故障 | 独立 PostgreSQL 审计库停机时允许查询不交付原始结果;恢复后允许查询返回脱敏值并新增一条持久 `allow` 审计。`scripts/v2.3-sqlserver-fault.ps1 -Target audit` 最终复验通过;记录在 `D:\CodexLS\outputs\agentsql-v230\audit-fault-final.log`。 | +| 业务库故障 | SQL Server 停机时查询不交付原始结果,独立审计库留下 `error`;待目标只读数据库连接真正恢复后,MCP 返回脱敏结果并新增 `allow` 审计。`scripts/v2.3-sqlserver-fault.ps1 -Target business` 最终复验通过;记录在 `D:\CodexLS\outputs\agentsql-v230\business-fault-final.log`。 | +| 上版数据升级候选演练 | 校验并解出 v2.2.0 正式 amd64 包,外层 SHA256 为 `8a41566a3d61a6f284e59aad6f049caebc34d2749e3bb6385f945d9d3a916d05`。用当前源码构建的 v2.3.0 候选 `agentsqlctl`(SHA256 `f072af5084cf272673cf76737244d08703241e5d43c3c9d98a0b4ed9d61fc891`)对 v2.2.0 SQLite schema 22 和 PostgreSQL metadata 21/audit 6 分库备份进行隔离迁移、`--check`、旧包只读 `--check` 与备份恢复;数据哨兵保留。记录在 `D:\CodexLS\outputs\agentsql-v230\upgrade-sqlite-candidate.log`、`upgrade-postgres-candidate.log`。PostgreSQL 成对备份 SHA256 分别为 `f9b18b948cb229f5d7e770a0ff0dbbf5a1b1ef6bfc17a68b3056e714af70c680` 与 `08ca0605a77325ec3f43943d13d92bfb1c0da2485cc92c6162b0569b88c9842a`。正式包制成后还须按同一拓扑复验。 | +| 前端与官网候选 | 前端 17 个 Vitest 文件、56 个用例、TypeScript `--noEmit` 和 Vite 生产构建通过;三份 v2.3.0 PDF 已生成,网站本地静态校验 8/8 通过。证据在 `D:\CodexLS\outputs\agentsql-v230\web-vitest.log`、`web-typecheck.log`、`web-build.log`、`pdf-build-final.log`、`website-local-static.log`。公网仍为 v2.2.0,不能据此宣称部署完成。 | +| Go 候选回归 | Linux `go test -short -p 1 -timeout 20m ./... -count=1` 全包退出 0;`go vet -p 1 ./...` 退出 0。另有 SQL Server 真库 E2E、TLS 负向、故障与客户端专项测试。日志为 `D:\CodexLS\outputs\agentsql-v230\go-full-short.log` 与 `go-vet.log`;封版合并 SHA 的 CI 仍须单独通过。 | +| 修复过程 | 初轮无权表因 SQL Server `SHOWPLAN` 权限先返回数据库错误,没有返回静态 R010;源码改为在 SQL Server 窄路径将静态 R010 直接拒绝,再跑真实 MCP 通过。故障脚本首次因环境文件拼接错误导致用例跳过,修复脚本后重新运行。业务库首次重启在目标数据库就绪前立即断言而失败;增加目标读账号的真实查询就绪门槛后通过,仍保持故障期间不交付原始结果。失败和重试日志分别保留为 `mcp-real-e2e-initial.log`、`audit-fault-initial.log`、`business-fault-initial.log`。 | + +## 边界 + +- 这里只证明 SQL Server 2025 Developer x86_64 Docker 单实例、合成数据、严格 TLS 和窄 SELECT MCP 安全路径。`SHOWPLAN` 是数据库级权限;未授权表的 R010 在网关静态规则阶段终止,不用管理账号代替只读账号跑执行计划。 +- 尚未证明其他 SQL Server 版本、Windows、Always On、生产证书轮换、长期负载、客户拓扑、复杂 SQL、B2 列级授权、B5 会话或写入路径。不得声明厂商认证或完整生产支持。 +- 官网 Live Demo 保持合成 PostgreSQL 数据,不展示 SQL Server 真库。 diff --git a/docs/v2.3.0-design.md b/docs/v2.3.0-design.md new file mode 100644 index 0000000..c5a1862 --- /dev/null +++ b/docs/v2.3.0-design.md @@ -0,0 +1,29 @@ +# v2.3.0 SQL Server 指定实例深化设计 + +状态:实施中;v2.2.0 已正式发布,目标范围以本文和后续真库验收为准。 + +## 用户问题与目标 + +让使用 SQL Server 2025 的企业试点能以校验证书的 TDS 连接和最小权限账号完成 Agent 查询,同时在规则拒绝、数据库撤权、断线和审计库故障时保证不交付原始结果。保持注册数据库、窄只读验证与完整生产支持三个层级区分。 + +## 指定环境 + +- 官方镜像 `mcr.microsoft.com/mssql/server@sha256:742481d1b1186cccc81220a1eaacef244ca8d0f7d19c8e5c70df8ae541ffbab2`,SQL Server 2025 Developer、17.00.5015 日志 build,隔离 x86_64 测试实例。 +- 服务端证书为本地测试 CA 签署、含 DNS/IP SAN 与 serverAuth EKU,镜像以非 root 用户读取专用卷内的私钥。正确 CA 严格 TLS 查询已通过;错误 CA 拒绝、正确 CA 再次通过的差分预检见 `D:\CodexLS\outputs\agentsql-v230\preflight.md`。 +- 预检使用 SA 管理账号,仅证明底层驱动和证书路径;正式验收必须创建随机合成表与只获目标表 SELECT 和必要 SHOWPLAN 权限的读账号。Microsoft 文档要求 SHOWPLAN_XML 对所引用数据库授予 SHOWPLAN;无此权限应失败关闭,不改用管理账号。 + +## 必须交付的验收 + +1. 建立随机合成库/表、只读 login 与 user;检查 SQL Server 服务端拒绝 INSERT,读账号无 db_owner/db_datareader;清理测试对象和账号。 +2. 正确 CA 与错误 CA、证书主机名不符的连接差分;保持默认严格证书校验,不把 TrustServerCertificate 设为生产默认。 +3. 经真实 HTTP MCP 完成 Agent 身份、表级 allow、无权表 R010、窄 SELECT、手机号脱敏和持久审计;写入、多语句、动态来源和存储过程拒绝。 +4. 撤销 SELECT、SHOWPLAN 和 Agent 策略分别验证无原始行交付;恢复权限后复测,确认 SHOWPLAN_XML 会话状态清理。 +5. SQL Server 停机、独立审计库停机及恢复;验证失败时不交付原始行,恢复后返回脱敏结果并留下可复核审计。 +6. MCP Inspector 与 Dify 各完成允许与拒绝调用;用上一正式版 v2.2.0 的发布包演练 SQLite、PostgreSQL metadata/audit 升级与成套备份恢复。 +7. 独立 CI、双架构签名资产、GHCR 精确标签、官网和 Live Demo 按 `docs/RELEASE_ROUTINE.md` 交付。 + +## 不扩大声明 + +x86_64 Docker Developer 单实例测试不构成 SQL Server 各版本、Windows/Always On、生产证书生命周期或厂商认证;复杂 SQL、B2/B5、写入与完整生产支持另行验收。Live Demo 继续使用合成 PostgreSQL 数据,不宣称展示 SQL Server 真库。 + +官方参考:https://learn.microsoft.com/en-us/sql/linux/containers/security?view=sql-server-ver17;https://learn.microsoft.com/en-us/sql/linux/security/encrypted-connections?view=sql-server-ver17;https://learn.microsoft.com/en-us/sql/t-sql/statements/set-showplan-xml-transact-sql?view=sql-server-ver17。 diff --git a/internal/authorizedexecute/internal/businessdb/sqlserver_e2e_test.go b/internal/authorizedexecute/internal/businessdb/sqlserver_e2e_test.go index 09a0ab0..f87be8b 100644 --- a/internal/authorizedexecute/internal/businessdb/sqlserver_e2e_test.go +++ b/internal/authorizedexecute/internal/businessdb/sqlserver_e2e_test.go @@ -62,6 +62,42 @@ func TestSQLServer2025ExecutorE2E(t *testing.T) { require.Equal(t, [][]string{{"1", "alpha"}}, result.Rows) } +func TestSQLServer2025StrictTLSRejectsWrongAuthorityAndNameE2E(t *testing.T) { + if testing.Short() || os.Getenv("AGENTSQL_SQLSERVER_2025_E2E") != "1" { + t.Skip("set AGENTSQL_SQLSERVER_2025_E2E=1 and SQL Server connection variables to run") + } + port, err := strconv.Atoi(requiredSQLServerTestEnv(t, "AGENTSQL_SQLSERVER_PORT")) + require.NoError(t, err) + base := model.Datasource{ + ID: "sqlserver-2025-tls", Name: "SQL Server 2025 TLS E2E", DBType: "sqlserver", + Host: requiredSQLServerTestEnv(t, "AGENTSQL_SQLSERVER_HOST"), Port: port, + Database: requiredSQLServerTestEnv(t, "AGENTSQL_SQLSERVER_DATABASE"), + Username: requiredSQLServerTestEnv(t, "AGENTSQL_SQLSERVER_USERNAME"), + TLSMode: "strict", TLSCAFile: requiredSQLServerTestEnv(t, "AGENTSQL_SQLSERVER_TLS_CA_FILE"), + ConnLimit: 1, StmtTimeoutMS: 5_000, RowLimit: 10, + } + password := requiredSQLServerTestEnv(t, "AGENTSQL_SQLSERVER_PASSWORD") + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + wrongCA := base + wrongCA.TLSCAFile = requiredSQLServerTestEnv(t, "AGENTSQL_SQLSERVER_BAD_CA_FILE") + badExecutor, err := NewSQLServerExecutor(ctx, wrongCA, password, true) + require.Error(t, err, "an unrelated CA must not authenticate the server") + require.Nil(t, badExecutor) + + wrongName := base + wrongName.TLSServerName = "wrong-sqlserver-name.invalid" + badExecutor, err = NewSQLServerExecutor(ctx, wrongName, password, true) + require.Error(t, err, "a mismatched certificate hostname must not authenticate the server") + require.Nil(t, badExecutor) + + executor, err := NewSQLServerExecutor(ctx, base, password, true) + require.NoError(t, err, "the authorized CA and server name must connect") + t.Cleanup(func() { require.NoError(t, executor.Close()) }) + require.NoError(t, executor.Ping(ctx)) +} + func requiredSQLServerTestEnv(t *testing.T, key string) string { t.Helper() value := os.Getenv(key) diff --git a/internal/mcpserver/sqlserver_live_test.go b/internal/mcpserver/sqlserver_live_test.go new file mode 100644 index 0000000..0a3e20e --- /dev/null +++ b/internal/mcpserver/sqlserver_live_test.go @@ -0,0 +1,400 @@ +package mcpserver + +import ( + "context" + "crypto/rand" + "database/sql" + "encoding/hex" + "encoding/json" + "net" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + "time" + + "github.com/cuipengdba/agentsql/internal/audit" + "github.com/cuipengdba/agentsql/internal/auth" + executor "github.com/cuipengdba/agentsql/internal/authorizedexecute" + "github.com/cuipengdba/agentsql/internal/mask" + "github.com/cuipengdba/agentsql/internal/model" + "github.com/cuipengdba/agentsql/internal/pipeline" + "github.com/cuipengdba/agentsql/internal/store" + _ "github.com/microsoft/go-mssqldb" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/rs/zerolog" + "github.com/stretchr/testify/require" +) + +// TestSQLServerHTTPMCPRealE2E verifies the bounded SQL Server 2025 path with +// a real certificate, dedicated database and least-privilege login. It uses +// only randomized synthetic data and is never part of the short test suite. +func TestSQLServerHTTPMCPRealE2E(t *testing.T) { + if testing.Short() || os.Getenv("AGENTSQL_SQLSERVER_2025_MCP_E2E") != "1" { + t.Skip("set AGENTSQL_SQLSERVER_2025_MCP_E2E=1 and SQL Server test connection variables") + } + host := sqlServerE2ERequired(t, "AGENTSQL_SQLSERVER_HOST") + port, err := strconv.Atoi(sqlServerE2ERequired(t, "AGENTSQL_SQLSERVER_PORT")) + require.NoError(t, err) + caFile := sqlServerE2ERequired(t, "AGENTSQL_SQLSERVER_TLS_CA_FILE") + adminPassword := sqlServerE2ERequired(t, "AGENTSQL_SQLSERVER_ADMIN_PASSWORD") + auditDSN := os.Getenv("AGENTSQL_TEST_SQLSERVER_AUDIT_DSN") + auditFaultDir := os.Getenv("AGENTSQL_TEST_SQLSERVER_AUDIT_FAULT_DIR") + businessFaultDir := os.Getenv("AGENTSQL_TEST_SQLSERVER_BUSINESS_FAULT_DIR") + clientDir := os.Getenv("AGENTSQL_TEST_SQLSERVER_CLIENT_DIR") + timeout := 4 * time.Minute + if auditFaultDir != "" || businessFaultDir != "" || clientDir != "" { + timeout = 15 * time.Minute + } + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + open := func(database, username, password string) (*sql.DB, error) { + connection := &url.URL{ + Scheme: "sqlserver", User: url.UserPassword(username, password), + Host: net.JoinHostPort(host, strconv.Itoa(port)), + } + query := connection.Query() + query.Set("database", database) + query.Set("encrypt", "strict") + query.Set("certificate", caFile) + connection.RawQuery = query.Encode() + return sql.Open("sqlserver", connection.String()) + } + admin, err := open("master", "sa", adminPassword) + require.NoError(t, err) + require.NoError(t, admin.PingContext(ctx)) + defer admin.Close() + + random := make([]byte, 4) + _, err = rand.Read(random) + require.NoError(t, err) + suffix := strings.ToLower(hex.EncodeToString(random)) + database := "agsql_v23_" + suffix + table := "agsql_v23_t_" + suffix + deniedTable := "agsql_v23_d_" + suffix + reader := "agsql_v23_r_" + suffix + secret := make([]byte, 16) + _, err = rand.Read(secret) + require.NoError(t, err) + readerPassword := "Aa1!" + hex.EncodeToString(secret) + createdDatabase, createdLogin := false, false + t.Cleanup(func() { + cleanupCtx, stop := context.WithTimeout(context.Background(), 40*time.Second) + defer stop() + cleanup, openErr := open("master", "sa", adminPassword) + if openErr != nil || cleanup.PingContext(cleanupCtx) != nil { + t.Error("reconnect SQL Server administrator for cleanup failed") + return + } + defer cleanup.Close() + if createdDatabase { + _, dropErr := cleanup.ExecContext(cleanupCtx, + "ALTER DATABASE ["+database+"] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; DROP DATABASE ["+database+"]") + if dropErr != nil { + var remaining int + if countErr := cleanup.QueryRowContext(cleanupCtx, + "SELECT COUNT(*) FROM sys.databases WHERE name = @p1", database).Scan(&remaining); countErr != nil || remaining != 0 { + t.Error("remove temporary SQL Server database failed") + } + } + } + if createdLogin { + if _, dropErr := cleanup.ExecContext(cleanupCtx, "DROP LOGIN ["+reader+"]"); dropErr != nil { + t.Error("remove temporary SQL Server login failed") + } + } + }) + _, err = admin.ExecContext(ctx, "CREATE DATABASE ["+database+"]") + require.NoError(t, err) + createdDatabase = true + _, err = admin.ExecContext(ctx, "CREATE LOGIN ["+reader+"] WITH PASSWORD = '"+readerPassword+"', CHECK_POLICY = OFF") + require.NoError(t, err) + createdLogin = true + adminDB, err := open(database, "sa", adminPassword) + require.NoError(t, err) + require.NoError(t, adminDB.PingContext(ctx)) + defer adminDB.Close() + for _, statement := range []string{ + "CREATE TABLE [dbo].[" + table + "] ([id] int NOT NULL, [phone] varchar(32) NOT NULL)", + "CREATE TABLE [dbo].[" + deniedTable + "] ([id] int NOT NULL, [phone] varchar(32) NOT NULL)", + "INSERT INTO [dbo].[" + table + "] ([id], [phone]) VALUES (1, '13800135678')", + "INSERT INTO [dbo].[" + deniedTable + "] ([id], [phone]) VALUES (1, '13800135678')", + "CREATE USER [" + reader + "] FOR LOGIN [" + reader + "]", + "GRANT SELECT ON OBJECT::[dbo].[" + table + "] TO [" + reader + "]", + "GRANT SHOWPLAN TO [" + reader + "]", + } { + _, err = adminDB.ExecContext(ctx, statement) + require.NoError(t, err) + } + readerDB, err := open(database, reader, readerPassword) + require.NoError(t, err) + require.NoError(t, readerDB.PingContext(ctx)) + defer readerDB.Close() + var plain string + require.NoError(t, readerDB.QueryRowContext(ctx, + "SELECT [phone] FROM [dbo].["+table+"] WHERE [id] = 1").Scan(&plain)) + require.Equal(t, "13800135678", plain) + _, err = readerDB.ExecContext(ctx, "INSERT INTO [dbo].["+table+"] ([id], [phone]) VALUES (2, 'x')") + require.Error(t, err, "the database account must reject writes independently of AgentSQL") + for _, role := range []string{"db_owner", "db_datareader", "db_datawriter"} { + var member int + require.NoError(t, readerDB.QueryRowContext(ctx, "SELECT IS_MEMBER('"+role+"')").Scan(&member)) + require.Zero(t, member, "temporary reader must not have broad database role: "+role) + } + + opener := func(path string) (*store.Store, error) { + if auditDSN == "" { + return store.OpenWithSecret(ctx, path, mcpTestSecret) + } + return store.OpenMetadata(ctx, store.MetadataOptions{ + Driver: store.DialectSQLite, SQLitePath: path, AutoMigrate: true, + Audit: store.AuditOptions{Separate: true, Driver: store.DialectPostgres, + PostgresDSN: auditDSN, AutoMigrate: true}, + }, mcpTestSecret) + } + fixture := newMCPFixtureWithOpener(t, "readonly", opener) + const datasourceID = "ds-sqlserver-v23" + _, err = fixture.runtime.Store.Datasources().Create(ctx, model.Datasource{ + ID: datasourceID, Name: "SQL Server 2025 least-privilege test", DBType: "sqlserver", + Host: host, Port: port, Database: database, Username: reader, + TLSMode: "strict", TLSCAFile: caFile, ConnLimit: 2, StmtTimeoutMS: 10_000, RowLimit: 10, + }, readerPassword) + require.NoError(t, err) + policy := model.Policy{ID: "sqlserver-allowed-table", AgentID: fixture.agent.ID, + DatasourceID: datasourceID, ObjectType: "table", ObjectName: "dbo." + table, Action: "allow"} + _, err = fixture.runtime.Store.Policies().Create(ctx, policy) + require.NoError(t, err) + redactor, err := mask.NewRedactor([]mask.Rule{{Schema: "dbo", Table: table, Column: "phone", SensitiveType: mask.TypePhone, Algorithm: mask.AlgoMask}}) + require.NoError(t, err) + fixture.redactors.redactor = redactor + gateway := executor.NewGateway(true) + t.Cleanup(func() { require.NoError(t, gateway.CloseAll()) }) + fixture.runtime.Pipeline, err = pipeline.New(pipeline.Ports{ + Authenticator: auth.NewAuthenticator(fixture.runtime.Store.Agents()), + Datasources: fixture.runtime.Store.Datasources(), Policies: fixture.runtime.Store.Policies(), + Executors: gateway, Approvals: fixture.runtime.Store.Approvals(), + Audit: audit.NewRecorder(fixture.runtime.Store.AuditLogs()), Redactors: fixture.redactors, + TaskReadGrants: fixture.runtime.Store.TaskReadGrants(), + }, mcpTestSecret) + require.NoError(t, err) + handler, err := NewHTTPHandler(fixture.runtime, statefulHTTPTestConfig(100), zerolog.Nop()) + require.NoError(t, err) + httpServer := httptest.NewServer(handler) + defer httpServer.Close() + httpClient := httpServer.Client() + httpClient.Transport = bearerTransport{base: httpClient.Transport, key: fixture.handlers.apiKey} + client := mcp.NewClient(&mcp.Implementation{Name: "sqlserver-v23-http-e2e", Version: "test"}, nil) + session, err := client.Connect(ctx, &mcp.StreamableClientTransport{ + Endpoint: httpServer.URL + modernMCPPath, HTTPClient: httpClient, + }, nil) + require.NoError(t, err) + defer session.Close() + call := func(name, query string) string { + t.Helper() + arguments := map[string]any{"datasource_id": datasourceID, "sql": query} + if name == "execute_write" { + arguments["reason"] = "verify SQL Server read-only enforcement" + } + result, callErr := session.CallTool(ctx, &mcp.CallToolParams{Name: name, Arguments: arguments}) + require.NoError(t, callErr) + encoded, marshalErr := json.Marshal(result) + require.NoError(t, marshalErr) + return string(encoded) + } + allowSQL := "SELECT [phone] FROM [dbo].[" + table + "] WHERE [id] = 1" + allowed := call("query", allowSQL) + require.Contains(t, allowed, `"decision":"allow"`) + require.Contains(t, allowed, "138****5678") + require.NotContains(t, allowed, plain) + denied := call("query", "SELECT [phone] FROM [dbo].["+deniedTable+"] WHERE [id] = 1") + require.Contains(t, denied, `"decision":"deny"`) + require.Contains(t, denied, `"RuleID":"R010"`) + require.NotContains(t, denied, plain) + for _, unsafeSQL := range []string{ + "SELECT /* unsupported */ [phone] FROM [dbo].[" + table + "] WHERE [id] = 1", + "SELECT [phone] FROM [dbo].[" + table + "] WHERE [id] = 1; SELECT [phone] FROM [dbo].[" + table + "] WHERE [id] = 1", + "EXEC xp_cmdshell 'whoami'", + "SELECT [phone] FROM OPENROWSET('SQLNCLI', 'server', 'query')", + } { + unsafe := call("query", unsafeSQL) + require.NotContains(t, unsafe, `"decision":"allow"`) + require.NotContains(t, unsafe, plain) + } + write := call("execute_write", "UPDATE [dbo].["+table+"] SET [phone] = 'changed' WHERE [id] = 1") + require.NotContains(t, write, `"decision":"allow"`) + require.NotContains(t, write, plain) + + page, err := fixture.runtime.Store.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + reopened, err := opener(fixture.databasePath) + require.NoError(t, err) + defer reopened.Close() + durable, err := reopened.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + require.Equal(t, page.Total, durable.Total) + require.Equal(t, page.List[0].ID, durable.List[0].ID) + + _, err = adminDB.ExecContext(ctx, "REVOKE SELECT ON OBJECT::[dbo].["+table+"] FROM ["+reader+"]") + require.NoError(t, err) + permissionRevoked := call("query", allowSQL) + require.NotContains(t, permissionRevoked, `"decision":"allow"`) + require.NotContains(t, permissionRevoked, plain) + _, err = adminDB.ExecContext(ctx, "GRANT SELECT ON OBJECT::[dbo].["+table+"] TO ["+reader+"]") + require.NoError(t, err) + _, err = adminDB.ExecContext(ctx, "REVOKE SHOWPLAN FROM ["+reader+"]") + require.NoError(t, err) + planRevoked := call("query", allowSQL) + require.NotContains(t, planRevoked, `"decision":"allow"`) + require.NotContains(t, planRevoked, plain) + _, err = adminDB.ExecContext(ctx, "GRANT SHOWPLAN TO ["+reader+"]") + require.NoError(t, err) + permissionRestored := call("query", allowSQL) + require.Contains(t, permissionRestored, `"decision":"allow"`) + require.Contains(t, permissionRestored, "138****5678") + + require.NoError(t, fixture.runtime.Store.Policies().Delete(ctx, policy.ID)) + visibilityDenied := call("query", allowSQL) + require.Contains(t, visibilityDenied, `"decision":"deny"`) + require.NotContains(t, visibilityDenied, plain) + after, err := reopened.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + require.Equal(t, "datasource_visibility_deny", *after.List[0].Action) + require.Nil(t, after.List[0].SQLRaw) + _, err = fixture.runtime.Store.Policies().Create(ctx, policy) + require.NoError(t, err) + if auditFaultDir != "" || businessFaultDir != "" || clientDir != "" { + runSQLServerMCPExternalChecks(t, ctx, call, readerDB, "SELECT [phone] FROM [dbo].["+table+"] WHERE [id] = 1", opener, fixture.databasePath, + auditDSN, auditFaultDir, businessFaultDir, clientDir, httpServer.URL, + fixture.handlers.apiKey, datasourceID, allowSQL, + "SELECT [phone] FROM [dbo].["+deniedTable+"] WHERE [id] = 1", plain) + } +} + +func sqlServerE2ERequired(t *testing.T, name string) string { + t.Helper() + value := os.Getenv(name) + require.NotEmpty(t, value, name+" is required") + return value +} + +func runSQLServerMCPExternalChecks( + t *testing.T, ctx context.Context, call func(string, string) string, + readerDB *sql.DB, readinessSQL string, + opener func(string) (*store.Store, error), databasePath, auditDSN, + auditFaultDir, businessFaultDir, clientDir, serverURL, apiKey, datasourceID, + allowSQL, denySQL, plain string, +) { + t.Helper() + signal := func(dir, name string) { + t.Helper() + require.NoError(t, os.WriteFile(filepath.Join(dir, name), []byte("ready\n"), 0o600)) + } + waitSignal := func(dir, name string) { + t.Helper() + for { + if _, err := os.Stat(filepath.Join(dir, name)); err == nil { + return + } else if !os.IsNotExist(err) { + require.NoError(t, err) + } + select { + case <-ctx.Done(): + t.Fatal("timed out waiting for SQL Server fault/client signal: " + name) + case <-time.After(250 * time.Millisecond): + } + } + } + if auditFaultDir != "" { + require.NotEmpty(t, auditDSN, "audit outage requires independent audit database") + before, err := opener(databasePath) + require.NoError(t, err) + baseline, err := before.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + require.NoError(t, before.Close()) + signal(auditFaultDir, "ready") + waitSignal(auditFaultDir, "down") + failed := call("query", allowSQL) + require.NotContains(t, failed, `"decision":"allow"`) + require.NotContains(t, failed, plain) + signal(auditFaultDir, "down_checked") + waitSignal(auditFaultDir, "up") + restored := call("query", allowSQL) + require.Contains(t, restored, `"decision":"allow"`) + require.Contains(t, restored, "138****5678") + after, err := opener(databasePath) + require.NoError(t, err) + persisted, err := after.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + require.NoError(t, after.Close()) + require.Equal(t, baseline.Total+1, persisted.Total) + require.Equal(t, "allow", persisted.List[0].Decision) + } + if businessFaultDir != "" { + before, err := opener(databasePath) + require.NoError(t, err) + baseline, err := before.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + require.NoError(t, before.Close()) + signal(businessFaultDir, "ready") + waitSignal(businessFaultDir, "down") + failed := call("query", allowSQL) + require.NotContains(t, failed, `"decision":"allow"`) + require.NotContains(t, failed, plain) + signal(businessFaultDir, "down_checked") + waitSignal(businessFaultDir, "up") + ready := false + for attempt := 0; attempt < 30; attempt++ { + readCtx, cancel := context.WithTimeout(ctx, 2*time.Second) + var value string + err := readerDB.QueryRowContext(readCtx, readinessSQL).Scan(&value) + cancel() + if err == nil && value == plain { + ready = true + break + } + select { + case <-ctx.Done(): + t.Fatal("SQL Server test database did not recover") + case <-time.After(time.Second): + } + } + require.True(t, ready, "SQL Server reader must recover before MCP recovery assertion") + restored := call("query", allowSQL) + require.Contains(t, restored, `"decision":"allow"`) + require.Contains(t, restored, "138****5678") + after, err := opener(databasePath) + require.NoError(t, err) + persisted, err := after.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + require.NoError(t, after.Close()) + require.Equal(t, baseline.Total+2, persisted.Total) + require.Equal(t, "allow", persisted.List[0].Decision) + require.Equal(t, "error", persisted.List[1].Decision) + } + if clientDir == "" { + return + } + before, err := opener(databasePath) + require.NoError(t, err) + page, err := before.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + require.NoError(t, before.Close()) + info, err := json.Marshal(map[string]string{ + "url": serverURL + "/mcp", "api_key": apiKey, "datasource_id": datasourceID, + "allow_sql": allowSQL, "deny_sql": denySQL, "deny_rule": "R010", + }) + require.NoError(t, err) + infoPath := filepath.Join(clientDir, "client.json") + require.NoError(t, os.WriteFile(infoPath, info, 0o600)) + defer os.Remove(infoPath) + waitSignal(clientDir, "clients_done") + after, err := opener(databasePath) + require.NoError(t, err) + defer after.Close() + latest, err := after.AuditLogs().Page(ctx, 1, 30) + require.NoError(t, err) + require.Equal(t, page.Total+4, latest.Total) +} diff --git a/internal/pipeline/b2_mysql_routing_test.go b/internal/pipeline/b2_mysql_routing_test.go index 35c034a..dc346b6 100644 --- a/internal/pipeline/b2_mysql_routing_test.go +++ b/internal/pipeline/b2_mysql_routing_test.go @@ -302,6 +302,12 @@ func TestDeniedSelectBindingExceptionOnlyAppliesToR010(t *testing.T) { Decision: model.DecisionDeny, Hits: []model.RuleHit{r010}, })) + sqlServerAST := &model.AST{Dialect: model.DialectSQLServer, StmtType: model.StmtType("SELECT")} + require.True(t, shouldExplain(sqlServerAST)) + require.False(t, shouldBindDeniedSelect(sqlServerAST, model.Assessment{ + Decision: model.DecisionDeny, + Hits: []model.RuleHit{r010}, + })) } func TestFunctionQualificationFailureIsAuditedAuthorizationDeny(t *testing.T) { diff --git a/internal/pipeline/pipeline.go b/internal/pipeline/pipeline.go index 1c17dff..cbf3e03 100644 --- a/internal/pipeline/pipeline.go +++ b/internal/pipeline/pipeline.go @@ -1349,9 +1349,12 @@ func shouldExplain(ast *model.AST) bool { } func shouldBindDeniedSelect(ast *model.AST, assessment model.Assessment) bool { - // Without a verified Yashan or least-privilege Oracle plan binder, a - // static R010 deny is final and must not touch the database for a plan. - if ast == nil || ast.Dialect == model.DialectYashan || ast.Dialect == model.DialectOracle || ast.StmtType != model.StmtType("SELECT") || assessment.Decision != model.DecisionDeny { + // A least-privilege SQL Server account cannot SHOWPLAN a table for which + // it has no SELECT grant. As with Yashan and Oracle, a static R010 deny is + // final and must not turn into a database permission error during binding. + if ast == nil || ast.Dialect == model.DialectYashan || ast.Dialect == model.DialectOracle || + ast.Dialect == model.DialectSQLServer || ast.StmtType != model.StmtType("SELECT") || + assessment.Decision != model.DecisionDeny { return false } foundObjectDeny := false diff --git a/internal/version/version.go b/internal/version/version.go index 41f2ddd..a9bb14b 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -2,4 +2,4 @@ package version // Version is overridden in release builds through -ldflags -X. -var Version = "v2.2.0" +var Version = "v2.3.0" diff --git a/scripts/build-ghcr-multiarch.ps1 b/scripts/build-ghcr-multiarch.ps1 index 78362db..9b97ec0 100644 --- a/scripts/build-ghcr-multiarch.ps1 +++ b/scripts/build-ghcr-multiarch.ps1 @@ -10,7 +10,7 @@ [CmdletBinding()] param( [ValidatePattern('^v[0-9]+\.[0-9]+\.[0-9]+$')] - [string]$Version = 'v2.2.0', + [string]$Version = 'v2.3.0', [string]$Builder = 'agentsql-multiarch', diff --git a/scripts/build-release-linux.sh b/scripts/build-release-linux.sh index e5c15a0..d577ffd 100644 --- a/scripts/build-release-linux.sh +++ b/scripts/build-release-linux.sh @@ -3,7 +3,7 @@ set -eu ARCH=${ARCH:-amd64} -VERSION=${VERSION:-v2.2.0} +VERSION=${VERSION:-v2.3.0} GO_VERSION=${GO_VERSION:-1.26.9} GOPROXY=${GOPROXY:-https://goproxy.cn,direct} YASHAN_CLIENT_VERSION=${YASHAN_CLIENT_VERSION:-23.4.7.100} diff --git a/scripts/package-release.sh b/scripts/package-release.sh index cbbf211..8577eb1 100644 --- a/scripts/package-release.sh +++ b/scripts/package-release.sh @@ -3,7 +3,7 @@ set -eu umask 022 -VERSION=${VERSION:-v2.2.0} +VERSION=${VERSION:-v2.3.0} ARCH=${ARCH:-amd64} SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH:-1704067200} diff --git a/scripts/release-dryrun.ps1 b/scripts/release-dryrun.ps1 index 66bd391..8539b45 100644 --- a/scripts/release-dryrun.ps1 +++ b/scripts/release-dryrun.ps1 @@ -4,7 +4,7 @@ [CmdletBinding()] param( [ValidatePattern('^v[0-9]+\.[0-9]+\.[0-9]+$')] - [string]$Version = 'v2.2.0', + [string]$Version = 'v2.3.0', [string]$OutputDirectory, diff --git a/scripts/v2.3-sqlserver-clients.ps1 b/scripts/v2.3-sqlserver-clients.ps1 new file mode 100644 index 0000000..f7f33ed --- /dev/null +++ b/scripts/v2.3-sqlserver-clients.ps1 @@ -0,0 +1,69 @@ +param( + [string]$SQLServerContainer = 'agentsql-v230-mssql', + [string]$TempRoot = 'D:\CodexLS\tmp\agentsql-v230', + [string]$EnvironmentFile = 'D:\CodexLS\tmp\agentsql-v230\mssql\mcp-test.env', + [string]$CAFile = 'D:\CodexLS\tmp\agentsql-v230\mssql\ca.pem' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +$repo = Split-Path -Parent $PSScriptRoot +$moduleCache = 'D:\CodexLS\cache\agentsql-go-mod-linux' +$buildCache = 'D:\CodexLS\cache\agentsql-go-build-linux' +foreach ($path in @($repo, $TempRoot, $moduleCache, $buildCache)) { + if (-not (Test-Path -LiteralPath $path -PathType Container)) { throw "Required directory is missing: $path" } +} +foreach ($path in @($EnvironmentFile, $CAFile)) { + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Required file is missing: $path" } +} +if ((& docker inspect $SQLServerContainer --format '{{.State.Running}}').Trim() -ne 'true') { + throw 'The isolated SQL Server test container is not running' +} +$clientDir = Join-Path $TempRoot ('clients-' + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $clientDir | Out-Null +$testContainer = 'agentsql-v230-clients-' + [guid]::NewGuid().ToString('N').Substring(0, 8) +try { + & docker run -d --name $testContainer --network "container:$SQLServerContainer" ` + --env-file $EnvironmentFile --env AGENTSQL_SQLSERVER_HOST=127.0.0.1 ` + --env AGENTSQL_TEST_SQLSERVER_CLIENT_DIR=/clients ` + --mount "type=bind,source=$repo,target=/src" ` + --mount "type=bind,source=$moduleCache,target=/gomod" ` + --mount "type=bind,source=$buildCache,target=/buildcache" ` + --mount "type=bind,source=$CAFile,target=/test-ca.pem,readonly" ` + --mount "type=bind,source=$clientDir,target=/clients" ` + --workdir /src --env GOPROXY=off --env GOTOOLCHAIN=local ` + --env GOMODCACHE=/gomod --env GOCACHE=/buildcache ` + golang:1.26-bookworm /usr/local/go/bin/go test -timeout 12m -count=1 -v ` + -run '^TestSQLServerHTTPMCPRealE2E$' ./internal/mcpserver | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'Could not start SQL Server client test container' } + $infoPath = Join-Path $clientDir 'client.json' + $deadline = (Get-Date).AddMinutes(8) + while (-not (Test-Path -LiteralPath $infoPath)) { + if ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -ne 'true') { + & docker logs $testContainer + throw 'SQL Server MCP test exited before external clients connected' + } + if ((Get-Date) -ge $deadline) { throw 'Timed out waiting for SQL Server MCP gateway' } + Start-Sleep -Seconds 1 + } + $info = Get-Content -LiteralPath $infoPath -Raw | ConvertFrom-Json + & (Join-Path $PSScriptRoot 'v1.5-mcp-client-probes.ps1') ` + -Network "container:$SQLServerContainer" -MCPURL $info.url -ApiKey $info.api_key ` + -DatasourceID $info.datasource_id -AllowSQL $info.allow_sql ` + -DenySQL $info.deny_sql -DenyRule $info.deny_rule + if ($LASTEXITCODE -ne 0 -or -not $?) { throw 'SQL Server Inspector/Dify client probe failed' } + Set-Content -LiteralPath (Join-Path $clientDir 'clients_done') -Value 'four calls completed' + $deadline = (Get-Date).AddMinutes(2) + while ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -eq 'true') { + if ((Get-Date) -ge $deadline) { throw 'SQL Server MCP client test did not finish' } + Start-Sleep -Seconds 1 + } + $testExit = (& docker inspect $testContainer --format '{{.State.ExitCode}}').Trim() + & docker logs $testContainer + if ($testExit -ne '0') { throw "SQL Server MCP client test failed with exit=$testExit" } + Write-Output 'V230_SQLSERVER_INSPECTOR_DIFY=PASS' +} +finally { + Remove-Item -LiteralPath (Join-Path $clientDir 'client.json') -Force -ErrorAction SilentlyContinue + & docker rm -f $testContainer 2>$null | Out-Null +} diff --git a/scripts/v2.3-sqlserver-fault.ps1 b/scripts/v2.3-sqlserver-fault.ps1 new file mode 100644 index 0000000..f40342d --- /dev/null +++ b/scripts/v2.3-sqlserver-fault.ps1 @@ -0,0 +1,121 @@ +param( + [ValidateSet('audit', 'business')][string]$Target = 'audit', + [string]$SQLServerContainer = 'agentsql-v230-mssql', + [string]$AuditContainer = 'agentsql-v210-audit', + [string]$TempRoot = 'D:\CodexLS\tmp\agentsql-v230', + [string]$EnvironmentFile = 'D:\CodexLS\tmp\agentsql-v230\mssql\mcp-test.env', + [string]$CAFile = 'D:\CodexLS\tmp\agentsql-v230\mssql\ca.pem' +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest +$repo = Split-Path -Parent $PSScriptRoot +$moduleCache = 'D:\CodexLS\cache\agentsql-go-mod-linux' +$buildCache = 'D:\CodexLS\cache\agentsql-go-build-linux' +foreach ($path in @($repo, $TempRoot, $moduleCache, $buildCache)) { + if (-not (Test-Path -LiteralPath $path -PathType Container)) { throw "Required directory is missing: $path" } +} +foreach ($path in @($EnvironmentFile, $CAFile)) { + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Required file is missing: $path" } +} +foreach ($container in @($SQLServerContainer, $AuditContainer)) { + if ((& docker inspect $container --format '{{.State.Running}}').Trim() -ne 'true') { + throw "Required test database is not running: $container" + } +} +$faultDir = Join-Path $TempRoot ($Target + '-fault-' + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $faultDir | Out-Null +$dbName = 'agv23_' + [guid]::NewGuid().ToString('N').Substring(0, 10) +$testContainer = 'agentsql-v230-' + $Target + '-' + [guid]::NewGuid().ToString('N').Substring(0, 8) +$auditPasswordLine = & docker inspect $AuditContainer --format '{{range .Config.Env}}{{println .}}{{end}}' | + Where-Object { $_ -like 'POSTGRES_PASSWORD=*' } | Select-Object -First 1 +if (-not $auditPasswordLine) { throw 'Audit test database credential is unavailable' } +$auditPassword = $auditPasswordLine.Substring('POSTGRES_PASSWORD='.Length) +$dsn = "postgres://postgres:${auditPassword}@host.docker.internal:25432/${dbName}?sslmode=disable" +$privateFaultEnv = Join-Path $faultDir 'fault.env' +$faultEnvName = if ($Target -eq 'audit') { 'AGENTSQL_TEST_SQLSERVER_AUDIT_FAULT_DIR' } else { 'AGENTSQL_TEST_SQLSERVER_BUSINESS_FAULT_DIR' } +$lines = @(Get-Content -LiteralPath $EnvironmentFile) + @("AGENTSQL_TEST_SQLSERVER_AUDIT_DSN=$dsn", "$faultEnvName=/fault") +[System.IO.File]::WriteAllText($privateFaultEnv, ($lines -join "`n") + "`n", [System.Text.UTF8Encoding]::new($false)) +$auditPassword = $null +$dsn = $null +$faultContainer = if ($Target -eq 'audit') { $AuditContainer } else { $SQLServerContainer } +$network = if ($Target -eq 'audit') { "container:$SQLServerContainer" } else { 'agentsql-v230-net' } +$testHost = if ($Target -eq 'audit') { '127.0.0.1' } else { $SQLServerContainer } +$faultStopped = $false +$createdDB = $false +try { + & docker exec $AuditContainer psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c "CREATE DATABASE $dbName" | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'Could not create isolated audit database' } + $createdDB = $true + & docker run -d --name $testContainer --network $network --env-file $privateFaultEnv ` + --env "AGENTSQL_SQLSERVER_HOST=$testHost" ` + --mount "type=bind,source=$repo,target=/src" ` + --mount "type=bind,source=$moduleCache,target=/gomod" ` + --mount "type=bind,source=$buildCache,target=/buildcache" ` + --mount "type=bind,source=$CAFile,target=/test-ca.pem,readonly" ` + --mount "type=bind,source=$faultDir,target=/fault" ` + --workdir /src --env GOPROXY=off --env GOTOOLCHAIN=local ` + --env GOMODCACHE=/gomod --env GOCACHE=/buildcache ` + golang:1.26-bookworm /usr/local/go/bin/go test -timeout 17m -count=1 -v ` + -run '^TestSQLServerHTTPMCPRealE2E$' ./internal/mcpserver | Out-Null + if ($LASTEXITCODE -ne 0) { throw 'Could not start SQL Server fault test container' } + $deadline = (Get-Date).AddMinutes(8) + while (-not (Test-Path -LiteralPath (Join-Path $faultDir 'ready'))) { + if ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -ne 'true') { + & docker logs $testContainer + throw 'SQL Server fault test exited before ready signal' + } + if ((Get-Date) -ge $deadline) { throw 'Timed out waiting for SQL Server fault test' } + Start-Sleep -Seconds 1 + } + & docker stop $faultContainer | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not stop $Target test database" } + $faultStopped = $true + Set-Content -LiteralPath (Join-Path $faultDir 'down') -Value 'stopped' + $deadline = (Get-Date).AddMinutes(2) + while (-not (Test-Path -LiteralPath (Join-Path $faultDir 'down_checked'))) { + if ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -ne 'true') { + & docker logs $testContainer + throw 'SQL Server MCP test exited during database outage' + } + if ((Get-Date) -ge $deadline) { throw 'Timed out waiting for fail-closed check' } + Start-Sleep -Seconds 1 + } + & docker start $faultContainer | Out-Null + if ($LASTEXITCODE -ne 0) { throw "Could not restart $Target test database" } + $faultStopped = $false + $ready = $false + for ($i = 0; $i -lt 120; $i++) { + if ($Target -eq 'audit') { + & docker exec $AuditContainer pg_isready -U postgres -d $dbName *> $null + } else { + & docker exec $SQLServerContainer sh -c 'SQLCMDPASSWORD="$MSSQL_SA_PASSWORD" /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -C -Q "SELECT 1" -b' *> $null + } + $ready = $LASTEXITCODE -eq 0 + if ($ready) { break } + Start-Sleep -Seconds 5 + } + if (-not $ready) { throw "$Target test database did not recover" } + Set-Content -LiteralPath (Join-Path $faultDir 'up') -Value 'recovered' + $deadline = (Get-Date).AddMinutes(3) + while ((& docker inspect $testContainer --format '{{.State.Running}}').Trim() -eq 'true') { + if ((Get-Date) -ge $deadline) { throw 'SQL Server MCP fault test did not finish' } + Start-Sleep -Seconds 1 + } + $testExit = (& docker inspect $testContainer --format '{{.State.ExitCode}}').Trim() + & docker logs $testContainer + if ($testExit -ne '0') { throw "SQL Server MCP fault test failed with exit=$testExit" } + Write-Output "V230_SQLSERVER_${Target}_FAULT=PASS" +} +finally { + if ($faultStopped) { & docker start $faultContainer | Out-Null } + if ($createdDB -and $Target -eq 'audit' -and + (& docker inspect $AuditContainer --format '{{.State.Running}}').Trim() -ne 'true') { + & docker start $AuditContainer | Out-Null + } + if ($createdDB) { + & docker exec $AuditContainer psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c "DROP DATABASE IF EXISTS $dbName" *> $null + } + Remove-Item -LiteralPath $privateFaultEnv -Force -ErrorAction SilentlyContinue + & docker rm -f $testContainer 2>$null | Out-Null +} diff --git a/website/public/assets/docs/agentsql-getting-started-v2.3.0.pdf b/website/public/assets/docs/agentsql-getting-started-v2.3.0.pdf new file mode 100644 index 0000000..17c1cff Binary files /dev/null and b/website/public/assets/docs/agentsql-getting-started-v2.3.0.pdf differ diff --git a/website/public/assets/docs/agentsql-mcp-integrations-v2.3.0.pdf b/website/public/assets/docs/agentsql-mcp-integrations-v2.3.0.pdf new file mode 100644 index 0000000..cd593f5 Binary files /dev/null and b/website/public/assets/docs/agentsql-mcp-integrations-v2.3.0.pdf differ diff --git a/website/public/assets/docs/agentsql-user-guide-v2.3.0.pdf b/website/public/assets/docs/agentsql-user-guide-v2.3.0.pdf new file mode 100644 index 0000000..42b5f58 Binary files /dev/null and b/website/public/assets/docs/agentsql-user-guide-v2.3.0.pdf differ diff --git a/website/public/demo/index.html b/website/public/demo/index.html index e52dda2..6a723b2 100644 --- a/website/public/demo/index.html +++ b/website/public/demo/index.html @@ -40,7 +40,7 @@
-
AgentSQL v2.2.0 · Live Demo 安全入口
+
AgentSQL v2.3.0 · Live Demo 安全入口

先确认边界,再进入演示

此环境只用于体验 AgentSQL 的控制台与六个预置安全剧本,不是生产服务,也不连接任何真实业务数据库。

@@ -56,7 +56,7 @@

先确认边界,再进入演示

我已了解,进入演示 返回官网
-

控制台由独立的单网关演示源提供;入口地址保持为 https://agentsql.cn/demo/。公开演示未启用双网关 Kubernetes 配置或可选人员委托模式;版本边界见发布说明。

+

控制台由独立的单网关演示源提供;入口地址保持为 https://agentsql.cn/demo/。公开演示未启用双网关 Kubernetes 配置或可选人员委托模式;版本边界见发布说明。

diff --git a/website/public/index.html b/website/public/index.html index 702ea0f..69500de 100644 --- a/website/public/index.html +++ b/website/public/index.html @@ -75,14 +75,14 @@
-
v2.2.0
+
v2.3.0

AI Agent 的数据库安全网关

AgentSQL(中文名:智盾)是面向 AI Agent 的数据库安全网关与 MCP Server。模型发出的 PostgreSQL / MySQL 请求在到达数据库前,经过身份认证、SQL 解析、静态与动态风险检查、四态决策与受控执行;结果按配置脱敏,并在应用层留下持久审计。

-

v2.2.0 在 Oracle AI Database 26ai Free、FREEPDB1 指定合成实例上,验证最小权限账号的真实 HTTP MCP、R010 拒绝、手机号脱敏、双客户端及业务库和独立审计库故障关闭。Oracle 商业版、完整生产支持和预执行计划未验证;公开 Live Demo 仍使用单网关合成 PostgreSQL 数据。

+

v2.3.0 在 SQL Server 2025 Developer 指定 Linux x86_64 合成实例上,验证严格证书校验、最小权限账号的真实 HTTP MCP、R010、手机号脱敏、双客户端及业务库和独立审计库故障关闭。其他版本、Windows、Always On、生产证书轮换与完整生产支持未验证;公开 Live Demo 仍使用单网关合成 PostgreSQL 数据。

v0.5.0 起 Apache-2.0 开源 · Go 单二进制交付(glibc / cgo 构建)· 自托管 · 无第三方遥测依赖

它不是 BI,也不是 ORM 或 Text2SQL,而是模型与数据库之间一道默认拒绝的安全闸门。

@@ -200,24 +200,24 @@

让模型的数据库访问可约束、可解释、可追溯

文档 / 下载

从本地演示到 MCP 接入

按真实部署状态选择入口。文档对应源码仓库 github.com/cuipengdba/agentsql。

-
01

快速上手

用本地 Live Demo 看效果,再按默认拒绝的顺序接入自己的数据库。

-
02

使用手册

按控制台菜单了解审计、Agent、数据源、权限、规则、审批与脱敏边界。

-
03

MCP 接入指南

对照客户端能力选择现有 /mcp、独立无状态 /mcp/2026-07-28 或 stdio,查看工具与拒绝边界。

+
01

快速上手

用本地 Live Demo 看效果,再按默认拒绝的顺序接入自己的数据库。

+
02

使用手册

按控制台菜单了解审计、Agent、数据源、权限、规则、审批与脱敏边界。

+
03

MCP 接入指南

对照客户端能力选择现有 /mcp、独立无状态 /mcp/2026-07-28 或 stdio,查看工具与拒绝边界。

-

更多资料

部署指南 · 在线演示说明

v2.2.0 提供 Linux amd64 / arm64 安装包与多架构镜像;Live Demo 入口仅使用合成数据。

GHCR 多架构拉取(自动匹配 amd64 / arm64)

docker pull ghcr.io/cuipengdba/agentsql:v2.2.0

一键安装(Linux amd64 / arm64)

curl -fsSL https://github.com/cuipengdba/agentsql/releases/download/v2.2.0/install.sh | sudo sh -s -- install --version v2.2.0

v0.4.0 起提供 linux/arm64 原生 glibc 包 · x86_64 / amd64 或 aarch64 / arm64 · glibc ≥ 2.28 · systemd · 需要 sudo/root

- +

更多资料

部署指南 · 在线演示说明

v2.3.0 提供 Linux amd64 / arm64 安装包与多架构镜像;Live Demo 入口仅使用合成数据。

GHCR 多架构拉取(自动匹配 amd64 / arm64)

docker pull ghcr.io/cuipengdba/agentsql:v2.3.0

一键安装(Linux amd64 / arm64)

curl -fsSL https://github.com/cuipengdba/agentsql/releases/download/v2.3.0/install.sh | sudo sh -s -- install --version v2.3.0

v0.4.0 起提供 linux/arm64 原生 glibc 包 · x86_64 / amd64 或 aarch64 / arm64 · glibc ≥ 2.28 · systemd · 需要 sudo/root

+
-

兼容性

v2.2.0 兼容矩阵

本版补齐 Oracle Free 指定实例的最小权限、受控只读、脱敏、审计与故障关闭证据;其他数据库范围沿用 v2.1.0。下表按实际能力与指定环境证据分档。

+

兼容性

v2.3.0 兼容矩阵

本版补齐 SQL Server 2025 Developer 指定实例的严格 TLS、最小权限、受控只读、脱敏、审计与故障关闭证据;其他数据库范围沿用 v2.2.0。下表按实际能力与指定环境证据分档。

8 大类 / 29 款已注册瀚高 HighGo 已计入;另列金仓 KingbaseES V9 的指定实例 PG 协议验证
- + @@ -225,7 +225,7 @@

让模型的数据库访问可约束、可解释、可追溯

- + @@ -266,11 +266,11 @@

让模型的数据库访问可约束、可解释、可追溯

生态与兼容

国产数据库生态适配进展

v1.8.0 在 Vastbase G100 Build 9 PG 模式的指定实例补充 TLS 服务端身份校验、证书与凭据轮换。其他版本、模式、正式主备切换和完整生产支持仍须单独验证。欢迎数据库厂商联合验证。

-

国产数据库生态适配矩阵(v2.2.0)

+

国产数据库生态适配矩阵(v2.3.0)

表格可左右滑动,查看完整验证说明。

AgentSQL v2.2.0 数据库兼容矩阵:8 大类、29 款已注册数据库,金仓 KingbaseES V9 另列,共 30 行。AgentSQL v2.3.0 数据库兼容矩阵:8 大类、29 款已注册数据库,金仓 KingbaseES V9 另列,共 30 行。
类别数据库适配状态边界说明
关系型PostgreSQL 14–18🟢 已适配 · 完整防护全链路实测通过
Oracle AI Database 26ai Free🔵 指定版本受控只读FREEPDB1、服务端 23.26.3.0.0:最小权限 HTTP MCP、R010、脱敏、双客户端、权限撤销和故障关闭通过;预执行计划、商业版及完整生产支持未验证
达梦 DM8🟢 指定场景闭环Pack3、COMPATIBLE_MODE=0 真库只读链路
崖山 YashanDB🔵 指定版本受控只读23.4.1.109 指定实例:最小权限 HTTP MCP、R010、脱敏、双客户端与独立持久审计通过;TLS、主备和完整生产支持未验证
SQL Server 2025🔵 已适配 · 受控只读连接 / 元数据 / 只读查询子集
SQL Server 2025 Developer🔵 指定版本受控只读Linux x86_64 单实例合成数据:严格 TLS、最小权限 HTTP MCP、R010、脱敏、双客户端、SELECT/SHOWPLAN 撤权及故障关闭通过;其他版本、Windows/Always On、生产证书轮换和完整生产支持未验证
Vastbase G100🔵 指定版本受控只读V3.0 Build 9 PG 模式:有界单表 SELECT、授权、脱敏、MCP 和持久审计实测;v1.8.0 验证 TLS、证书与凭据轮换。临时许可,HA、其他版本和生产负载未测
瀚高 HighGo🔵 指定版本受控只读独立 highgo 类型;9.0.10 企业版 PG 模式的有界 SELECT、权限、掩码、客户端与故障链路通过
金仓 KingbaseES V9(另列)🟢 指定场景闭环V9R1C10 PG 模式基础闭环,不计入 29
- + @@ -300,11 +300,11 @@

让模型的数据库访问可约束、可解释、可追溯

开始使用

快速开始

在 Docker 与依赖网络已就绪时通常数分钟可完成(受网络环境影响)。GHCR tag 会自动匹配 linux/amd64 或 linux/arm64;源码路径仍以仓库 README 为准。

GHCRamd64 / arm64
# Docker 自动选择当前平台镜像
-docker pull ghcr.io/cuipengdba/agentsql:v2.2.0
+docker pull ghcr.io/cuipengdba/agentsql:v2.3.0
 
 # 推荐使用仓库 quickstart 生成凭据并启动
-curl -fsSL https://raw.githubusercontent.com/cuipengdba/agentsql/v2.2.0/scripts/quickstart.sh -o quickstart.sh
-sh quickstart.sh --version v2.2.0
默认仅绑定 127.0.0.1:7780
+curl -fsSL https://raw.githubusercontent.com/cuipengdba/agentsql/v2.3.0/scripts/quickstart.sh -o quickstart.sh +sh quickstart.sh --version v2.3.0
默认仅绑定 127.0.0.1:7780
Live Demo合成数据
# 复制演示环境样例并替换占位凭据
 cp examples/docker/demo.env.example demo/demo.env
 
@@ -357,8 +357,9 @@ 

让模型的数据库访问可约束、可解释、可追溯

  • v1.9.0Dify / Inspector 接入向导与审计定位已发布阶段
  • v2.0.0固定双网关部署与 PostgreSQL 列策略登记已发布阶段
  • v2.1.0崖山指定实例受控只读与预检拒绝审计已发布阶段
  • -
  • v2.2.0Oracle Free 指定实例受控只读与故障关闭当前正式版本
  • -
  • 下一阶段国产与商业数据库适配
  • +
  • v2.2.0Oracle Free 指定实例受控只读与故障关闭已发布阶段
  • +
  • v2.3.0SQL Server 2025 指定实例严格 TLS 与受控只读当前正式版本
  • +
  • 下一阶段openGauss 指定实例有界适配
  • 后续方向企业身份与审计增强SSO/RBAC、WORM 选项
  • 后续方向高可用与集中管控
  • diff --git a/website/tools/build-docs-pdf.mjs b/website/tools/build-docs-pdf.mjs index b7f7e7b..e56fc24 100644 --- a/website/tools/build-docs-pdf.mjs +++ b/website/tools/build-docs-pdf.mjs @@ -16,7 +16,7 @@ import { tmpdir } from 'node:os'; import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -const VERSION = 'v2.2.0'; +const VERSION = 'v2.3.0'; const REPOSITORY_BLOB = `https://github.com/cuipengdba/agentsql/blob/${VERSION}`; const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)); const REPOSITORY_ROOT = resolve(SCRIPT_DIR, '..', '..'); @@ -29,19 +29,19 @@ const FIXED_PDF_DATE = "D:20261019000000+00'00'"; const DOCUMENTS = [ { source: resolve(DOCS_ROOT, 'GETTING_STARTED.md'), - output: 'agentsql-getting-started-v2.2.0.pdf', + output: 'agentsql-getting-started-v2.3.0.pdf', title: '快速上手', slug: 'getting-started', }, { source: resolve(DOCS_ROOT, 'USER_GUIDE.md'), - output: 'agentsql-user-guide-v2.2.0.pdf', + output: 'agentsql-user-guide-v2.3.0.pdf', title: '使用手册', slug: 'user-guide', }, { source: resolve(DOCS_ROOT, 'INTEGRATIONS.md'), - output: 'agentsql-mcp-integrations-v2.2.0.pdf', + output: 'agentsql-mcp-integrations-v2.3.0.pdf', title: 'MCP 接入指南', slug: 'mcp-integrations', }, @@ -813,6 +813,9 @@ async function main() { 'agentsql-getting-started-v2.1.0.pdf', 'agentsql-mcp-integrations-v2.1.0.pdf', 'agentsql-user-guide-v2.1.0.pdf', + 'agentsql-getting-started-v2.2.0.pdf', + 'agentsql-mcp-integrations-v2.2.0.pdf', + 'agentsql-user-guide-v2.2.0.pdf', ]; const expectedFiles = [...DOCUMENTS.map((document) => document.output), ...archivedFiles].sort(); if (JSON.stringify(actualFiles) !== JSON.stringify(expectedFiles)) {
    AgentSQL v2.2.0 国产数据库生态适配进展;状态以指定版本、镜像、拓扑及仓库证据为准。AgentSQL v2.3.0 国产数据库生态适配进展;状态以指定版本、镜像、拓扑及仓库证据为准。
    数据库适配状态说明
    中电科金仓 KingbaseES🟢 PG 协议指定子集实测通过仅 V9R1C10、PG 模式、单实例合成表:只读角色拒写,受控 SELECT、手机号脱敏、R006 拒绝和持久审计通过;v1.6.0 补齐故障关闭,v1.7.0 验证 CA 与主机名校验、错误证书拒绝、证书及账号密码轮换。其他模式、复杂查询、正式主备切换与生产负载未验证。验收记录。