chore(security): fail closed booking development configuration

This commit is contained in:
鲨鱼辣椒
2026-08-08 16:00:28 +08:00
parent d56587a078
commit 9f29b59c26
13 changed files with 179 additions and 29 deletions

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ client/dist/
client/dist.zip
client/.vite/
.pnpm-store/
.planning/
server/target/

1
client/.node-version Normal file
View File

@@ -0,0 +1 @@
24.14.0

View File

@@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"type": "module",
"packageManager": "pnpm@11.16.0",
"scripts": {
"dev": "vite --host 127.0.0.1 --port 5174",
"build": "vue-tsc --noEmit && vite build",
@@ -37,6 +38,7 @@
"vue-tsc": "^3.0.0"
},
"engines": {
"node": ">=22.13.0"
"node": "24.x",
"pnpm": "11.x"
}
}

23
client/pnpm-lock.yaml generated
View File

@@ -4,6 +4,10 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides:
nanoid: 3.3.17
postcss: 8.5.23
importers:
.:
@@ -947,6 +951,7 @@ packages:
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
hookable@5.5.3:
@@ -1179,8 +1184,8 @@ packages:
muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
nanoid@3.3.15:
resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==}
nanoid@3.3.17:
resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -1274,8 +1279,8 @@ packages:
resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==}
engines: {node: '>=4'}
postcss@8.5.16:
resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==}
postcss@8.5.23:
resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
@@ -2229,7 +2234,7 @@ snapshots:
'@vue/shared': 3.5.39
estree-walker: 2.0.2
magic-string: 0.30.21
postcss: 8.5.16
postcss: 8.5.23
source-map-js: 1.2.1
'@vue/compiler-ssr@3.5.39':
@@ -2836,7 +2841,7 @@ snapshots:
muggle-string@0.4.1: {}
nanoid@3.3.15: {}
nanoid@3.3.17: {}
natural-compare@1.4.0: {}
@@ -2922,9 +2927,9 @@ snapshots:
cssesc: 3.0.0
util-deprecate: 1.0.2
postcss@8.5.16:
postcss@8.5.23:
dependencies:
nanoid: 3.3.15
nanoid: 3.3.17
picocolors: 1.1.1
source-map-js: 1.2.1
@@ -3120,7 +3125,7 @@ snapshots:
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.5
postcss: 8.5.16
postcss: 8.5.23
rolldown: 1.1.4
tinyglobby: 0.2.17
optionalDependencies:

View File

@@ -1,5 +1,9 @@
packages:
- .
overrides:
nanoid: 3.3.17
postcss: 8.5.23
allowBuilds:
vue-demi: true

View File

@@ -0,0 +1,20 @@
# Booking V0.1 安全 checkpoint
最后更新2026-08-08
## 当前已完成的仓库内处置
- 开发与生产数据库配置不再提交远程连接地址、用户名或密码;开发环境必须通过 `TH_HOTEL_DEV_DB_URL``TH_HOTEL_DEV_DB_USERNAME``TH_HOTEL_DEV_DB_PASSWORD` 注入,生产环境使用对应的 `TH_HOTEL_PROD_*` Secret。
- 默认本地 profile 使用进程内 H2且关闭 AgentBus 捕获、SuperAgent 分发、Debug EML、文档转换和演示数据入口。
- Git 历史不做重写。历史中曾出现的数据库凭据不得继续使用,也不得从历史恢复到配置文件。
- BR00 原型只在开发态且 `VITE_ENABLE_BR00_PROTOTYPE=true` 时注册;生产构建不暴露原型路由,原型不连接正式 API。
## G0 的外部前置条件
数据库管理员必须在部署 Secret 系统中废止/轮换历史泄露的数据库凭据并记录轮换日期与工单号。此操作不由代码仓库或应用启动过程替代在收到完成证明前G0 不得标记为完全通过。
## 验证边界
- 本地验证使用 H2不使用远程数据库默认值。
- 远程测试环境只允许通过 Secret 注入连接信息。
- 真实邮件样本、密码和 `.planning` 过程文件不进入产品提交。

44
scripts/verify-no-secrets.sh Executable file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# Lightweight fail-closed scan for the checked-out source tree. Historical Git
# secrets are deliberately not rewritten here; this gate prevents new obvious
# plaintext credentials from entering the current product tree.
set -euo pipefail
if ! command -v rg >/dev/null 2>&1; then
echo 'rg is required for the repository secret scan.' >&2
exit 69
fi
# Only configuration and executable wiring can introduce a usable credential.
# Application code contains benign variables such as `rawPassword`; scanning it
# creates noise and makes a security gate easy to ignore.
scan_args=(--hidden --pcre2 --glob '!.git/**' --glob '!client/node_modules/**' --glob '!server/target/**' --glob '!client/dist/**' --glob '*.yml' --glob '*.yaml' --glob '*.properties' --glob '.env' --glob '.env.*' --glob '*.sh')
# Ignore environment placeholders and explicitly fake examples. Reject literal
# values of at least 12 characters, which includes all deployable credentials.
credential_pattern='(?i)(?<![A-Za-z0-9_])(password|passwd|secret|api[_-]?key|access[_-]?key)[[:space:]]*[:=][[:space:]]*["\x27]?(?!\$\{|<|your-|[A-Za-z0-9._-]*xxx)[A-Za-z0-9][A-Za-z0-9_+/.=-]{11,}'
credential_matches="$(rg -n -I "${scan_args[@]}" "${credential_pattern}" . || true)"
if [[ -n "${credential_matches}" ]]; then
echo 'Potential plaintext credential(s) detected outside tests:' >&2
printf '%s\n' "${credential_matches}" >&2
exit 73
fi
jdbc_userinfo_matches="$(rg -n -I "${scan_args[@]}" 'jdbc:(mysql|postgresql):[^[:space:]]*//[^[:space:]@/]+:[^[:space:]@/]+@' . || true)"
if [[ -n "${jdbc_userinfo_matches}" ]]; then
echo 'JDBC URL with embedded user information detected:' >&2
printf '%s\n' "${jdbc_userinfo_matches}" >&2
exit 74
fi
for config_file in server/src/main/resources/application-dev.yml server/src/main/resources/application-prod.yml; do
if [[ ! -f "${config_file}" ]]; then
continue
fi
if rg --pcre2 -n '^\s*(url|username|password):\s*(?!\$\{)[^#\s].*$' "${config_file}" >/dev/null; then
echo "${config_file} contains a non-environment datasource value." >&2
exit 75
fi
done
echo 'Repository plaintext-secret scan passed.'

1
server/.java-version Normal file
View File

@@ -0,0 +1 @@
17

View File

@@ -56,11 +56,20 @@
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>

View File

@@ -1,9 +1,10 @@
spring:
datasource:
# dev 使用可提交的开发测试库地址;密码仍通过环境变量注入。
url: "jdbc:mysql://rm-7xv137v8f04vfk7a4ko.mysql.rds.aliyuncs.com:3306/th_hotel?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC"
username: th_hotel_user
password: 'CxrLg6b6kw2zJjmcjHUj'
# dev 连接信息只能由部署环境注入。缺失任一变量时 Spring 会在启动阶段失败,
# 禁止使用可提交的远程默认值。
url: ${TH_HOTEL_DEV_DB_URL}
username: ${TH_HOTEL_DEV_DB_USERNAME}
password: ${TH_HOTEL_DEV_DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
flyway:
enabled: true
@@ -18,13 +19,12 @@ agentbus:
# dev 默认不主动连接 AgentBus避免本地启动误消费消息。
enabled: false
ws:
# AgentBus dev/test 真实测试地址可以提交Token 仍走环境变量。
url: wss://mesh.nianxx.cn/ws
token: ${AGENTBUS_DEV_WS_TOKEN:}
url: ${AGENTBUS_DEV_WS_URL:${AGENTBUS_WS_URL:}}
token: ${AGENTBUS_DEV_WS_TOKEN:${AGENTBUS_WS_TOKEN:}}
reconnect-delay: 5s
connect-timeout: 15s
capture:
enabled: true
enabled: false
superagent-dispatch:
# dev 默认不自动调用 SuperAgent需要联调时显式开启。
enabled: ${AGENTBUS_DEV_SUPERAGENT_DISPATCH_ENABLED:${AGENTBUS_SUPERAGENT_DISPATCH_ENABLED:false}}
@@ -99,10 +99,9 @@ auth:
reservation:
demo-data:
# dev 本地联调默认开启演示数据造数接口;生产环境不得开启。
enabled: true
# 本地演示数据接口访问口令,需和 X-TH-Hotel-Demo-Data-Key 请求头一致。
# 本地演示数据接口必须显式开启。
enabled: ${RESERVATION_DEV_DEMO_DATA_ENABLED:${RESERVATION_DEMO_DATA_ENABLED:false}}
access-key: ${RESERVATION_DEV_DEMO_DATA_ACCESS_KEY:${RESERVATION_DEMO_DATA_ACCESS_KEY:}}
server:
port: 8080
port: ${SERVER_PORT:8080}

View File

@@ -0,0 +1,42 @@
# 默认本机 profile只使用进程内 H2所有外部入口和写入型调试能力保持关闭。
spring:
datasource:
url: jdbc:h2:mem:th_hotel_local;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1
username: sa
password: ""
driver-class-name: org.h2.Driver
flyway:
enabled: true
springdoc:
api-docs:
enabled: false
swagger-ui:
enabled: false
agentbus:
capture:
enabled: false
superagent-dispatch:
enabled: false
worker-enabled: false
superagent:
open-api:
enabled: false
debug:
eml-upload:
enabled: false
document-conversion:
enabled: false
worker-enabled: false
reservation:
demo-data:
enabled: false
server:
address: 127.0.0.1
port: ${SERVER_PORT:8080}

View File

@@ -1,10 +1,10 @@
spring:
datasource:
# test 数据库连接信息通过测试机 env 文件统一注入,默认值仅用于本机快速启动
url: ${SPRING_DATASOURCE_URL:jdbc:mysql://127.0.0.1:3306/th_hotel_test?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC}
username: ${SPRING_DATASOURCE_USERNAME:th_hotel_test}
password: ${SPRING_DATASOURCE_PASSWORD:${TH_HOTEL_TEST_DB_PASSWORD:}}
driver-class-name: com.mysql.cj.jdbc.Driver
# 本机 test 一律使用 H2测试机远程数据库必须由三项专属 Secret 同时注入,禁止任何远程默认值
url: ${TH_HOTEL_TEST_DB_URL:jdbc:h2:mem:th_hotel_test;MODE=MySQL;DATABASE_TO_LOWER=TRUE;CASE_INSENSITIVE_IDENTIFIERS=TRUE;DB_CLOSE_DELAY=-1}
username: ${TH_HOTEL_TEST_DB_USERNAME:sa}
password: ${TH_HOTEL_TEST_DB_PASSWORD:}
driver-class-name: ${TH_HOTEL_TEST_DB_DRIVER:org.h2.Driver}
flyway:
enabled: true
servlet:

View File

@@ -2,8 +2,8 @@ spring:
application:
name: th-hotel-server
profiles:
# 未显式指定 profile 时默认使用 dev避免本地启动误连 test/prod
default: dev
# 未显式指定 profile 时只使用本机 H2禁止本地启动隐式连接任何远程数据库
default: local
flyway:
enabled: true
servlet:
@@ -16,6 +16,28 @@ spring:
# Spring MVC async timeout 是应用级全局值;当前主要用于避免 Debug EML SSE 先于 SuperAgent 调试调用关闭。
request-timeout: ${DEBUG_EML_UPLOAD_SSE_REQUEST_TIMEOUT:${SPRING_MVC_ASYNC_REQUEST_TIMEOUT:1800s}}
# 新 Booking 主线 PostgreSQL runtime 默认关闭。启用时仅允许部署 Secret 注入连接信息,
# 与旧 MySQL/H2 datasource 隔离,不建立跨库事务或未定义双写。
booking:
agent:
# 真实 Booking Agent 必须显式启用;未启用时不确定材料只形成 Risk不会伪造任务。
enabled: ${TH_HOTEL_BOOKING_AGENT_ENABLED:false}
# 外部 Agent 调用默认通过 processing-run worker 完成;入口立即返回 202 和 run ID。
async-enabled: ${TH_HOTEL_BOOKING_AGENT_ASYNC_ENABLED:true}
profile-version: ${TH_HOTEL_BOOKING_AGENT_PROFILE_VERSION:booking-agent-v1}
external-subject-id: ${TH_HOTEL_BOOKING_AGENT_EXTERNAL_SUBJECT_ID:}
postgres:
enabled: ${TH_HOTEL_BOOKING_PG_ENABLED:false}
url: ${TH_HOTEL_BOOKING_PG_URL:}
username: ${TH_HOTEL_BOOKING_PG_USERNAME:}
password: ${TH_HOTEL_BOOKING_PG_PASSWORD:}
schema: th_hotel_booking
maximum-pool-size: ${TH_HOTEL_BOOKING_PG_MAX_POOL_SIZE:4}
connection-timeout-ms: ${TH_HOTEL_BOOKING_PG_CONNECTION_TIMEOUT_MS:10000}
material-download-max-bytes: ${TH_HOTEL_BOOKING_MATERIAL_DOWNLOAD_MAX_BYTES:10485760}
retention-enabled: ${TH_HOTEL_BOOKING_RETENTION_ENABLED:false}
retention-cron: ${TH_HOTEL_BOOKING_RETENTION_CRON:0 15 3 * * *}
mybatis-plus:
configuration:
map-underscore-to-camel-case: true