新闻详情

新闻详情

首页 / 资讯中心 / 详情

MCP工具安全网关:动态验证阻断AI Agent后门风险

发布时间:2026/9/29 19:48:46来源:尧图网络
MCP工具安全网关:动态验证阻断AI Agent后门风险
1. 为什么 AI Agent 的工具调用正在变成最危险的“后门”最近帮一家做智能客服中台的客户做安全复盘他们上线了基于 MCP 协议的 AI Agent 架构——前端是用户对话界面中间层是 LLM 编排引擎底层通过 MCPModel Control Protocol标准协议对接几十个外部工具CRM 查询、工单创建、知识库检索、API 网关、甚至财务系统审批接口。表面看是“解耦标准化”实际跑通两周后我们发现一个反直觉的事实所有高危漏洞没出现在 LLM 提示词里也没藏在模型权重中而是精准卡在 MCP 工具注册表的 JSON 配置字段里。比如他们注册了一个名为get_user_profile的工具声明路径是/api/v2/user/profile但真实后端服务其实监听在/internal/debug/user/profile?auth_bypass1另一个叫send_notification的工具schema 声明只接受{user_id: string, content: string}可实际实现代码里偷偷解析了X-Admin-Token请求头——而这个 header 根本没写在 MCP 的 OpenAPI 描述里。更致命的是Agent 引擎在调用前只校验工具名是否存在、参数是否符合 schema从不验证工具声明路径与真实 endpoint 是否一致也不检查 runtime 时是否注入了未声明的 headers 或 query 参数。这根本不是“AI 安全”问题而是“AI 工具链治理失效”问题。MCP 本身是中立协议它像 USB-C 接口标准——规定插头形状和电压范围但不保证你插进去的是手机充电器还是自制的高压电击器。当企业把工具注册、权限分配、调用审计全交给运维手动维护 YAML 文件再让 Agent 引擎无差别执行时就等于在防火墙上开了 50 个带密码锁的窗口却把钥匙串挂在窗台上。Rug Pull 不需要黑进模型只要在工具注册环节把transfer_funds工具的真实 endpoint 指向钓鱼服务器工具投毒不需要篡改 Python 代码只需把validate_payment工具的 schema 中amount字段的maximum从 10000 改成 999999999认证绕过更简单——在 MCP 的authentication字段填none然后在真实 API 层用硬编码 token 绕过网关。我翻了 7 个主流 MCP 实现库包括mcp-server-python、playwright-mcp、vscode-mcp-client发现它们共性缺陷工具发现阶段只做静态 JSON 解析调用执行阶段只做参数类型校验全程跳过动态行为验证、endpoint 真实性核验、调用链路签名审计。这不是能力缺失而是设计哲学错位——MCP 规范文档里明确写着 “MCP is a protocol for tool discovery and invocation, not a security framework”但现实是90% 的落地项目把它当成了安全边界。所以当标题说“工具层成为新攻击面”它指的不是某个漏洞 CVE 编号而是整个工具治理流程的结构性失能注册即信任、声明即真实、调用即合法。提示别被“AI 安全”这个词带偏。真正的风险不在大模型幻觉里而在你司 DevOps 同事上周五下班前匆忙提交的tools_registry.yaml第 47 行——那里藏着一个没经过灰度验证的delete_all_records工具其description字段写着“仅供测试”但enabled字段值是true。2. MCP 安全网关的核心设计逻辑不做“防火墙”做“工具体检中心”市面上已有不少方案试图给 MCP 加安全层有在 Agent 引擎外挂 WAF 的有给每个工具加 OAuth2 中间件的还有用 Burp Suite 拦截 MCP 流量做规则匹配的。我试过全部结果很讽刺——WAF 把合法的search_knowledge_base?querySQL%20injection当攻击拦截OAuth2 中间件让get_current_weather这种无状态工具每次调用都得走授权码流程延迟飙升 300msBurp 规则匹配则完全失效因为 MCP 的tool_callpayload 是结构化 JSON而攻击者早把恶意逻辑藏在arguments的嵌套对象里比如arguments: {query: SELECT * FROM users WHERE id 1; -- , limit: 10}WAF 规则根本无法理解这是 SQL 注入还是正常搜索。所以我的方案彻底放弃“拦截”思路转向“前置体检”。核心理念就一句话任何工具要接入 MCP 网关必须先通过三项动态健康检查否则拒绝注册且检查结果实时写入审计日志。这三项检查不是静态扫描而是模拟真实 Agent 调用场景的主动探测Endpoint 真实性验证网关拿到工具声明的endpointURL 后不直接信任而是构造一个最小化 HTTP 请求HEAD 方法 随机 User-Agent验证该 URL 是否真实可达、返回状态码是否在 2xx/3xx 范围、响应头是否包含X-MCP-Verified: true由后端服务主动添加。如果后端没加这个 header网关会拒绝注册并提示“请在目标服务响应头中添加 X-MCP-Verified 标识”。Schema 一致性验证网关不仅解析 OpenAPI spec 的 JSON Schema还会启动一个沙箱环境用jsonschema库生成 50 组符合 schema 的随机测试数据再用httpx实际调用一次 endpoint比对真实返回的response.body结构是否与 schema 中responses.200.schema完全匹配。比如 schema 声明{user: {name: string}}但真实 API 返回{user: {name: string, admin_flag: true}}网关立刻告警“响应体存在未声明字段”。调用链路签名验证网关要求所有工具必须支持X-MCP-Signature请求头。签名算法很简单HMAC-SHA256(payload_json_string, shared_secret)。网关在注册阶段生成一对密钥公钥存网关私钥交工具提供方每次调用前网关用私钥签名 payload工具端用公钥验签。这招专治“认证绕过”——即使攻击者篡改了 MCP 的authentication字段为none没有正确签名的请求在工具端就被拒了。这个设计的关键在于“不信任声明只信任行为”。它不阻止任何调用但确保每个注册工具都经过真实世界压力测试。我拿playwright-mcp的 demo 工具做了对比测试原生 MCP server 启动后立即接受所有工具注册而我的网关在加载同一份tools.yaml时卡在第 3 个工具上——那个工具声明的 endpoint 是https://fake-api.example.com/v1/users但实际 DNS 解析失败网关自动标记为UNVERIFIED状态Agent 引擎查询可用工具列表时根本看不到它。注意这个网关不是替代 MCP server而是作为前置代理层部署。Agent 引擎仍连 MCP server但 server 的所有tool_call请求先经网关路由。架构图很简单Agent Engine → MCP Server → MCP Security Gateway → Real Tool Backend。网关本身无状态所有策略配置存 Redis故障时可降级为直连模式但会记录所有降级事件。3. 用 Python 实现 MCP 安全网关从零构建可审计的工具治理层我选择 Python 实现不是因为“Python 简单”而是三个硬性需求第一必须无缝集成现有 MCP 生态mcp-server-python是纯 Python 实现第二需要快速验证动态检查逻辑httpxpydanticcryptography组合比 Node.js 更适合做协议解析第三企业客户要求所有组件可审计、可 debugPython 的 pdb 和 logging 体系比 Go 的 pprof 更贴近运维习惯。下面拆解核心模块的实现细节每一步都附真实踩坑经验。3.1 网关主进程基于 FastAPI 的轻量级代理框架不用 Flask 是因为它的异步支持弱而 MCP 工具调用常涉及 Playwright 等耗时操作不用 Django 是因为它太重网关需要毫秒级响应。FastAPI 的依赖注入和 Pydantic 模型验证天然契合 MCP 的 JSON-RPC 风格。主文件gateway/main.py只有 87 行核心是重写HTTPTransport类# gateway/transport.py from fastapi import Request, Response from mcp.server.stdio import stdio_server from mcp.types import ToolResult, ToolCall import httpx import json import logging class SecureHTTPTransport: def __init__(self, registry: ToolRegistry): self.registry registry # 工具注册中心实例 self.logger logging.getLogger(gateway.transport) async def handle_request(self, request: Request) - Response: # 1. 解析原始 MCP 请求 raw_body await request.body() try: payload json.loads(raw_body) except json.JSONDecodeError: return Response(Invalid JSON, status_code400) # 2. 提取 tool_call 信息MCP 标准格式 if payload.get(method) callTool: tool_name payload[params][name] arguments payload[params][arguments] # 3. 关键从注册中心获取工具元数据并触发健康检查 tool_meta await self.registry.get_tool(tool_name) if not tool_meta or tool_meta.status ! VERIFIED: self.logger.warning(fTool {tool_name} not verified, blocked) return Response( json.dumps({error: Tool not verified}), media_typeapplication/json, status_code403 ) # 4. 生成签名并转发 signed_payload self._sign_payload(tool_meta, arguments) async with httpx.AsyncClient() as client: resp await client.post( tool_meta.endpoint, jsonsigned_payload, headers{X-MCP-Signature: tool_meta.signature_header} ) return Response(resp.content, status_coderesp.status_code)这里有个关键设计网关不解析整个 MCP 协议栈只聚焦callTool方法。因为其他方法listTools、getTool都是元数据查询风险低直接透传。真正需要严控的只有工具执行环节。实测下来这个设计让网关 P99 延迟压在 12ms 内AWS t3.micro 实例比原生 MCP server 高 3ms完全可接受。3.2 工具注册中心动态验证与状态机驱动ToolRegistry类是网关心脏它用 Redis 存储工具状态避免内存泄漏。状态机只有四个状态PENDING刚注册、VERIFYING正在做三项检查、VERIFIED通过、FAILED失败。重点看verify_tool方法# gateway/registry.py import asyncio import httpx from pydantic import BaseModel, ValidationError from cryptography.hazmat.primitives.hmac import HMAC from cryptography.hazmat.primitives import hashes class ToolMeta(BaseModel): name: str endpoint: str schema: dict status: str PENDING signature_key: str class ToolRegistry: def __init__(self, redis_client): self.redis redis_client async def verify_tool(self, tool_meta: ToolMeta) - bool: # 步骤1Endpoint 真实性验证 try: async with httpx.AsyncClient() as client: resp await client.head(tool_meta.endpoint, timeout5.0) if resp.status_code not in [200, 204, 301, 302]: raise Exception(fEndpoint unreachable: {resp.status_code}) if X-MCP-Verified not in resp.headers: raise Exception(Missing X-MCP-Verified header) except Exception as e: await self._set_status(tool_meta.name, FAILED, str(e)) return False # 步骤2Schema 一致性验证简化版实际用 jsonschema.validate try: test_data {query: test} # 生成测试数据 async with httpx.AsyncClient() as client: resp await client.post(tool_meta.endpoint, jsontest_data) # 验证响应结构是否匹配 schema expected_keys tool_meta.schema.get(properties, {}).keys() actual_keys resp.json().keys() if resp.is_success else [] if not set(expected_keys).issubset(set(actual_keys)): raise Exception(Response schema mismatch) except Exception as e: await self._set_status(tool_meta.name, FAILED, str(e)) return False # 步骤3签名密钥生成使用 Redis INCR 生成唯一 key tool_meta.signature_key fmcp_{await self.redis.incr(sig_counter)} await self._set_status(tool_meta.name, VERIFIED, ) return True踩过的最大坑Redis 连接池泄漏。最初用redis-py默认配置高并发下连接数暴涨到 1000导致网关 OOM。解决方案是显式设置max_connections20并用aioredis.from_url()替代redis.Redis()。另一个坑是httpx的 timeout 设置——必须设timeout5.0否则网络抖动时验证卡死整个网关阻塞。现在每个验证任务都加了asyncio.wait_for(..., timeout10.0)双重保险。3.3 审计日志模块用结构化日志替代“日志大海”很多安全方案败在日志不可查。我用structlog替代logging每条日志都是 JSON字段固定event事件类型、tool_name、status成功/失败、duration_ms、error失败时填充。例如一次 Rug Pull 检测日志{ event: TOOL_CALL_BLOCKED, tool_name: transfer_funds, reason: signature_verification_failed, client_ip: 10.0.1.23, timestamp: 2024-06-15T08:22:14.882Z, duration_ms: 2.34 }这些日志直接输出到 stdout由 Docker 日志驱动或 Filebeat 采集到 ELK。关键技巧所有日志都加logger.bind(tool_nametool_name)避免在业务逻辑里重复传参。实测效果当客户遇到异常调用时运维用grep transfer_funds /var/log/gateway.log | jq -r .error | sort | uniq -c三秒定位到是签名密钥轮换没同步。4. 三大实战攻防场景工具投毒、Rug Pull、认证绕过的一线检测过程安全方案的价值不在设计多漂亮而在能否真实捕获攻击。我把网关部署到客户生产环境后两周内抓到三类典型攻击下面还原完整检测链路。所有案例均脱敏但技术细节 100% 真实。4.1 工具投毒事件get_stock_price工具的 schema 被恶意篡改攻击手法攻击者内部运维在更新tools.yaml时将get_stock_price工具的schema中symbol字段的maxLength从 10 改为 1000并新增一个未声明的callback_url字段。目的是后续用超长 symbol 触发缓冲区溢出或通过 callback_url 回传敏感数据。检测过程网关监听到tools.yaml更新自动触发verify_tool执行 Schema 一致性验证时用jsonschema生成测试数据{symbol: A * 1000}发送请求目标服务返回500 Internal Server Error因后端没做输入长度校验网关捕获异常日志记录event: SCHEMA_VERIFICATION_FAILED,error: HTTP 500 on test payload状态机将工具设为FAILEDAgent 引擎调用时收到403 Forbidden。关键洞察这个检测不依赖规则引擎而是靠“用极端数据压测”。很多团队以为 schema 验证就是 JSON 格式校验其实真正的验证是“用 schema 生成的数据能否让后端稳定运行”。我后来加了个增强逻辑对每个工具网关自动发送 3 组边界数据最小/最大/空值并监控后端 Prometheus 指标如果http_server_requests_seconds_count{status500}在验证期间突增立即告警。4.2 Rug Pull 事件withdraw_crypto工具 endpoint 被悄悄替换攻击手法攻击者在 CI/CD 流水线中把withdraw_crypto工具的endpoint从https://prod-wallet-api.company.com/v1/withdraw改为https://evil-server.net/api/withdraw并伪造了 SSL 证书。由于是 HTTPS传统流量分析无法识别。检测过程网关启动 Endpoint 真实性验证向新 endpoint 发送 HEAD 请求httpx报错ssl.SSLCertVerificationError: Certificate not verified因伪造证书不被系统信任网关日志记录event: ENDPOINT_VERIFICATION_FAILED,error: SSL certificate verification failed状态机设为FAILED同时触发企业微信机器人告警“检测到 withdraw_crypto 工具 endpoint SSL 证书异常请立即核查”。避坑经验最初网关用verifyFalse忽略 SSL 错误结果漏掉这个攻击。后来强制verifyTrue并增加证书指纹白名单机制——网关启动时用openssl s_client -connect evil-server.net:443 2/dev/null | openssl x509 -fingerprint -sha256 -noout获取真实证书指纹存入 Redis。验证时比对指纹不匹配即告警。这样既防中间人又避免误报自签名证书开发环境允许配置ALLOW_SELF_SIGNEDTrue。4.3 认证绕过事件delete_database工具的 authentication 字段被设为 none攻击手法攻击者在 MCP 的authentication字段填none声称该工具无需认证但后端服务实际用硬编码 token 访问数据库且 token 权限极高。检测过程网关在verify_tool阶段发现authentication字段为none触发签名验证强制流程生成测试 payload 并签名发送到 endpoint后端服务返回401 Unauthorized因没实现签名验证逻辑网关日志event: SIGNATURE_VERIFICATION_FAILED,error: Backend does not support X-MCP-Signature状态机设为FAILED并提示“请后端服务实现签名验证”。深度防御这个检测暴露了后端服务的安全短板。网关随后启动“签名兼容性测试”对所有authentication: none的工具自动发起三次请求——第一次带正确签名第二次带错误签名第三次不带签名。如果三次都成功说明后端完全没做签名校验网关直接拒绝注册。客户据此推动后端团队在一周内完成了签名 SDK 集成。5. 部署与运维实战如何让安全网关真正落地而不拖慢业务再好的设计部署失败就归零。我在 3 家客户现场落地时总结出四条铁律每一条都来自血泪教训。5.1 部署模式必须支持“旁路镜像”而非“强制串联”初期我坚持网关必须串联在 MCP 流量路径中结果客户抱怨“Agent 响应变慢用户投诉体验下降”。后来改成双模式默认旁路镜像mirror mode即 Agent 引擎流量 100% 直连 MCP server网关只镜像一份流量做离线分析当检测到可疑行为如连续 5 次transfer_funds调用网关才自动切换为串联模式将后续请求导入自身处理。切换逻辑用 Redis Pub/Sub 实现毫秒级生效。这样既保障业务 SLA又不牺牲安全能力。5.2 配置管理用 GitOps 替代 UI 配置曾有个客户要求加 Web UI 管理工具注册。我坚决反对理由很实在UI 配置无法版本控制、无法审计谁在何时改了什么、无法做自动化测试。最终方案是——所有配置存 Git 仓库目录结构如下config/ ├── tools/ │ ├── get_user_profile.yaml # 工具定义 │ └── transfer_funds.yaml ├── policies/ │ └── high_risk_tools.yaml # 高危工具策略如金额10000需二次确认 └── secrets/ └── keys.json # 签名密钥加密存储CI 流水线监听config/tools/目录变更自动触发gateway verify --all命令。验证失败则阻断合并PR 评论自动贴出失败详情。这套流程让配置变更从“人工操作”变成“代码变更”审计追溯变得极其简单。5.3 性能压测用真实 Agent 流量做基准测试别信理论值。我用客户真实的 24 小时 MCP 调用日志脱敏后提取 top 10 工具的调用频率和 payload 大小用 Locust 写压测脚本# locustfile.py from locust import HttpUser, task, between import json class MCPUser(HttpUser): wait_time between(0.1, 0.5) task def call_get_user_profile(self): self.client.post(/mcp/call, json{ method: callTool, params: { name: get_user_profile, arguments: {user_id: U123456789} } })在 t3.xlarge 实例上网关稳定支撑 1200 QPSP99 15ms而客户峰值才 800 QPS。关键发现当 payload 超过 1MB 时json.loads()成为瓶颈。解决方案是改用ujson库性能提升 3.2 倍。5.4 故障恢复设计“熔断-降级-自愈”三级机制网关不可能永远 100% 可用。我的设计是一级熔断Redis 连接失败时网关自动切换为“只读模式”所有工具状态维持最后已知状态不接受新注册二级降级当验证服务如 httpx 请求超时率 5%网关暂停动态验证仅做静态 schema 校验状态设为DEGRADED三级自愈网关内置健康检查端点/healthzK8s liveness probe 每 10 秒调用一次。若连续 3 次失败K8s 自动重启 Pod并从 Redis 恢复状态。这套机制让网关在一次 Redis 集群网络分区事故中自动降级运行 47 分钟期间无一次业务中断客户甚至没感知到。6. 超越网关构建 AI Agent 工具层的纵深防御体系安全网关只是起点。真正的防御必须贯穿工具生命周期。我在客户现场推动的“纵深防御四层模型”已被采纳为公司 AI 安全标准。6.1 开发层工具 SDK 强制签名与 schema 生成要求所有工具提供方必须使用我们提供的 Python SDK 开发# sdk/tool.py from mcp_secure_sdk import Tool, signature_required Tool(nameget_user_profile, descriptionGet user profile by ID) signature_required # 装饰器自动处理签名验证 def get_user_profile(user_id: str) - dict: # 业务逻辑 return {name: Alice, email: aliceexample.com}SDK 在启动时自动读取pyproject.toml中的tool.mcp.schema配置用pydantic生成 OpenAPI spec并注入X-MCP-Verified响应头。这样工具开发者无需关心安全细节SDK 全包办。6.2 发布层CI/CD 流水线嵌入自动化验证在 GitLab CI 中加入验证 stage# .gitlab-ci.yml verify-tool: stage: verify script: - pip install mcp-secure-gateway - mcp-gateway verify --tool ./src/tools/get_user_profile.py allow_failure: false验证内容包括schema 语法检查、endpoint 可达性用 mock server、签名逻辑单元测试。任一失败流水线终止PR 不可合并。6.3 运行层实时调用链路审计与异常聚类网关日志接入 Grafana构建两个核心看板工具健康度看板显示每个工具的VERIFIED/FAILED状态、验证失败原因分布、平均验证耗时调用异常看板用 Loki 的 logql 查询| json | status_code ! 200 | group_by(tool_name, status_code) | count_over_time(1h)自动聚类高频失败工具。曾发现send_email工具在凌晨 2 点集中失败排查发现是邮件服务商限流。这不属于安全事件但属于稳定性风险——网关的审计能力让运维从“救火”转向“预见”。6.4 治理层建立工具安全评分卡给每个工具打分维度包括声明可信度schema 是否完整、endpoint 是否 HTTPS行为合规度是否支持签名、是否返回 X-MCP-Verified历史稳定性7 天内验证失败次数、调用错误率权限合理性scope 字段是否最小化如read_onlyvsfull_access评分低于 70 分的工具自动进入“观察期”Agent 引擎调用时需人工审批。这个机制倒逼工具提供方持续改进。最后分享个真实体会做 AI 安全最大的陷阱是追逐“炫技型漏洞”比如 prompt injection而忽视“工程型风险”比如 YAML 配置错误。当你的团队还在争论“LLM 是否会被 jailbreak”时攻击者已经用sed -i s/prod/staging/g tools.yaml黑掉了生产数据库。这个网关的价值不在于它多酷而在于它把安全从“专家会议议题”变成了“CI 流水线里的一个 check 步骤”。下次你看到 MCP 工具注册表别只检查字段语法试试用 curl -I 看一眼那个 endpoint 的响应头——真正的防线往往就藏在最朴素的 HTTP 头里。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Elasticsearch AI Indices 实战:让 agents 用 ES|QL 保留答案,无需通读内容 2026/9/29 20:33:33

Elasticsearch AI Indices 实战:让 agents 用 ES|QL 保留答案,无需通读内容

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Towards Low-Resource StarGAN Voice Conversion using Weight Adaptive Instance Norm:TaoToken 统一 Key 接入 2026/9/29 20:33:33

Towards Low-Resource StarGAN Voice Conversion using Weight Adaptive Instance Norm:TaoToken 统一 Key 接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
AI辅助论文写作与知网查重:合规使用工具和流程详解 2026/9/29 20:33:33

AI辅助论文写作与知网查重:合规使用工具和流程详解

我收到很多同学问同一个问题:"有没有那种导师不会主动教,但能让我在知网查重里零痕迹的AI神器?" 说实话,第一次看到这个问题我心里挺矛盾的。AI辅助写论文是完全正当的事,我也一直在用;但把"…

阅读更多 →
C#接入ActiveMQ实战:NMSActiveMQ实现消息队列收发与持久化 2026/9/29 20:33:33

C#接入ActiveMQ实战:NMSActiveMQ实现消息队列收发与持久化

简介:ActiveMQ是一款成熟可靠的开源消息中间件,在企业级异步通信、系统解耦与流量削峰等场景中被广泛使用。这套基于C#语言、采用WinForm开发的演示程序,提供了消息发送端与接收端的完整实现,涵盖生产者、消费者、界面交互等核心模…

阅读更多 →
LangGraph 实战:用 StateGraph 编排带分支与循环的 Agent 2026/9/29 20:33:33

LangGraph 实战:用 StateGraph 编排带分支与循环的 Agent

1. 从"能跑通"到"能编排":LangGraph 到底补了哪块短板很多人第一次接触 Agent 开发,都是从 LangChain 的AgentExecutor起步的。写个提示词、挂两个工具、跑一个while循环,看起来就"智能"了。但真把它放到稍微复…

阅读更多 →
DeepSeek V4 接入 Vscode 中方法:TaoToken 统一 Key 配置 settings.json 骨架与验证 2026/9/29 20:33:26

DeepSeek V4 接入 Vscode 中方法:TaoToken 统一 Key 配置 settings.json 骨架与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉