docs: project docs, scripts, deployment configs, and evidence

This commit is contained in:
2026-04-02 11:22:17 +08:00
parent 4718980ab5
commit bbeeb63dfa
396 changed files with 165018 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
# Alertmanager Render Drill
- Generated at: 2026-03-27 18:20:59 +08:00
- Template file: D:\project\deployment\alertmanager\alertmanager.yml
- Rendered file: D:\project\docs\evidence\ops\2026-03-27\alerting\20260327-182059\alertmanager.rendered.yaml
- Synthetic secret values were injected through process environment variables for this drill only.
- Result: template placeholders resolved successfully and the rendered config contains no unresolved `${ALERTMANAGER_*}` tokens.
## Scope Note
- This drill validates the config injection/rendering path only.
- It does not prove real SMTP delivery, real contact routing, or production secret manager integration.
## Evidence Files
- alertmanager.rendered.yaml

View File

@@ -0,0 +1,85 @@
global:
resolve_timeout: 5m
# 注意:
# 该文件为模板文件,生产环境必须先注入并渲染 `${ALERTMANAGER_*}` 变量,
# 再将渲染结果交给 Alertmanager 使用。
# 告警路由
route:
group_by: ['alertname', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 12h
receiver: 'default'
# 子路由,根据严重级别分发
routes:
# Critical 告警
- match:
severity: critical
receiver: 'critical-alerts'
group_wait: 10s
continue: true
# Warning 告警
- match:
severity: warning
receiver: 'warning-alerts'
continue: true
# 告警接收者
receivers:
# 默认接收者
- name: 'default'
email_configs:
- to: 'ops-team@example.org'
from: 'alertmanager@example.org'
smarthost: 'smtp.example.org:587'
auth_username: 'alertmanager@example.org'
auth_password: 'synthetic-secret-for-render-drill'
headers:
Subject: '[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }}'
# Critical 告警接收者
- name: 'critical-alerts'
email_configs:
- to: 'critical-oncall@example.org'
from: 'alertmanager@example.org'
smarthost: 'smtp.example.org:587'
auth_username: 'alertmanager@example.org'
auth_password: 'synthetic-secret-for-render-drill'
headers:
Subject: '[CRITICAL] {{ .GroupLabels.alertname }}'
# Warning 告警接收者
- name: 'warning-alerts'
email_configs:
- to: 'warning-oncall@example.org'
from: 'alertmanager@example.org'
smarthost: 'smtp.example.org:587'
auth_username: 'alertmanager@example.org'
auth_password: 'synthetic-secret-for-render-drill'
headers:
Subject: '[WARNING] {{ .GroupLabels.alertname }}'
# 告警抑制规则
inhibit_rules:
# 如果有 critical 告警,抑制同一服务的 warning 告警
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['service']
# 告警静默规则(按需配置)
# silences:
# - matchers:
# - name: alertname
# value: LowOnlineUsers
# - name: severity
# value: info
# startsAt: "2026-03-12T00:00:00+08:00"
# endsAt: "2026-03-12T23:59:59+08:00"
# comment: "维护期间静默低在线用户告警"

View File

@@ -0,0 +1,34 @@
# Alerting Package Validation
- Generated at: 2026-03-27 18:20:59 +08:00
- Alerts file: D:\project\deployment\alertmanager\alerts.yml
- Alertmanager file: D:\project\deployment\alertmanager\alertmanager.yml
- Baseline report: D:\project\docs\evidence\ops\2026-03-27\observability\LOCAL_BASELINE_20260327-182005.md
## Structural Validation
- Rule inventory: critical=3, warning=4, info=2
- Missing required rules: none
- Root receiver: default
- Critical route receiver: critical-alerts
- Warning route receiver: warning-alerts
- Missing required receivers: none
- Structural ready: True
## Threshold Alignment
- HighResponseTime threshold: 1s
- Latest browser max baseline: 186ms
- Latest browser timings: login-desktop=186ms, login-initial=99ms, login-mobile=96ms, login-tablet=117ms
## External Delivery Readiness
- Placeholder findings: \$\{ALERTMANAGER_[A-Z0-9_]+\}
- External delivery closed: False
- Interpretation: rules and route topology can be reviewed locally, but unresolved template variables or example SMTP/accounts mean real notification delivery evidence is still open until environment-specific contacts and secrets are injected.
## Conclusion
- Repo-level alerting package structurally ready: True
- Repo-level oncall/delivery package fully closed: False

View File

@@ -0,0 +1,39 @@
# Backup Restore Drill
- Generated at: 2026-03-27 18:21:07 +08:00
- Source DB: D:\project\data\user_management.db
- Backup DB: D:\project\docs\evidence\ops\2026-03-27\backup-restore\20260327-182059\user_management.backup.db
- Restored DB: D:\project\docs\evidence\ops\2026-03-27\backup-restore\20260327-182059\user_management.restored.db
- Probe port: 18080
## Hash Validation
- source sha256: 546D353065A10AA7B2B429A8ED6CBE03830D528567FB769A242A0854256F4857
- backup sha256: 546D353065A10AA7B2B429A8ED6CBE03830D528567FB769A242A0854256F4857
- restored sha256: 546D353065A10AA7B2B429A8ED6CBE03830D528567FB769A242A0854256F4857
## Snapshot Comparison
- source tables: {"devices":0,"login_logs":18,"operation_logs":50,"password_histories":0,"permissions":17,"role_permissions":20,"roles":2,"user_roles":1,"users":1,"webhook_deliveries":0,"webhooks":0}
- restored tables: {"devices":0,"login_logs":18,"operation_logs":50,"password_histories":0,"permissions":17,"role_permissions":20,"roles":2,"user_roles":1,"users":1,"webhook_deliveries":0,"webhooks":0}
- source existing tables: devices, login_logs, operation_logs, password_histories, permissions, role_permissions, roles, sqlite_sequence, user_roles, user_social_accounts, users, webhook_deliveries, webhooks
- restored existing tables: devices, login_logs, operation_logs, password_histories, permissions, role_permissions, roles, sqlite_sequence, user_roles, user_social_accounts, users, webhook_deliveries, webhooks
- source missing tables: social_accounts
- restored missing tables: social_accounts
- sample users: e2e_admin
## Restore Service Verification
- GET /health: pass
- GET /health/ready: pass
- GET /api/v1/auth/capabilities: pass
- auth capabilities payload: {"password":true,"email_activation":false,"email_code":false,"sms_code":false,"password_reset":false,"admin_bootstrap_required":false,"oauth_providers":[]}
## Evidence Files
- source-snapshot.json
- restored-snapshot.json
- server.stdout.log
- server.stderr.log
- config.restore.yaml

View File

@@ -0,0 +1,215 @@
server:
port: 18080
mode: release # debug, release
read_timeout: 30s
read_header_timeout: 10s
write_timeout: 30s
idle_timeout: 60s
shutdown_timeout: 15s
max_header_bytes: 1048576
database:
type: sqlite # current runtime support: sqlite
sqlite:
path: "D:/project/docs/evidence/ops/2026-03-27/backup-restore/20260327-182059/user_management.restored.db"
postgresql:
host: localhost
port: 5432
database: user_management
username: postgres
password: ""
ssl_mode: disable
max_open_conns: 100
max_idle_conns: 10
mysql:
host: localhost
port: 3306
database: user_management
username: root
password: ""
charset: utf8mb4
max_open_conns: 100
max_idle_conns: 10
cache:
l1:
enabled: true
max_size: 10000
ttl: 5m
l2:
enabled: false
type: redis
redis:
addr: localhost:6379
password: ""
db: 0
pool_size: 50
ttl: 30m
redis:
enabled: false
addr: localhost:6379
password: ""
db: 0
jwt:
algorithm: RS256
secret: ""
private_key_path: "./data/jwt/private.pem"
public_key_path: "./data/jwt/public.pem"
private_key_pem: ""
public_key_pem: ""
access_token_expire: 2h
refresh_token_expire: 168h # 7澶?= 168灏忔椂
security:
password_min_length: 8
password_require_special: true
password_require_number: true
login_max_attempts: 5
login_lock_duration: 30m
ratelimit:
enabled: true
login:
enabled: true
algorithm: token_bucket
capacity: 5
rate: 1
window: 1m
register:
enabled: true
algorithm: leaky_bucket
capacity: 3
rate: 1
window: 1h
api:
enabled: true
algorithm: sliding_window
capacity: 1000
window: 1m
monitoring:
prometheus:
enabled: true
path: /metrics
tracing:
enabled: false
endpoint: http://localhost:4318
service_name: user-management-system
logging:
level: info # debug, info, warn, error
format: json # json, text
output:
- stdout
- ./logs/app.log
rotation:
max_size: 100 # MB
max_age: 30 # days
max_backups: 10
admin:
username: ""
password: ""
email: ""
cors:
enabled: true
allowed_origins:
- "http://localhost:3000"
- "http://127.0.0.1:3000"
allowed_methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Content-Type
- X-Requested-With
- X-CSRF-Token
max_age: 3600
email:
host: "" # 鐢熶骇鐜濉啓鐪熷疄 SMTP Host
port: 18080
username: ""
password: ""
from_email: ""
from_name: "鐢ㄦ埛绠$悊绯荤粺"
sms:
enabled: false
provider: "" # aliyun, tencent锛涚暀绌鸿〃绀虹鐢ㄧ煭淇¤兘鍔? code_ttl: 5m
resend_cooldown: 1m
max_daily_limit: 10
aliyun:
access_key_id: ""
access_key_secret: ""
sign_name: ""
template_code: ""
endpoint: ""
region_id: "cn-hangzhou"
code_param_name: "code"
tencent:
secret_id: ""
secret_key: ""
app_id: ""
sign_name: ""
template_id: ""
region: "ap-guangzhou"
endpoint: ""
password_reset:
token_ttl: 15m
site_url: "http://localhost:8080"
# OAuth 绀句氦鐧诲綍閰嶇疆锛堢暀绌哄垯绂佺敤瀵瑰簲 Provider锛?
oauth:
google:
client_id: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/google/callback"
wechat:
app_id: ""
app_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/wechat/callback"
github:
client_id: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/github/callback"
qq:
app_id: ""
app_key: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/qq/callback"
alipay:
app_id: ""
private_key: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/alipay/callback"
sandbox: false
douyin:
client_key: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/douyin/callback"
# Webhook 鍏ㄥ眬閰嶇疆
webhook:
enabled: true
secret_header: "X-Webhook-Signature" # 绛惧悕 Header 鍚嶇О
timeout_sec: 30 # 鍗曟鎶曢€掕秴鏃讹紙绉掞級
max_retries: 3 # 鏈€澶ч噸璇曟鏁?
retry_backoff: "exponential" # 閫€閬跨瓥鐣ワ細exponential / fixed
worker_count: 4 # 鍚庡彴鎶曢€掑崗绋嬫暟
queue_size: 1000 # 鎶曢€掗槦鍒楀ぇ灏?
# IP 瀹夊叏閰嶇疆
ip_security:
auto_block_enabled: true # 鏄惁鍚敤鑷姩灏佺
auto_block_duration: 30m # 鑷姩灏佺鏃堕暱
brute_force_threshold: 10 # 鏆村姏鐮磋В闃堝€硷紙绐楀彛鍐呭け璐ユ鏁帮級
detection_window: 15m # 妫€娴嬫椂闂寸獥鍙?

View File

@@ -0,0 +1,40 @@
{
"generated_at": "2026-03-27T18:21:02+08:00",
"path": "D:\\project\\docs\\evidence\\ops\\2026-03-27\\backup-restore\\20260327-182059\\user_management.restored.db",
"file_size": 208896,
"existing_tables": [
"devices",
"login_logs",
"operation_logs",
"password_histories",
"permissions",
"role_permissions",
"roles",
"sqlite_sequence",
"user_roles",
"user_social_accounts",
"users",
"webhook_deliveries",
"webhooks"
],
"missing_tables": [
"social_accounts"
],
"tables": {
"devices": 0,
"login_logs": 18,
"operation_logs": 50,
"password_histories": 0,
"permissions": 17,
"role_permissions": 20,
"roles": 2,
"user_roles": 1,
"users": 1,
"webhook_deliveries": 0,
"webhooks": 0
},
"sample_users": [
"e2e_admin"
]
}

View File

@@ -0,0 +1,40 @@
{
"generated_at": "2026-03-27T18:21:01+08:00",
"path": "D:\\project\\data\\user_management.db",
"file_size": 208896,
"existing_tables": [
"devices",
"login_logs",
"operation_logs",
"password_histories",
"permissions",
"role_permissions",
"roles",
"sqlite_sequence",
"user_roles",
"user_social_accounts",
"users",
"webhook_deliveries",
"webhooks"
],
"missing_tables": [
"social_accounts"
],
"tables": {
"devices": 0,
"login_logs": 18,
"operation_logs": 50,
"password_histories": 0,
"permissions": 17,
"role_permissions": 20,
"roles": 2,
"user_roles": 1,
"users": 1,
"webhook_deliveries": 0,
"webhooks": 0
},
"sample_users": [
"e2e_admin"
]
}

View File

@@ -0,0 +1,27 @@
# Config And Env Isolation Drill
- Generated at: 2026-03-27 18:21:06 +08:00
- Source DB: D:\project\data\user_management.db
- Isolated DB: D:\project\docs\evidence\ops\2026-03-27\config-isolation\20260327-182059\user_management.isolated.db
- Isolated config: D:\project\docs\evidence\ops\2026-03-27\config-isolation\20260327-182059\config.isolated.yaml
## Verification Results
- Base config default port: 8080
- UMS_CONFIG_PATH isolated port: 18085
- UMS_SERVER_PORT override port: 18086
- UMS_CORS_ALLOWED_ORIGINS override accepted origin: https://admin.example.com
- UMS_CORS_ALLOWED_ORIGINS override excluded origin: none
- auth capabilities with config-only override: {"password":true,"email_activation":false,"email_code":false,"sms_code":false,"password_reset":false,"admin_bootstrap_required":false,"oauth_providers":[]}
- auth capabilities with env override: {"password":true,"email_activation":false,"email_code":false,"sms_code":false,"password_reset":false,"admin_bootstrap_required":false,"oauth_providers":[]}
## Evidence Files
- config-only.stdout.log
- config-only.stderr.log
- env-override.stdout.log
- env-override.stderr.log
- capabilities.config-only.json
- capabilities.env-override.json
- config.isolated.yaml

View File

@@ -0,0 +1,12 @@
{
"password": true,
"email_activation": false,
"email_code": false,
"sms_code": false,
"password_reset": false,
"admin_bootstrap_required": false,
"oauth_providers": [
]
}

View File

@@ -0,0 +1,12 @@
{
"password": true,
"email_activation": false,
"email_code": false,
"sms_code": false,
"password_reset": false,
"admin_bootstrap_required": false,
"oauth_providers": [
]
}

View File

@@ -0,0 +1,215 @@
server:
port: 18085
mode: release # debug, release
read_timeout: 30s
read_header_timeout: 10s
write_timeout: 30s
idle_timeout: 60s
shutdown_timeout: 15s
max_header_bytes: 1048576
database:
type: sqlite # current runtime support: sqlite
sqlite:
path: "D:/project/docs/evidence/ops/2026-03-27/config-isolation/20260327-182059/user_management.isolated.db"
postgresql:
host: localhost
port: 5432
database: user_management
username: postgres
password: ""
ssl_mode: disable
max_open_conns: 100
max_idle_conns: 10
mysql:
host: localhost
port: 3306
database: user_management
username: root
password: ""
charset: utf8mb4
max_open_conns: 100
max_idle_conns: 10
cache:
l1:
enabled: true
max_size: 10000
ttl: 5m
l2:
enabled: false
type: redis
redis:
addr: localhost:6379
password: ""
db: 0
pool_size: 50
ttl: 30m
redis:
enabled: false
addr: localhost:6379
password: ""
db: 0
jwt:
algorithm: RS256
secret: ""
private_key_path: "./data/jwt/private.pem"
public_key_path: "./data/jwt/public.pem"
private_key_pem: ""
public_key_pem: ""
access_token_expire: 2h
refresh_token_expire: 168h # 7澶?= 168灏忔椂
security:
password_min_length: 8
password_require_special: true
password_require_number: true
login_max_attempts: 5
login_lock_duration: 30m
ratelimit:
enabled: true
login:
enabled: true
algorithm: token_bucket
capacity: 5
rate: 1
window: 1m
register:
enabled: true
algorithm: leaky_bucket
capacity: 3
rate: 1
window: 1h
api:
enabled: true
algorithm: sliding_window
capacity: 1000
window: 1m
monitoring:
prometheus:
enabled: true
path: /metrics
tracing:
enabled: false
endpoint: http://localhost:4318
service_name: user-management-system
logging:
level: info # debug, info, warn, error
format: json # json, text
output:
- stdout
- ./logs/app.log
rotation:
max_size: 100 # MB
max_age: 30 # days
max_backups: 10
admin:
username: ""
password: ""
email: ""
cors:
enabled: true
allowed_origins:
- "http://localhost:3000"
- "http://127.0.0.1:3000"
allowed_methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Content-Type
- X-Requested-With
- X-CSRF-Token
max_age: 3600
email:
host: "" # 鐢熶骇鐜濉啓鐪熷疄 SMTP Host
port: 18085
username: ""
password: ""
from_email: ""
from_name: "鐢ㄦ埛绠$悊绯荤粺"
sms:
enabled: false
provider: "" # aliyun, tencent锛涚暀绌鸿〃绀虹鐢ㄧ煭淇¤兘鍔? code_ttl: 5m
resend_cooldown: 1m
max_daily_limit: 10
aliyun:
access_key_id: ""
access_key_secret: ""
sign_name: ""
template_code: ""
endpoint: ""
region_id: "cn-hangzhou"
code_param_name: "code"
tencent:
secret_id: ""
secret_key: ""
app_id: ""
sign_name: ""
template_id: ""
region: "ap-guangzhou"
endpoint: ""
password_reset:
token_ttl: 15m
site_url: "http://localhost:8080"
# OAuth 绀句氦鐧诲綍閰嶇疆锛堢暀绌哄垯绂佺敤瀵瑰簲 Provider锛?
oauth:
google:
client_id: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/google/callback"
wechat:
app_id: ""
app_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/wechat/callback"
github:
client_id: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/github/callback"
qq:
app_id: ""
app_key: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/qq/callback"
alipay:
app_id: ""
private_key: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/alipay/callback"
sandbox: false
douyin:
client_key: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/douyin/callback"
# Webhook 鍏ㄥ眬閰嶇疆
webhook:
enabled: true
secret_header: "X-Webhook-Signature" # 绛惧悕 Header 鍚嶇О
timeout_sec: 30 # 鍗曟鎶曢€掕秴鏃讹紙绉掞級
max_retries: 3 # 鏈€澶ч噸璇曟鏁?
retry_backoff: "exponential" # 閫€閬跨瓥鐣ワ細exponential / fixed
worker_count: 4 # 鍚庡彴鎶曢€掑崗绋嬫暟
queue_size: 1000 # 鎶曢€掗槦鍒楀ぇ灏?
# IP 瀹夊叏閰嶇疆
ip_security:
auto_block_enabled: true # 鏄惁鍚敤鑷姩灏佺
auto_block_duration: 30m # 鑷姩灏佺鏃堕暱
brute_force_threshold: 10 # 鏆村姏鐮磋В闃堝€硷紙绐楀彛鍐呭け璐ユ鏁帮級
detection_window: 15m # 妫€娴嬫椂闂寸獥鍙?

View File

@@ -0,0 +1,59 @@
# Admin Bootstrap Closure Evidence
Generated at: `2026-03-27 17:39:14 +08:00`
## Scope
This evidence package covers the first-admin bootstrap closure for the current repository state:
- public backend endpoint: `POST /api/v1/auth/bootstrap-admin`
- public frontend route: `/bootstrap-admin`
- login/register first-run entry points
- supported-browser validation for `首次管理员初始化 -> 进入后台 -> 登出`
## Implemented closure
- Backend:
- added one-time admin bootstrap service flow guarded by `GET /api/v1/auth/capabilities -> admin_bootstrap_required`
- bootstrap now creates the first active admin, binds the `admin` role, issues a real session, and closes the bootstrap window afterward
- Frontend:
- added `/bootstrap-admin` page
- added login/register entry points when bootstrap is still required
- added post-bootstrap auto-login into `/dashboard`
- E2E:
- `frontend/admin/scripts/run-playwright-auth-e2e.ps1` no longer depends on startup-injected admin credentials
- the Playwright CDP suite now validates real bootstrap creation before the rest of the admin workflow scenarios
## Verification executed
```powershell
go test ./... -count=1
go build ./cmd/server
cd D:\project\frontend\admin
npm.cmd run lint
npm.cmd run test:run
npm.cmd run build
powershell -ExecutionPolicy Bypass -File .\scripts\run-playwright-auth-e2e.ps1
```
## Latest supported-browser result
The latest real-browser run completed with:
- `PASS admin-bootstrap`
- `PASS public-registration`
- `PASS email-activation`
- `PASS login-surface`
- `PASS auth-workflow`
- `PASS responsive-login`
- `PASS desktop-mobile-navigation`
- `Playwright CDP E2E completed successfully`
## Real boundary
- This closes the product loop for first-admin initialization in the current supported browser-validation environment.
- It does not change the previously stated external boundaries:
- no live third-party OAuth provider evidence yet
- no live external SMTP provider deliverability evidence yet
- no external production delivery/governance evidence beyond the local auditable package already formed in-repo

View File

@@ -0,0 +1,65 @@
# PRD 1.1 Email Activation Closure Evidence
Date: 2026-03-27
Scope: self-service email registration -> activation email delivery -> activation page -> successful login
## Closure Summary
- Added a real public frontend activation route: `/activate-account`.
- Activation emails now point to the frontend activation page instead of the raw backend API endpoint.
- Added public resend-activation entry points from:
- `/activate-account`
- `/login`
- `/register` success state for inactive email accounts
- Fixed a real frontend regression uncovered during closure:
- the activation page could consume one-time activation tokens twice under React StrictMode development execution and remain stuck on loading.
- the page now guards against duplicate activation requests while still allowing the successful request to commit UI state.
## Validation Executed
```powershell
$env:GOCACHE='D:\project\.gocache'
$env:GOMODCACHE='D:\project\.gomodcache'
go test ./... -count=1
go build ./cmd/server
cd D:\project\frontend\admin
npm.cmd run lint
npm.cmd run test:run
npm.cmd run build
powershell -ExecutionPolicy Bypass -File .\scripts\run-playwright-auth-e2e.ps1
```
## Supported Browser E2E Result
The updated `run-playwright-auth-e2e.ps1` starts:
- isolated backend
- isolated frontend
- isolated SQLite database
- isolated local SMTP capture service
- isolated CDP browser session
The real browser suite passed the following scenarios:
- `public-registration`
- `email-activation`
- `login-surface`
- `auth-workflow`
- `responsive-login`
- `desktop-mobile-navigation`
The new `email-activation` scenario verified:
1. create a self-service account with email
2. receive a real SMTP-delivered activation email through the local SMTP capture service
3. extract the activation link generated by the backend
4. open the frontend activation page in the real browser
5. complete backend activation successfully
6. return to login and sign in with the newly activated account
## Real Boundary
- This closes the product loop and supported-browser validation loop.
- It does not prove live external SMTP provider deliverability or third-party mailbox delivery behavior.
- External production evidence for real SMTP providers remains a separate environment-governance topic and should not be conflated with this closure.

View File

@@ -0,0 +1,45 @@
# SELF_SERVICE_REGISTER_CLOSURE_20260327-000848
## Scope
- PRD `1.1 多种注册方式`
- frontend self-service registration entry, page, route, and public workflow
- SMS register-code request contract normalization
- normal-user first-login redirect away from admin-only dashboard
## Implemented Closure
- Backend:
- retained the existing `POST /api/v1/auth/register` product API and closed the remaining client contract gap.
- `POST /api/v1/auth/send-code` now accepts both `purpose` and the legacy `scene` field, normalizing both onto the same SMS-purpose path for backward compatibility.
- Frontend:
- added `/register` as a real public route with username/password registration, optional nickname/email, and capability-gated phone registration.
- added a login-to-register product entry on `/login`.
- fixed SMS register/login send-code requests to use `purpose` instead of the mismatched `scene` payload.
- after registration, normal users are no longer dropped onto an admin-only dashboard path; `/dashboard` is now admin-guarded and non-admin first login lands on `/profile`.
- `/register` was added to the public-session whitelist so expired refresh-token cleanup does not incorrectly force-register users back to `/login`.
## Validation
- `go test ./... -count=1`
- `go build ./cmd/server`
- `cd D:\project\frontend\admin && npm.cmd run lint`
- `cd D:\project\frontend\admin && npm.cmd run test:run`
- `cd D:\project\frontend\admin && npm.cmd run build`
- `cd D:\project\frontend\admin && powershell -ExecutionPolicy Bypass -File .\scripts\run-playwright-auth-e2e.ps1`
## Real Browser Result
- `public-registration` now passes in the supported raw-CDP browser path.
- verified path:
- `/login` -> `创建账号`
- `/register` -> submit self-service registration
- success page -> `返回登录`
- login with newly registered normal user
- redirect settles on `/profile` instead of an admin-only dashboard error path
## Boundary
- phone registration remains capability-gated by configured Aliyun/Tencent SMS delivery.
- email activation still depends on SMTP-backed activation capability; the frontend supports the loop, but live SMTP delivery proof remains environment-dependent.
- this closes the product loop and supported-browser regression path; it does not change the separate boundary around live third-party OAuth provider evidence or external production delivery governance evidence.

View File

@@ -0,0 +1,26 @@
# Local Observability Baseline
- Generated at: 2026-03-27 18:20:30 +08:00
- Scope: single-node local baseline, not a production traffic certification result
## Concurrent Login Baseline
- Source command: `go test ./internal/e2e -run TestE2EConcurrentLogin -v -count=1`
- Concurrency configured by test: 20
- Result: success=2 fail=18 status=map[200:2 429:18] total=117.7617ms avg=13.83308ms
- Interpretation: current login rate limiter absorbs most burst traffic with 429, while successful requests remained sub-second and no 5xx appeared.
## Browser Flow Baseline
- Source command: `cd frontend/admin && npm.cmd run e2e:auth-smoke:win`
- login-initial: 99ms
- login-desktop: 186ms
- login-tablet: 117ms
- login-mobile: 96ms
- Interpretation: current raw CDP browser validation stayed well below the existing `HighResponseTime` alert threshold of 1s in `deployment/alertmanager/alerts.yml`.
## Evidence Files
- concurrent-login-20260327-182005.txt
- raw-cdp-auth-smoke-20260327-182005.txt

View File

@@ -0,0 +1,26 @@
=== RUN TestE2EConcurrentLogin
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/register | status: 200 | latency: 110.5421ms | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 611.7µs | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 429 | latency: 0s | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 200 | latency: 109.2177ms | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
2026/03/27 18:20:09 [API] 2026-03-27 18:20:09 POST /api/v1/auth/login | status: 200 | latency: 116.7157ms | ip: 127.0.0.1 | user_id: <nil> | ua: Go-http-client/1.1
e2e_test.go:397: 并发登录结果: 成功=2 失败=18 状态码分布=map[200:2 429:18] 总耗时=117.7617ms 平均=13.83308ms
--- PASS: TestE2EConcurrentLogin (0.24s)
PASS
ok github.com/user-management-system/internal/e2e 0.520s

View File

@@ -0,0 +1,34 @@
> admin@0.0.0 e2e:auth-smoke:win
> powershell -ExecutionPolicy Bypass -File ./scripts/run-cdp-auth-smoke.ps1
CDP browser: C:\Users\Admin\AppData\Local\ms-playwright\chromium_headless_shell-1208\chrome-headless-shell-win64\chrome-headless-shell.exe
CDP endpoint ready: http://127.0.0.1:64521/json/version
Launching command: node ./scripts/run-cdp-smoke.mjs
CDP smoke completed successfully
browser: HeadlessChrome/145.0.7632.6
title: 用户管理系统
capabilities: password=true email=false sms=false passwordReset=false
tabs:
forgot-password path: disabled
protected dashboard redirect: /login (from=/dashboard)
protected users redirect: /login (from=/users)
pre-login users redirect from: /users
login landing path: /users
user detail title: 用户详情
assign roles title: 分配角色 - e2e_admin
roles path: /roles
permissions title: 分配权限 - 管理员
dashboard path: /dashboard
logout path: /login
post-logout dashboard redirect: /login (from=/dashboard)
post-logout users redirect: /login (from=/users)
responsive:
- desktop: innerWidth=1920, bodyScrollWidth=1920
- tablet: innerWidth=768, bodyScrollWidth=768
- mobile: innerWidth=375, bodyScrollWidth=375
load timings:
- login-initial: 99ms
- login-desktop: 186ms
- login-tablet: 117ms
- login-mobile: 96ms

View File

@@ -0,0 +1 @@
npm warn Unknown user config "//git@github.com/" (git config --global url."https://github.com/".insteadOf ssh://git@github.com/). This will stop working in the next major version of npm.

View File

@@ -0,0 +1,70 @@
# 2026-03-27 Auth Session Hardening Remediation
## Scope
- Q-001 session hardening
- Q-002 OAuth return_to trust-boundary hardening
- Q-003 security-sensitive random fail-close hardening
- real-browser E2E closure after the auth/session model change
## Implemented Remediation
- Backend refresh continuity now uses a backend-managed `HttpOnly` refresh cookie.
- Frontend access token, current user, and current roles are memory-only; they are no longer persisted into `localStorage` or `sessionStorage`.
- Backend now also sets a non-sensitive session-presence cookie (`ums_session_present`) so the frontend can distinguish:
- "there may be a server session worth restoring"
- "there is clearly no session, so do not probe `/auth/refresh`"
- Frontend `AuthProvider` now:
- skips restore probing when the session-presence cookie is absent
- keeps restore probing available when the cookie exists, including page reload on protected pages
- stops performing its own redirect on restore failure and lets `RequireAuth` preserve the original `from` route
- exports effective auth state from the in-memory session store to avoid post-login route races
- OAuth `return_to` no longer trusts request-derived forwarded origin inference and is restricted to:
- absolute frontend paths
- explicitly allowlisted origins
- `crypto/rand` failure no longer silently degrades into weaker random generation for JWT JTI, email code, or captcha identifiers.
## Validation
Validated on 2026-03-27 with:
```powershell
go test ./... -count=1
go vet ./...
go build ./cmd/server
cd D:\project\frontend\admin
npm.cmd run test:run
npm.cmd run lint
npm.cmd run build
powershell -ExecutionPolicy Bypass -File .\scripts\run-playwright-auth-e2e.ps1
```
## Latest Real Result
- Backend gates: passed
- Frontend gates: passed
- Real browser CDP E2E: passed
- Verified E2E scenarios:
- `admin-bootstrap`
- `public-registration`
- `email-activation`
- `login-surface`
- `auth-workflow`
- `responsive-login`
- `desktop-mobile-navigation`
## Real Outcome
- Q-001 is no longer a current open high-risk issue in the project's implemented session model.
- Q-002 is no longer a current open high-risk issue in the OAuth frontend return path trust boundary.
- Q-003 is no longer a current open medium-risk issue in security-sensitive randomness handling.
- This remediation also closed a real regression introduced during the session hardening pass:
- public or unauthenticated route loads no longer emit browser console `400 Bad Request` noise from blind `/auth/refresh` probing
- protected-route redirects again preserve the original route intent through `RequireAuth`
## Remaining Real Gaps
- Q-004 automation coverage depth is still insufficient in several low-level/backend modules and key frontend containers.
- Q-005 dev toolchain SCA findings are still not fully cleared.
- Q-006 external alert delivery evidence is still not fully closed.

View File

@@ -0,0 +1,80 @@
# 2026-03-27 Q-004 Coverage Remediation
## Scope
- Objective: continue remediating `Q-004 自动化覆盖率不足,回归安全网偏薄`.
- This round focused on the highest-value low-coverage areas identified in the audit:
- Frontend: `router`, auth guards, `AdminLayout`, `ImportExportPage`
- Backend: `internal/database`, `internal/auth/providers`
## Changes
### Frontend
- Added route and guard regression tests:
- `frontend/admin/src/app/router.test.tsx`
- `frontend/admin/src/components/guards/guards.test.tsx`
- Added navigation and permission-surface tests:
- `frontend/admin/src/layouts/AdminLayout/AdminLayout.test.tsx`
- Added import/export product-flow tests:
- `frontend/admin/src/pages/admin/ImportExportPage/ImportExportPage.test.tsx`
- Tightened the new router test so it passes both `vitest` and `tsc -b`.
### Backend
- Added database bootstrap and upgrade-path coverage:
- `internal/database/db_test.go`
- Added provider auth URL/state coverage without live-network dependency:
- `internal/auth/providers/provider_urls_test.go`
- Fixed Windows-specific SQLite test cleanup by explicitly closing the underlying `sql.DB` handle in `db_test.go`.
- Removed an invalid provider test that attempted to hit the real Google endpoint during unit test execution.
## Verified Commands
```powershell
cd D:\project\frontend\admin
npm.cmd run test:run -- src/components/guards/guards.test.tsx src/app/router.test.tsx src/layouts/AdminLayout/AdminLayout.test.tsx src/pages/admin/ImportExportPage/ImportExportPage.test.tsx
npm.cmd run lint
npm.cmd run build
npm.cmd run test:coverage
cd D:\project
go test ./internal/database ./internal/auth/providers -count=1
go test ./... -count=1
go vet ./...
go test ./internal/auth/providers ./internal/database ./internal/repository -cover
```
## Results
### Frontend coverage
- Overall:
- statements `37.09%`
- branches `35.91%`
- functions `30.30%`
- lines `37.40%`
- Target modules:
- `src/app/router.tsx`: `47.72%` statements
- `src/components/guards/RequireAuth.tsx`: `100%`
- `src/components/guards/RequireAdmin.tsx`: `100%`
- `src/layouts/AdminLayout/AdminLayout.tsx`: `80.00%`
- `src/pages/admin/ImportExportPage/ImportExportPage.tsx`: `83.58%`
### Backend coverage
- `internal/database`: `83.2%`
- `internal/auth/providers`: `4.0%`
- `internal/repository`: `10.5%`
## Real Conclusion
- This round materially improves the regression net around routing, auth gating, admin navigation, import/export flows, and database bootstrap behavior.
- `Q-004` is improved but not fully closed.
- The real remaining coverage gaps are now concentrated in:
- Frontend: `UsersPage`, `WebhooksPage`, large parts of `ProfileSecurityPage`, and several service/http branches
- Backend: `internal/repository` and deeper `internal/auth/providers` token-validation/error branches
- Based on the current state, the next open remediation priority remains:
1. continue `Q-004` depth expansion
2. then `Q-005` dev toolchain SCA cleanup
3. then `Q-006` external alert delivery evidence closure

View File

@@ -0,0 +1,78 @@
# 2026-03-27 Q-004 Coverage Remediation Pass 2
## Scope
- Continue remediating `Q-004 自动化覆盖率不足,回归安全网偏薄`.
- This pass focused on:
- Frontend `WebhooksPage` and `services/webhooks.ts`
- Backend `internal/repository/webhook_repository.go`
- Frontend production build stability under current `Vite 8 + Windows + --configLoader native`
## Changes
### Frontend
- Added page-level regression tests for:
- `frontend/admin/src/pages/admin/WebhooksPage/WebhooksPage.test.tsx`
- Added service-level tests for:
- `frontend/admin/src/services/webhooks.test.ts`
- Added a Windows/Vite stability fix:
- `frontend/admin/vite.config.js`
- explicitly set `build.rollupOptions.input = 'index.html'` to avoid the native-loader absolute HTML input emission failure observed during `npm.cmd run build`
### Backend
- Added repository tests for:
- `internal/repository/webhook_repository_test.go`
- Hardened Webhook repository create behavior:
- `internal/repository/webhook_repository.go`
- explicit inactive status (`status=0`) is now preserved instead of being swallowed by the DB default during `Create`
## Verified Commands
```powershell
cd D:\project\frontend\admin
npm.cmd run test:run -- src/pages/admin/WebhooksPage/WebhooksPage.test.tsx src/services/webhooks.test.ts
npm.cmd run lint
npm.cmd run build
npm.cmd run test:coverage
cd D:\project
go test ./internal/repository -count=1
go test ./... -count=1
go vet ./...
go build ./cmd/server
go test ./internal/auth/providers ./internal/database ./internal/repository -cover
```
## Results
### Frontend coverage
- Overall:
- statements `41.06%`
- branches `38.48%`
- functions `36.00%`
- lines `41.47%`
- Target modules:
- `src/pages/admin/WebhooksPage/WebhooksPage.tsx`: `93.15%`
- `src/services/webhooks.ts`: `100%`
- `src/components/guards/RequireAuth.tsx`: `100%`
- `src/components/guards/RequireAdmin.tsx`: `100%`
- `src/layouts/AdminLayout/AdminLayout.tsx`: `80.00%`
- `src/pages/admin/ImportExportPage/ImportExportPage.tsx`: `83.58%`
### Backend coverage
- `internal/database`: `83.2%`
- `internal/repository`: `15.1%`
- `internal/auth/providers`: `4.0%`
## Real Conclusion
- This pass materially strengthens regression coverage for Webhook list/filter/action flows and their client adapters.
- It also closed one real build stability issue on Windows for the currently supported frontend build entry.
- `Q-004` is improved again, but still not fully closed.
- The current main remaining coverage gaps are:
- Frontend: `UsersPage`, deeper `ProfileSecurityPage`, and multiple service/http branches
- Backend: `internal/auth/providers` deep token-validation/error paths and the rest of `internal/repository`

View File

@@ -0,0 +1,97 @@
# 2026-03-27 Q-004 Coverage Remediation Pass 3
## Scope
- Continue remediating `Q-004 自动化覆盖率不足`.
- This pass stayed on the remaining real gaps instead of moving to `Q-005`.
- Focus areas:
- Frontend `UsersPage`
- Frontend deeper `ProfileSecurityPage` action branches
- Backend `internal/repository`
- Backend `internal/auth/providers`
## Changes
### Frontend
- Added page-level regression tests for:
- `frontend/admin/src/pages/admin/UsersPage/UsersPage.test.tsx`
- `frontend/admin/src/pages/admin/ProfileSecurityPage/ProfileSecurityPage.behavior.test.tsx`
- Newly covered user-facing branches include:
- `UsersPage`
- initial load
- keyword filter and reset
- refresh
- create/detail/edit/assign-role overlay flows
- self-delete guard
- all status transition branches (`1 -> 3`, `3 -> 1`, `2 -> 1`, `0 -> 1`)
- role-fetch failure
- page error retry
- `ProfileSecurityPage`
- TOTP setup open
- enable validation and success path
- disable validation and success path
- avatar type and size validation
- valid avatar upload success path
- device enable/disable/delete flows
### Backend
- Added repository regression tests in:
- `internal/repository/repository_additional_test.go`
- Added provider non-network coverage in:
- `internal/auth/providers/provider_urls_additional_test.go`
- Fixed one real repository defect in:
- `internal/repository/device.go`
- explicit `status=0` on device create was being swallowed by the DB default, causing inactive devices to persist as active
## Verified Commands
```powershell
cd D:\project\frontend\admin
npm.cmd run test:run -- src/pages/admin/UsersPage/UsersPage.test.tsx src/pages/admin/ProfileSecurityPage/ProfileSecurityPage.behavior.test.tsx
npm.cmd run lint
npm.cmd run build
npm.cmd run test:coverage
cd D:\project
$env:GOCACHE='D:\project\.tmp\gocache'
$env:GOPATH='D:\project\.tmp\go'
$env:GOMODCACHE='D:\project\.tmp\go\pkg\mod'
go test ./internal/repository ./internal/auth/providers -count=1
go test ./internal/auth/providers ./internal/repository -cover
go test ./... -count=1
go vet ./...
go build ./cmd/server
```
## Results
### Frontend coverage
- Overall:
- statements `49.18%`
- branches `42.86%`
- functions `44.92%`
- lines `49.79%`
- Target modules:
- `src/pages/admin/UsersPage/UsersPage.tsx`: `90.98%` statements, `68.75%` branches
- `src/pages/admin/ProfileSecurityPage/ProfileSecurityPage.tsx`: `70.17%` statements, `48.97%` branches
- `src/pages/admin/ProfileSecurityPage/ContactBindingsSection.tsx`: `85.29%` statements
- `src/pages/admin/WebhooksPage/WebhooksPage.tsx`: `93.15%`
- `src/pages/admin/ImportExportPage/ImportExportPage.tsx`: `83.58%`
### Backend coverage
- `internal/repository`: `37.1%`
- `internal/auth/providers`: `8.5%`
## Real Conclusion
- This pass materially reduced the previously explicit `UsersPage` and `ProfileSecurityPage` gaps.
- It also exposed and closed a real persistence bug in `DeviceRepository.Create`.
- `Q-004` has improved again, but it still cannot be honestly declared closed.
- The main remaining gaps are still:
- low-coverage frontend service layers and multiple untouched admin pages
- deeper `internal/auth/providers` non-network parsing/error paths
- the rest of `internal/repository` beyond the newly covered device/login-log/password-history/operation-log paths

View File

@@ -0,0 +1,86 @@
# 2026-03-27 Q-004 Coverage Remediation Pass 4
## Scope
- Continue remediating the remaining real `Q-004` gaps after Pass 3.
- This pass focused on:
- low-coverage frontend service adapters
- deeper non-network `internal/auth/providers` branches
## Changes
### Frontend
- Added adapter-level regression coverage in:
- `frontend/admin/src/services/service_adapters_additional.test.ts`
- Coverage added for:
- `users.ts`
- `roles.ts`
- `devices.ts`
- `profile.ts`
- `login-logs.ts`
- `operation-logs.ts`
- `permissions.ts`
- `stats.ts`
- `import-export.ts`
- During strict verification, `tsc -b` exposed two test-payload type mismatches in the new permission-service test data.
- These were fixed in the test code before final validation.
### Backend
- Added provider non-network tests in:
- `internal/auth/providers/provider_crypto_test.go`
- `internal/auth/providers/http_test.go` (expanded)
- Newly covered provider logic includes:
- Alipay private-key parsing for raw PKCS#8 and PEM PKCS#1 input
- Alipay parameter signing and signature verification
- Twitter PKCE verifier/challenge/auth URL generation
- OAuth helper error handling for empty-body and long-body non-2xx responses
## Verified Commands
```powershell
cd D:\project\frontend\admin
npm.cmd run test:run -- src/services/service_adapters_additional.test.ts src/services/users.test.ts
npm.cmd run lint
npm.cmd run build
npm.cmd run test:coverage
cd D:\project
$env:GOCACHE='D:\project\.tmp\gocache'
$env:GOPATH='D:\project\.tmp\go'
$env:GOMODCACHE='D:\project\.tmp\go\pkg\mod'
go test ./internal/auth/providers -count=1
go test ./internal/auth/providers ./internal/repository -cover
go test ./... -count=1
go vet ./...
go build ./cmd/server
```
## Results
### Frontend coverage
- Overall:
- statements `52.05%`
- branches `42.86%`
- functions `51.84%`
- lines `52.69%`
- Target areas:
- `services`: `86.2%` statements, `90.9%` branches
- `src/pages/admin/UsersPage/UsersPage.tsx`: `90.98%`
- `src/pages/admin/ProfileSecurityPage/ProfileSecurityPage.tsx`: `70.17%`
### Backend coverage
- `internal/auth/providers`: `15.2%`
- `internal/repository`: `37.1%`
## Real Conclusion
- This pass substantially reduced the frontend service-layer gap; that is no longer a primary blocker for `Q-004`.
- `internal/auth/providers` improved again, but it is still materially under-covered.
- `Q-004` still cannot be honestly declared closed.
- The main remaining real gaps are now concentrated in:
- more untouched admin pages/components
- deeper provider token/user-info parsing and error branches that can still be exercised without live network calls

View File

@@ -0,0 +1,100 @@
# 2026-03-27 Q-004 Coverage Remediation Pass 5
## Scope
- Continue remediating the remaining real `Q-004` gaps after Pass 4.
- This pass focused on:
- deeper non-network `internal/auth/providers` parsing and error branches
- low-coverage admin log pages
## Changes
### Frontend
- Added page-level regression coverage in:
- `frontend/admin/src/pages/admin/LoginLogsPage/LoginLogsPage.test.tsx`
- `frontend/admin/src/pages/admin/OperationLogsPage/OperationLogsPage.test.tsx`
- Newly covered page behavior includes:
- initial load
- filter application
- reset
- refresh
- pagination callback handling
- detail drawer open/close flow
- page-level error retry
### Backend
- Added provider non-network transport tests in:
- `internal/auth/providers/provider_http_roundtrip_test.go`
- The new tests replace live network dependencies with a fake `http.DefaultTransport`.
- Newly covered provider logic includes:
- `QQProvider.GetOpenID` success and invalid-JSON failure
- `QQProvider.GetUserInfo` API-error and success branches
- `WeiboProvider.ValidateToken` error / valid / ambiguous / invalid-JSON branches
- `WeChatProvider.ValidateToken` success / failure / invalid-JSON branches
- `GoogleProvider.ValidateToken` success and parse-failure branches
- `FacebookProvider.GetUserInfo` `error.message` and success branches
## Verified Commands
```powershell
cd D:\project\frontend\admin
npm.cmd run test:run -- src/pages/admin/LoginLogsPage/LoginLogsPage.test.tsx
npm.cmd run test:run -- src/pages/admin/OperationLogsPage/OperationLogsPage.test.tsx
npm.cmd run lint
npm.cmd run build
npm.cmd run test:coverage
cd D:\project
$env:GOCACHE='D:\project\.tmp\gocache'
$env:GOPATH='D:\project\.tmp\go'
$env:GOMODCACHE='D:\project\.tmp\go\pkg\mod'
go test ./internal/auth/providers -run 'Test(QQProviderGetOpenIDAndUserInfoWithDefaultTransport|WeiboProviderValidateTokenWithDefaultTransport|WeChatProviderValidateTokenWithDefaultTransport|GoogleProviderValidateTokenWithDefaultTransport|FacebookProviderGetUserInfoWithDefaultTransport)$' -count=1
go test ./... -count=1
go vet ./...
go build ./cmd/server
go test ./internal/auth/providers ./internal/repository -cover -count=1
```
## Results
### Frontend coverage
- Overall:
- statements `56.81%`
- branches `44.67%`
- functions `57.38%`
- lines `57.57%`
- Target areas:
- `services`: `86.2%` statements, `90.9%` branches
- `src/pages/admin/LoginLogsPage/LoginLogsPage.tsx`: `93.1%` statements, `55.55%` branches
- `src/pages/admin/OperationLogsPage/OperationLogsPage.tsx`: `91.52%` statements, `68.75%` branches
### Backend coverage
- `internal/auth/providers`: `28.7%`
- `internal/repository`: `37.1%`
## Validation Notes
- `npm.cmd run test:coverage` completed successfully with `28` passing test files and `96` passing tests.
- The same successful coverage run still printed one post-summary jsdom `AggregateError` network-noise line.
- This did not fail the command.
- It is still a real residual test-hygiene issue and should be traced separately instead of being ignored or misrepresented as a clean zero-noise run.
## Real Conclusion
- This pass materially reduced two real `Q-004` gaps:
- admin log pages are no longer among the main uncovered page-level hotspots
- `internal/auth/providers` improved substantially from the previous `15.2%`, but remains materially under-covered
- `Q-004` still cannot be honestly declared closed.
- The main remaining real gaps are now concentrated in:
- deeper `internal/auth/providers` paths beyond the new non-network parsing/error coverage
- still-uncovered admin pages/components such as:
- `PermissionsPage`
- `RolesPage`
- `ProfilePage`
- multiple admin drawers/modals that still remain near `0%`
- Secondary remaining gap:
- `internal/repository` has improved, but `37.1%` is still not a closure-grade depth

View File

@@ -0,0 +1,804 @@
# 2026-03-27 全量测试与质量审计
## 1.3 2026-03-28 Q-004 latest remediation note XIII
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `89.72 / 77.57 / 84.48 / 90.64`
- `src/app/App.tsx` is now `100 / 100 / 100 / 100`
- `src/app/RootLayout.tsx` is now `100 / 100 / 100 / 100`
- `src/components/common/ErrorBoundary/ErrorBoundary.tsx` is now `100 / 83.33 / 100 / 100`
- The latest remediation closed three more previously real frontend hotspots:
- `App.tsx` is no longer an open `Q-004` gap
- `RootLayout.tsx` is no longer an open `Q-004` gap
- `ErrorBoundary.tsx` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- after the shell and boundary layer were closed, the remaining higher-value frontend gaps narrow further to router, dashboard, and shared page-state coverage
- The validation hygiene note remains materially unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-110341.md`
## 1.2 2026-03-28 Q-004 latest remediation note XII
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `89.06 / 77.14 / 83.56 / 89.96`
- `src/pages/auth/ForgotPasswordPage/ForgotPasswordPage.tsx` is now `100 / 75 / 100 / 100`
- `src/pages/auth/ResetPasswordPage/ResetPasswordPage.tsx` is now `95 / 94.44 / 100 / 95`
- The latest remediation closed two more previously real frontend hotspots:
- `ForgotPasswordPage` is no longer an open `Q-004` gap
- `ResetPasswordPage` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- after the auth recovery pages were closed, the remaining higher-value frontend gaps shift more toward app shell, routing, error-boundary, and dashboard entry-point coverage
- The validation hygiene note remains materially unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-105226.md`
## 1.1 2026-03-28 Q-004 latest remediation note XI
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `85.89 / 74.91 / 81.87 / 86.71`
- `src/pages/admin/ProfileSecurityPage/ProfileSecurityPage.tsx` is now `90.35 / 75.51 / 92.45 / 90.13`
- The latest remediation closed one more previously real frontend hotspot:
- `src/pages/admin/ProfileSecurityPage/ProfileSecurityPage.tsx` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- with `client.ts` and `ProfileSecurityPage` closed, the next highest-value frontend gaps now shift toward auth recovery pages such as `ForgotPasswordPage` and `ResetPasswordPage`
- The validation hygiene note remains materially unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-104341.md`
## 1.0 2026-03-28 Q-004 latest remediation note X
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `83.86 / 72.68 / 79.87 / 84.72`
- `src/lib/http/client.ts` is now `100 / 92.30 / 100 / 100`
- The latest remediation closed one more previously real frontend hotspot:
- `src/lib/http/client.ts` is no longer an open `Q-004` gap
- This pass also closed one real validation-hygiene defect in production code:
- cached shared refresh waiters no longer leave an unhandled rejected promise behind when refresh fails
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- the remaining highest-value frontend gap is now more concentrated in deeper `ProfileSecurityPage`
- The validation hygiene note remains materially unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-102456.md`
## 0.9 2026-03-28 Q-004 latest remediation note IX
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `80.06 / 67.61 / 78.00 / 80.91`
- `src/lib/http/csrf.ts` is now `100 / 88.46 / 100 / 100`
- The latest remediation closed one more previously real frontend hotspot:
- `src/lib/http/csrf.ts` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- the remaining highest-value frontend gaps are now more concentrated in `src/lib/http/client.ts` and deeper `ProfileSecurityPage`
- The validation hygiene note remains materially unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-083841.md`
## 0.8 2026-03-28 Q-004 latest remediation note VIII
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `78.91 / 66.06 / 77.07 / 79.73`
- `src/pages/auth/RegisterPage/RegisterPage.tsx` is now `93.42 / 85.24 / 87.5 / 95.89`
- The latest remediation closed one more previously real frontend hotspot:
- `RegisterPage` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- the remaining highest-value frontend gaps are now more concentrated in deeper `ProfileSecurityPage` and `lib/http`
- The validation hygiene note remains materially unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-082843.md`
## 0.7 2026-03-28 Q-004 latest remediation note VII
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `78.38 / 64.77 / 76.92 / 79.19`
- `src/pages/auth/LoginPage/LoginPage.tsx` is now `92.56 / 84.09 / 86.2 / 95.61`
- The latest remediation closed one more previously real frontend hotspot:
- `LoginPage` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- the remaining highest-value frontend gaps are now more concentrated in `RegisterPage`, deeper `ProfileSecurityPage`, and `lib/http`
- The validation hygiene note remains materially unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- one concurrent `lint` + `build` attempt produced a transient Windows/Vite `index.html` emit-path failure, while the required standalone `build` rerun passed immediately afterward
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-081514.md`
## 0.6 2026-03-28 Q-004 latest remediation note VI
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `76.00 / 63.91 / 75.07 / 76.84`
- `src/app/providers` is now `96.38 / 93.75`
- `src/app/providers/AuthProvider.tsx` is now `100%`
- The latest remediation closed one more previously real frontend hotspot:
- `AuthProvider` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- the remaining highest-value frontend gaps are now more concentrated in `LoginPage`, `RegisterPage`, deeper `ProfileSecurityPage`, and `lib/http`
- The validation hygiene note remains unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-075725.md`
## 0.5 2026-03-28 Q-004 latest remediation note V
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `74.54 / 63.57 / 74.61 / 75.35`
- `src/pages/admin/UsersPage` is now `95.06%`
- `src/pages/admin/WebhooksPage` is now `94.92%`
- `internal/repository` is now `67.1%`
- The latest remediation closed two previously dominant frontend gap clusters:
- `UsersPage` drawers/modals are no longer one of the main remaining blockers
- `WebhooksPage` modal/drawer components are no longer one of the main remaining blockers
- A new real backend defect pair was discovered and fixed during this pass:
- `internal/repository/role.go`
- explicit `status=0` role creation was previously persisted as enabled
- `internal/repository/permission.go`
- explicit `status=0` permission creation was previously persisted as enabled
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- the remaining highest-value gaps are now more concentrated in deeper `ProfileSecurityPage`, `LoginPage`, `RegisterPage`, `AuthProvider`, `lib/http`, and still-remaining repository depth
- The validation hygiene note remains unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-011431.md`
## 0.4 2026-03-28 Q-004 latest remediation note IV
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `68.32 / 54.12 / 68.15 / 69.28`
- `src/pages/admin/RolesPage` is now at `94.53%`
- `src/pages/admin/PermissionsPage` is now at `93.51%`
- `src/pages/admin/ProfilePage/ProfilePage.tsx` is now at `91.42%`
- `internal/auth/providers` is now `80.6%`
- `internal/repository` remains `37.1%`
- The latest remediation changed the real gap map materially:
- provider coverage is no longer one of the dominant blockers
- `RolesPage`, `PermissionsPage`, and `ProfilePage` are no longer dominant uncovered admin page clusters
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- the remaining highest-value gaps are now concentrated in `internal/repository` depth plus still-uncovered frontend modal/drawer components, especially under `UsersPage` and `WebhooksPage`, and deeper remaining `ProfileSecurityPage` branches
- The validation hygiene note remains unchanged:
- `npm.cmd run test:coverage` passed again, but still emitted one post-summary jsdom `AggregateError` network-noise line
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-003416.md`
## 0.3 2026-03-27 Q-004 latest remediation note III
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `56.81 / 44.67 / 57.38 / 57.57`
- `src/pages/admin/LoginLogsPage/LoginLogsPage.tsx` is now at `93.1%`
- `src/pages/admin/OperationLogsPage/OperationLogsPage.tsx` is now at `91.52%`
- frontend `services` coverage remains `86.2%`
- `internal/auth/providers` is now `28.7%`
- `internal/repository` remains `37.1%`
- The latest remediation reduced two real gaps materially:
- admin log pages are no longer among the main page-level hotspots
- provider coverage is no longer extremely shallow, but it is still far from closure-grade depth
- A new validation hygiene note also appeared during this pass:
- `npm.cmd run test:coverage` passed, but still emitted one post-summary jsdom `AggregateError` network-noise line
- this is not a failing gate for the current pass, but it is still real and should not be misrepresented as a perfectly clean run
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- the main remaining gaps are now concentrated in deeper `internal/auth/providers` paths and still-uncovered admin pages/components
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-27/quality/COVERAGE_REMEDIATION_20260327-233824.md`
## 0.2 2026-03-27 Q-004 latest remediation note II
- `Q-004` was remediated further after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `52.05 / 42.86 / 51.84 / 52.69`
- frontend `services` coverage is now `86.2%`
- `internal/auth/providers` is now `15.2%`
- `internal/repository` remains `37.1%`
- The latest remediation reduced the frontend service-layer gap substantially.
- The updated real boundary is unchanged in principle:
- `internal/auth/providers` is still too shallow to truthfully mark `Q-004` closed
- there are still multiple uncovered admin pages/components outside the already-remediated `UsersPage` and `ProfileSecurityPage`
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-27/quality/COVERAGE_REMEDIATION_20260327-224352.md`
## 0.1 2026-03-27 Q-004 latest remediation note
- `Q-004` has been remediated further after this audit, but it is still not closed.
- Newly verified outcomes:
- frontend overall coverage is now `49.18 / 42.86 / 44.92 / 49.79`
- `UsersPage.tsx` is now at `90.98%` statements and `68.75%` branches
- `ProfileSecurityPage.tsx` is now at `70.17%` statements and `48.97%` branches
- `internal/repository` is now at `37.1%`
- `internal/auth/providers` is now at `8.5%`
- A new real defect was discovered and fixed during this remediation pass:
- `internal/repository/device.go`
- device create requests with explicit `status=0` were previously persisted as active because the DB default swallowed the zero value
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-27/quality/COVERAGE_REMEDIATION_20260327-221835.md`
- Updated real boundary:
- `UsersPage` and `ProfileSecurityPage` are no longer the primary blockers they were at audit time
- `internal/auth/providers` still remains too shallow to truthfully mark `Q-004` closed
## 0. 2026-03-27 优先级整改补充结论
以下内容覆盖本报告中 Q-001 / Q-002 / Q-003 的“当前真实状态”:
- Q-001 会话安全整改已完成并复验通过。
- 浏览器端不再把 access token、refresh token、用户信息、角色信息持久化到 `localStorage` / `sessionStorage`
- refresh continuity 已切到后端 `HttpOnly` refresh cookie。
- 为避免无会话用户访问受保护页时盲打 `/auth/refresh` 产生浏览器 `400 Bad Request` console error后端新增了非敏感会话存在标记 cookie前端先判断是否值得恢复再决定是否发起 refresh。
- Q-002 OAuth 信任边界整改已完成并复验通过。
- `return_to` 不再基于 `X-Forwarded-*` 推导的 request origin 做隐式同源放行。
- 当前只接受绝对路径,或显式 allowlist origin。
- Q-003 随机降级 fail-open 已完成整改并复验通过。
- `crypto/rand` 失败时不再静默退化到更弱随机源。
- 本轮补充整改还关闭了一个真实回归:
- 鉴于会话模型从 Web Storage 切到 cookie + memory 后,真实浏览器 E2E 一度出现公开页/无会话访问时的刷新噪音与登录后路由竞态。
- 该问题现已通过“会话存在标记 cookie + AuthProvider 恢复策略收敛 + 认证态导出去竞态 + E2E 基座修正”收口。
最新补充验证命令:
```powershell
go test ./... -count=1
go vet ./...
go build ./cmd/server
cd D:\project\frontend\admin
npm.cmd run test:run
npm.cmd run lint
npm.cmd run build
powershell -ExecutionPolicy Bypass -File .\scripts\run-playwright-auth-e2e.ps1
```
最新补充真实结论:
- Q-001 / Q-002 / Q-003 已不再是本项目“当前进行时”的开放问题。
- Q-004 已完成一轮增补整改并通过真实复验,但当前仍是“部分收口、未完全关闭”状态。
- Frontend overall coverage 已从审计时的 `29.38 / 29.32 / 24.84 / 29.78` 提升到 `41.06 / 38.48 / 36.00 / 41.47`
- 重点目标中:
- `router` 已到 `47.72%`
- `RequireAuth` / `RequireAdmin` 已到 `100%`
- `AdminLayout` 已到 `80.00%`
- `ImportExportPage` 已到 `83.58%`
- `WebhooksPage` 已到 `93.15%`
- `services/webhooks.ts` 已到 `100%`
- `internal/database` 已到 `83.2%`
- `internal/repository` 已到 `15.1%`
- 本轮还顺带关闭了一个真实构建问题:
- 在当前 Windows + `Vite 8` + `--configLoader native` 组合下,默认 HTML 输入会导致绝对路径 `index.html` 发射错误;现已通过显式 `rollupOptions.input = 'index.html'` 收口。
-`internal/auth/providers` 仍仅 `4.0%`,前端 `UsersPage` / `ProfileSecurityPage` 仍有明显缺口。
- 当前剩余真实缺口收敛为:
- Q-004 自动化覆盖率深度不足
- Q-005 dev toolchain SCA 未清零
- Q-006 外部告警交付证据未闭环
补充证据:
- [`docs/evidence/ops/2026-03-27/quality/AUTH_SESSION_REMEDIATION_20260327-194100.md`](/D:/project/docs/evidence/ops/2026-03-27/quality/AUTH_SESSION_REMEDIATION_20260327-194100.md)
- [`docs/evidence/ops/2026-03-27/quality/COVERAGE_REMEDIATION_20260327-212336.md`](/D:/project/docs/evidence/ops/2026-03-27/quality/COVERAGE_REMEDIATION_20260327-212336.md)
- [`docs/evidence/ops/2026-03-27/quality/COVERAGE_REMEDIATION_20260327-214422.md`](/D:/project/docs/evidence/ops/2026-03-27/quality/COVERAGE_REMEDIATION_20260327-214422.md)
## 1. 审计方法
- 会话内可用 skill 中没有现成的通用 testing/quality skill。
- 使用 `skill-installer` 检索了可安装技能,识别到 `playwright``security-best-practices` 可覆盖真实浏览器验证与安全审计。
- 由于当前沙箱对 skill 安装临时目录写入有限制,未能将 skill 正式安装到本地目录;本轮直接拉取并按其规范执行:
- `playwright`:以 CLI-first / real browser 为原则,沿用项目现有真实浏览器 E2E 路径验证。
- `security-best-practices`:按 Go backend + React/TypeScript frontend 的安全审计规则做证据化检查。
- 同时严格按照项目自身质量基线执行:`docs/team/QUALITY_STANDARD.md`
## 2. 已执行门禁
### 2.1 Backend
```powershell
go vet ./...
go test ./... -count=1
go build ./cmd/server
go test ./... -cover
```
结论:通过。
### 2.2 Frontend
```powershell
cd frontend/admin
npm.cmd run lint
npm.cmd run test:run
npm.cmd run build
npm.cmd run test:coverage
```
结论:通过。
### 2.3 Real Browser E2E
```powershell
cd frontend/admin
powershell -ExecutionPolicy Bypass -File .\scripts\run-playwright-auth-e2e.ps1
```
结论:通过。
本轮真实浏览器场景包含:
- `admin-bootstrap`
- `public-registration`
- `email-activation`
- `login-surface`
- `auth-workflow`
- `responsive-login`
- `desktop-mobile-navigation`
### 2.4 运维治理与交付证据
```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\ops\run-sca-evidence.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\ops\capture-local-baseline.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\ops\validate-alerting-package.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\ops\drill-alertmanager-render.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\ops\drill-sqlite-backup-restore.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\ops\drill-config-isolation.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\ops\drill-local-rollback.ps1
powershell -ExecutionPolicy Bypass -File .\scripts\ops\validate-secret-boundary.ps1
```
结论:
- SCA生产依赖通过完整依赖树未清零。
- 本地观测基线:通过。
- Alerting 包结构校验:通过,但外部通知闭环未完成。
- Alertmanager 渲染演练:通过。
- SQLite 备份恢复演练:通过。
- 配置/环境隔离演练:通过。
- 本地回滚演练:通过。
- 密钥边界校验:通过。
## 3. 正向结论
- 当前项目“可执行质量门禁”整体较强:后端、前端、真实浏览器 E2E、本地治理演练都能真实跑通。
- 真实浏览器链路已经不是 smoke 假闭环,而是可重复执行的产品级主链路验证。
- 前端未发现明显高信号 DOM XSS 反模式:
- 未扫到 `dangerouslySetInnerHTML`
- 未扫到 `eval/new Function/document.write`
- Release 模式下对 wildcard CORS 有显式拒绝测试,基础安全头中间件也已接入。
## 4. 真实问题清单
### Q-001 高风险:浏览器端仍将 access/refresh token 持久化到 Web Storage
- 位置:
- `frontend/admin/src/lib/storage/token-storage.ts:4-5`
- `frontend/admin/src/lib/storage/token-storage.ts:25-27`
- `frontend/admin/src/lib/http/auth-session.ts:5-6`
- `frontend/admin/src/lib/http/auth-session.ts:121-123`
- `frontend/admin/src/lib/http/auth-session.ts:140`
- `frontend/admin/src/lib/http/auth-session.ts:153`
- 证据:
- refresh token 落在 `localStorage`
- access token、用户信息、角色信息落在 `sessionStorage`
- 影响:
- 一旦前端发生 XSS、浏览器扩展注入或同机恶意读取令牌可被直接窃取。
- 这不符合企业级生产产品对会话凭证的保守策略。
- 结论:
- 当前“功能可用”不等于“会话安全成熟”。
- 更稳妥的方向应是 `HttpOnly + Secure + SameSite` cookie或 BFF / server session 模式。
### Q-002 高风险OAuth return_to 校验依赖未受信任代理证明的转发头
- 位置:
- `internal/api/handler/auth.go:511-524`
- `internal/api/handler/auth.go:567-588`
- 证据:
- `oauthRequestOrigin` 直接信任 `X-Forwarded-Proto``X-Forwarded-Host`
- `resolveOAuthReturnTo` 允许 `return_to` 与该 request origin 相同即通过
- 影响:
- 如果边缘代理未明确剥离/重写这些头,攻击者可能伪造头值影响 OAuth 回跳来源判断。
- 该问题至少会造成 origin trust 边界不清;在配置失误时可退化为开放跳转/回跳接收面扩大。
- 结论:
- 这是典型的“代码层看见依赖 forwarded headers但仓内没有可信代理证明”的问题。
- 当前应视为高风险边界项,而不是默认安全。
### Q-003 中风险:安全敏感随机值存在 fail-open 降级
- 位置:
- `internal/auth/jwt.go:62-65`
- `internal/service/email.go:295-297`
- `internal/service/captcha.go:142-145`
- 证据:
- `crypto/rand` 失败后JWT JTI / email code / captcha ID 会退化到时间戳或 `math/rand`
- 影响:
- 熵源异常时没有 fail closed而是继续生成可预测性更强的值。
- 这不是主路径问题,但不符合严格生产安全设计。
- 结论:
- 应改为显式报错并阻断相关安全流程,而不是静默降级。
### Q-004 中风险:自动化覆盖率不足,回归安全网偏薄
- Frontend 总覆盖率:
- statements `29.38%`
- branches `29.32%`
- functions `24.84%`
- lines `29.78%`
- Backend 覆盖率示例:
- `internal/service` `51.8%`
- `internal/api/handler` `31.4%`
- `internal/auth` `34.3%`
- `internal/auth/providers` `1.5%`
- `internal/repository` `10.5%`
- `internal/database` `0.0%`
- 影响:
- 当前 E2E 很强,但底层模块和异常分支的自动回归网仍然偏弱。
### Q-005 中风险:完整依赖树 SCA 未清零
- 结果:
- `npm audit production`: `0`
- `npm audit full`: `22`
- 其中 `21 moderate``1 high`
- `govulncheck reachable findings`: `0`
- 主要链路:
- `picomatch` 高危
- `vite` / `vitest` / `typescript-eslint` / `eslint` 相关 dev toolchain 链路存在中危项
- 影响:
- 生产依赖当前较干净。
- 但工程供应链本身还不能称为“完全收口”。
### Q-006 中风险:外部告警交付证据未闭环
- 结果:
- `Repo-level alerting package structurally ready: True`
- `Repo-level oncall/delivery package fully closed: False`
- 影响:
- 仓内模板、结构、演练已具备。
- 但真实外部通知联系人/渠道的交付闭环证据还缺。
## 5. 综合判断
### 5.1 已达到的水平
- 可以真实表述为:
- “项目当前可执行质量门禁整体通过,后端/前端/真实浏览器 E2E/本地治理演练已形成一轮真实闭环。”
### 5.2 不能夸大的表述
- 目前不能真实表述为:
- “已经完全达到企业级生产上线质量”
- “安全与治理材料全部闭环”
- “自动化测试覆盖已经充分”
### 5.3 真实状态
- 当前更准确的结论是:
- 执行层面很强,产品主链路和真实浏览器验证已明显成熟。
- 但安全会话模型、反向代理信任边界、覆盖率、dev 供应链漏洞、外部告警交付证据,仍是生产级质量的真实缺口。
## 6. 下一步优先级
1. 会话安全整改
- 移除 Web Storage 中的 access/refresh token 持久化。
- 切到 HttpOnly cookie 或 BFF / server session。
2. OAuth 信任边界整改
- 不再直接信任 `X-Forwarded-*`
- 显式配置 trusted proxy / trusted origin并补 runtime 证据。
3. fail-open 随机降级整改
- `crypto/rand` 失败即报错,不再退化到时间戳或 `math/rand`
4. 覆盖率提升
- Frontend 优先补 `AuthProvider``router``AdminLayout``UsersPage``WebhooksPage``ImportExportPage`
- Backend 优先补 `internal/auth/providers``internal/repository``internal/database`
5. 清理 dev toolchain SCA
- 升级 `vite/vitest/eslint/typescript-eslint` 及其传递依赖,消除 `picomatch` 链路风险。
6. 补齐真实外部告警交付证据
- 接入真实通知渠道并形成可审计投递记录。
## 7. 本轮证据
- `docs/team/QUALITY_STANDARD.md`
- `docs/status/REAL_PROJECT_STATUS.md`
- `docs/PROJECT_REVIEW_REPORT.md`
- `docs/evidence/ops/2026-03-27/e2e/ADMIN_BOOTSTRAP_CLOSURE_20260327-173914.md`
- `docs/evidence/ops/2026-03-27/sca/SCA_SUMMARY_20260327-181910.md`
- `docs/evidence/ops/2026-03-27/observability/LOCAL_BASELINE_20260327-182005.md`
- `docs/evidence/ops/2026-03-27/alerting/ALERTING_PACKAGE_20260327-182058.md`
- `docs/evidence/ops/2026-03-27/backup-restore/20260327-182059/`
- `docs/evidence/ops/2026-03-27/config-isolation/20260327-182059/`
- `docs/evidence/ops/2026-03-27/rollback/20260327-182059/`
- `docs/evidence/ops/2026-03-27/secret-boundary/20260327-181910/`
## 8. 2026-03-28 Q-004 Closure Update
- Real status update:
- `Q-004` is improved again, but still cannot be honestly declared closed.
- Newly closed frontend hotspot:
- `frontend/admin/src/app/router.tsx` is now at `100 / 100 / 100 / 100`.
- Validation evidence added:
- targeted router test
- full frontend `test:run`
- `lint`
- `build`
- full frontend `test:coverage`
- Current frontend full coverage after this pass:
- statements `90.74%`
- branches `77.74%`
- functions `87.40%`
- lines `90.87%`
- Main remaining `Q-004` frontend hotspots now narrow to:
- `src/pages/admin/DashboardPage/DashboardPage.tsx`
- `src/components/feedback/PageState/PageState.tsx`
- additional lower-coverage shared/admin surfaces outside this pass
- Real hygiene gap still open:
- the successful frontend coverage run still prints one post-summary jsdom `AggregateError` network-noise line
- Evidence:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-121611.md`
## 9. 2026-03-28 Dashboard Closure Update
- Real status update:
- `Q-004` improved again, but still cannot be honestly declared closed.
- Newly closed frontend hotspot:
- `frontend/admin/src/pages/admin/DashboardPage/DashboardPage.tsx` is now at `100 / 100 / 100 / 100`.
- Validation evidence added:
- targeted dashboard test
- `lint`
- `build`
- full frontend `test:coverage`
- Current frontend full coverage after this pass:
- statements `91.66%`
- branches `78.26%`
- functions `87.86%`
- lines `91.82%`
- Main remaining `Q-004` frontend hotspots now narrow to:
- `src/components/feedback/PageState/PageState.tsx`
- additional lower-coverage shared/admin surfaces outside this pass
- Real hygiene gap still open:
- the successful frontend coverage run still prints one post-summary jsdom `AggregateError` network-noise line
- Evidence:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-122517.md`
## 10. 2026-03-28 PageState Closure Update
- Real status update:
- `Q-004` improved again, but still cannot be honestly declared closed.
- Newly closed frontend hotspot:
- `frontend/admin/src/components/feedback/PageState/PageState.tsx` is now at `100 / 100 / 100 / 100`.
- Validation evidence added:
- targeted PageState test
- `lint`
- `build`
- full frontend `test:coverage`
- Current frontend full coverage after this pass:
- statements `91.71%`
- branches `78.52%`
- functions `88.01%`
- lines `91.86%`
- Main remaining `Q-004` frontend hotspots now narrow to:
- `src/layouts/AdminLayout/AdminLayout.tsx`
- `src/pages/admin/ImportExportPage/ImportExportPage.tsx`
- `src/lib/errors/AppError.ts`
- `src/lib/storage/token-storage.ts`
- additional lower-coverage shared/admin surfaces outside this pass
- Real hygiene gap still open:
- the successful frontend coverage run still prints one post-summary jsdom `AggregateError` network-noise line
- Evidence:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-123228.md`
## 11. 2026-03-28 AdminLayout Closure Update
- Real status update:
- `Q-004` improved again, but still cannot be honestly declared closed.
- Newly closed frontend hotspot:
- `frontend/admin/src/layouts/AdminLayout/AdminLayout.tsx` is now at `100 / 100 / 100 / 100`.
- Validation evidence added:
- targeted AdminLayout test
- `lint`
- `build`
- full frontend `test:coverage`
- Current frontend full coverage after this pass:
- statements `92.06%`
- branches `79.29%`
- functions `89.09%`
- lines `92.22%`
- Main remaining `Q-004` frontend hotspots now narrow to:
- `src/lib/storage/token-storage.ts`
- `src/lib/errors/AppError.ts`
- `src/pages/admin/ImportExportPage/ImportExportPage.tsx`
- `src/pages/NotFoundPage/NotFoundPage.tsx`
- additional lower-coverage shared/admin surfaces outside this pass
- Real hygiene gap still open:
- the successful frontend coverage run still prints one post-summary jsdom `AggregateError` network-noise line
- Evidence:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-124756.md`
## 12. 2026-03-28 Token Storage Closure Update
- Real status update:
- `Q-004` improved again, but still cannot be honestly declared closed.
- Newly closed frontend hotspot:
- `frontend/admin/src/lib/storage/token-storage.ts` is now at `100 / 100 / 100 / 100`.
- Validation evidence added:
- targeted token-storage test
- `lint`
- `build`
- full frontend `test:coverage`
- Current frontend full coverage after this pass:
- statements `92.32%`
- branches `79.63%`
- functions `89.70%`
- lines `92.49%`
- Main remaining `Q-004` frontend hotspots now narrow to:
- `src/lib/errors/AppError.ts`
- `src/pages/admin/ImportExportPage/ImportExportPage.tsx`
- `src/pages/NotFoundPage/NotFoundPage.tsx`
- additional lower-coverage shared/admin surfaces outside this pass
- Real hygiene gap still open:
- the successful frontend coverage run still prints one post-summary jsdom `AggregateError` network-noise line
- Evidence:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-125454.md`
## 13. 2026-03-28 AppError Closure Update
- Real status update:
- `Q-004` improved again, but still cannot be honestly declared closed.
- Newly closed frontend hotspot:
- `frontend/admin/src/lib/errors/AppError.ts` is now at `100 / 100 / 100 / 100`.
- `frontend/admin/src/lib/errors/index.ts` is now at `100 / 100 / 100 / 100`.
- Validation evidence added:
- targeted AppError module test
- `lint`
- `build`
- full frontend `test:coverage`
- Current frontend full coverage after this pass:
- statements `93.07%`
- branches `81.35%`
- functions `90.32%`
- lines `93.26%`
- Main remaining `Q-004` frontend hotspots now narrow to:
- `src/pages/admin/ImportExportPage/ImportExportPage.tsx`
- `src/pages/NotFoundPage/NotFoundPage.tsx`
- `src/lib/hooks/useBreadcrumbs.ts`
- `src/app/providers/ThemeProvider.tsx`
- additional lower-coverage shared/admin surfaces outside this pass
- Real hygiene gap still open:
- the successful frontend coverage run still prints one post-summary jsdom `AggregateError` network-noise line
- Evidence:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-140215.md`
## 1.4 2026-03-28 Q-004 latest remediation note XIV
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `93.56 / 81.95 / 90.93 / 93.71`
- `src/pages/admin/ImportExportPage/ImportExportPage.tsx` is now `100 / 100 / 100 / 100`
- The latest remediation closed one more previously real frontend hotspot:
- `ImportExportPage.tsx` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- after the import/export page was closed, the remaining higher-value frontend gaps narrow further to `NotFoundPage`, `useBreadcrumbs`, `ThemeProvider`, and the still-open coverage-noise hygiene issue
- The validation hygiene note changed slightly but remains materially open:
- `ImportExportPage` tests no longer emit the extra jsdom `window.getComputedStyle(..., pseudoElt)` noise from `rc-table`
- `npm.cmd run test:coverage` still passed again while emitting post-summary jsdom `AggregateError` network-noise lines
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-142248.md`
## 1.5 2026-03-28 Q-004 latest remediation note XV
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `93.69 / 81.95 / 91.24 / 93.85`
- `src/pages/NotFoundPage/NotFoundPage.tsx` is now `100 / 100 / 100 / 100`
- The latest remediation closed one more previously real frontend hotspot:
- `NotFoundPage.tsx` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- after the 404 page was closed, the remaining higher-value frontend gaps narrow further to `useBreadcrumbs`, `ThemeProvider`, and the still-open coverage-noise hygiene issue
- The validation hygiene note remains materially open:
- `npm.cmd run test:coverage` still passed again while emitting post-summary jsdom `AggregateError` network-noise lines
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-143209.md`
## 1.6 2026-03-28 Q-004 latest remediation note XVI
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `93.84 / 82.29 / 91.21 / 94.01`
- `src/lib/hooks/useBreadcrumbs.ts` is now `100 / 100 / 100 / 100`
- The latest remediation closed one more previously real frontend hotspot:
- `useBreadcrumbs.ts` is no longer an open `Q-004` gap
- This pass also removed one small piece of dead frontend complexity:
- the hook's parent-injection branch was redundant under the current route model and has been removed rather than artificially test-forced
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- after the breadcrumb hook was closed, the remaining higher-value frontend gaps narrow further to `ThemeProvider` plus the still-open coverage-noise hygiene issue
- The validation hygiene note remains materially open:
- `npm.cmd run test:coverage` still passed again while emitting post-summary jsdom `AggregateError` network-noise lines
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-144036.md`
## 1.7 2026-03-28 Q-004 latest remediation note XVII
- `Q-004` was remediated further again after the previous addendum and still remains open.
- Newly verified outcomes:
- frontend overall coverage is now `93.93 / 82.29 / 91.37 / 94.10`
- `src/app/providers/ThemeProvider.tsx` is now `100 / 100 / 100 / 100`
- The latest remediation closed one more previously real frontend hotspot:
- `ThemeProvider.tsx` is no longer an open `Q-004` gap
- The updated real boundary remains:
- `Q-004` still cannot be truthfully closed
- after the theme provider was closed, the remaining frontend gap for this closure track narrows to the still-open post-summary jsdom `AggregateError` coverage-noise issue
- The validation hygiene note remains materially open:
- `npm.cmd run test:coverage` still passed again while emitting post-summary jsdom `AggregateError` network-noise lines
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-144756.md`
## 1.8 2026-03-28 Q-004 latest remediation note XVIII
- `Q-004` for the `frontend/admin` closure track can now be truthfully closed.
- Newly verified outcomes:
- frontend overall coverage is now `93.98 / 82.29 / 91.37 / 94.15`
- `src/app/router.tsx` remains `100 / 100 / 100 / 100` in the latest full-suite coverage run
- full frontend coverage completed with `54` passing test files and `248` passing tests
- The final materially open blocker is now closed:
- the successful `npm.cmd run test:coverage` run no longer emits the previously recurring post-summary jsdom `AggregateError` network-noise lines
- The real closure boundary is now:
- all previously identified frontend hotspots in this `Q-004` closure track remain closed
- the validation hygiene path is clean enough to honestly close `Q-004`
- a separate npm global config warning still prints after command completion, but it is external environment noise rather than a project-generated failure
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/quality/COVERAGE_REMEDIATION_20260328-151952.md`
## 1.9 2026-03-28 Q-005 SCA closure note XIX
- `Q-005` can now be truthfully closed.
- Newly verified outcomes:
- `npm audit production` is now `0`
- `npm audit full` is now `0`
- `govulncheck reachable findings` remain `0`
- The remediation that closed the dev-toolchain supply-chain gap was:
- upgrade `vite` to `8.0.3`
- upgrade `vitest` and `@vitest/coverage-v8` to `4.1.2`
- upgrade `typescript-eslint` to `8.57.2`
- pin vulnerable transitive chains with `overrides` for `picomatch` and `brace-expansion`
- Re-verification after the dependency update also passed:
- `frontend/admin` `lint`
- `frontend/admin` production `build`
- full frontend `test:coverage`
- The updated real boundary is now:
- `Q-004` and `Q-005` are both closed for the current closure track
- the next unclosed cross-cutting governance gap is `Q-006` external alert delivery evidence
- the separate product/external-proof boundary around live third-party OAuth provider browser evidence also still remains
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-28/sca/SCA_SUMMARY_20260328-220806.md`
## 2.0 2026-03-29 Q-006 readiness note XX
- `Q-006` still cannot be truthfully closed, but the repo-side closure path is stricter than before.
- Newly verified outcomes:
- alerting package structural validation still passes on the latest run
- render drill still passes on the latest run
- a new strict live-delivery drill now exists and fails closed on placeholder/example values
- The latest repo-side hardening for this gap is:
- add `scripts/ops/drill-alertmanager-live-delivery.ps1`
- refuse unresolved placeholders, `example.*` addresses/hosts, and placeholder secrets before any network attempt
- emit only redacted config artifacts and masked recipient evidence
- remove the date-rollover false blocker in `validate-alerting-package.ps1` by falling back to the latest available baseline evidence
- The updated real boundary is now:
- repo-side alert delivery verification tooling is materially better prepared
- `Q-006` remains open because no real non-placeholder on-call delivery environment has been injected and no successful live SMTP acceptance evidence has yet been captured
- the remaining closure work is external-environment proof, not another repo-local template/rendering fix
- Latest evidence for this addendum:
- `docs/evidence/ops/2026-03-29/alerting/ALERTING_PACKAGE_20260329-100316.md`
- `docs/evidence/ops/2026-03-29/alerting/20260329-100315/ALERTMANAGER_RENDER_DRILL.md`
- `docs/evidence/ops/2026-03-29/alerting/20260329-100315/ALERTMANAGER_LIVE_DELIVERY_DRILL.md`

View File

@@ -0,0 +1,32 @@
# Rollback Drill
- Generated at: 2026-03-27 18:21:12 +08:00
- Source DB: D:\project\data\user_management.db
- Stable DB copy: D:\project\docs\evidence\ops\2026-03-27\rollback\20260327-182059\user_management.stable.db
- Probe port: 18087
## Drill Result
- Stable release started successfully before rollback gate evaluation.
- Candidate release was rejected by release-mode runtime validation before becoming healthy.
- Rollback to the previous stable config/artifact path completed successfully on the same probe port.
- Candidate rejection evidence: stderr matched release validation failure
- Stable capabilities before rollback: {"password":true,"email_activation":false,"email_code":false,"sms_code":false,"password_reset":false,"admin_bootstrap_required":false,"oauth_providers":[]}
- Stable capabilities after rollback: {"password":true,"email_activation":false,"email_code":false,"sms_code":false,"password_reset":false,"admin_bootstrap_required":false,"oauth_providers":[]}
## Scope Note
- This local drill validates rollback operational steps and health gates for the current artifact/config path.
- It does not prove cross-version schema downgrade compatibility between distinct historical releases.
## Evidence Files
- config.stable.yaml
- config.candidate.yaml
- stable-initial.stdout.log
- stable-initial.stderr.log
- candidate.stdout.log
- candidate.stderr.log
- stable-rollback.stdout.log
- stable-rollback.stderr.log

View File

@@ -0,0 +1,121 @@
server:
port: 18087
mode: release # debug, release
read_timeout: 30s
read_header_timeout: 10s
write_timeout: 30s
idle_timeout: 60s
shutdown_timeout: 15s
max_header_bytes: 1048576
database:
type: sqlite # current runtime support: sqlite
sqlite:
path: "D:/project/docs/evidence/ops/2026-03-27/rollback/20260327-182059/user_management.stable.db"
postgresql:
host: localhost
port: 5432
database: user_management
username: postgres
password: ""
ssl_mode: disable
max_open_conns: 100
max_idle_conns: 10
mysql:
host: localhost
port: 3306
database: user_management
username: root
password: ""
charset: utf8mb4
max_open_conns: 100
max_idle_conns: 10
cache:
l1:
enabled: true
max_size: 10000
ttl: 5m
l2:
enabled: false
type: redis
redis:
addr: localhost:6379
password: ""
db: 0
pool_size: 50
ttl: 30m
redis:
enabled: false
addr: localhost:6379
password: ""
db: 0
jwt:
algorithm: RS256
secret: ""
private_key_path: "./data/jwt/private.pem"
public_key_path: "./data/jwt/public.pem"
private_key_pem: ""
public_key_pem: ""
access_token_expire: 2h
refresh_token_expire: 168h # 7澶?= 168灏忔椂
security:
password_min_length: 8
password_require_special: true
password_require_number: true
login_max_attempts: 5
login_lock_duration: 30m
ratelimit:
enabled: true
login:
enabled: true
algorithm: token_bucket
capacity: 5
rate: 1
window: 1m
register:
enabled: true
algorithm: leaky_bucket
capacity: 3
rate: 1
window: 1h
api:
enabled: true
algorithm: sliding_window
capacity: 1000
window: 1m
monitoring:
prometheus:
enabled: true
path: /metrics
tracing:
enabled: false
endpoint: http://localhost:4318
service_name: user-management-system
logging:
level: info # debug, info, warn, error
format: json # json, text
output:
- stdout
- ./logs/app.log
rotation:
max_size: 100 # MB
max_age: 30 # days
max_backups: 10
admin:
username: ""
password: ""
email: ""
cors:
enabled: true
allowed_origins:
- "*"

View File

@@ -0,0 +1,215 @@
server:
port: 18087
mode: release # debug, release
read_timeout: 30s
read_header_timeout: 10s
write_timeout: 30s
idle_timeout: 60s
shutdown_timeout: 15s
max_header_bytes: 1048576
database:
type: sqlite # current runtime support: sqlite
sqlite:
path: "D:/project/docs/evidence/ops/2026-03-27/rollback/20260327-182059/user_management.stable.db"
postgresql:
host: localhost
port: 5432
database: user_management
username: postgres
password: ""
ssl_mode: disable
max_open_conns: 100
max_idle_conns: 10
mysql:
host: localhost
port: 3306
database: user_management
username: root
password: ""
charset: utf8mb4
max_open_conns: 100
max_idle_conns: 10
cache:
l1:
enabled: true
max_size: 10000
ttl: 5m
l2:
enabled: false
type: redis
redis:
addr: localhost:6379
password: ""
db: 0
pool_size: 50
ttl: 30m
redis:
enabled: false
addr: localhost:6379
password: ""
db: 0
jwt:
algorithm: RS256
secret: ""
private_key_path: "./data/jwt/private.pem"
public_key_path: "./data/jwt/public.pem"
private_key_pem: ""
public_key_pem: ""
access_token_expire: 2h
refresh_token_expire: 168h # 7澶?= 168灏忔椂
security:
password_min_length: 8
password_require_special: true
password_require_number: true
login_max_attempts: 5
login_lock_duration: 30m
ratelimit:
enabled: true
login:
enabled: true
algorithm: token_bucket
capacity: 5
rate: 1
window: 1m
register:
enabled: true
algorithm: leaky_bucket
capacity: 3
rate: 1
window: 1h
api:
enabled: true
algorithm: sliding_window
capacity: 1000
window: 1m
monitoring:
prometheus:
enabled: true
path: /metrics
tracing:
enabled: false
endpoint: http://localhost:4318
service_name: user-management-system
logging:
level: info # debug, info, warn, error
format: json # json, text
output:
- stdout
- ./logs/app.log
rotation:
max_size: 100 # MB
max_age: 30 # days
max_backups: 10
admin:
username: ""
password: ""
email: ""
cors:
enabled: true
allowed_origins:
- "http://localhost:3000"
- "http://127.0.0.1:3000"
allowed_methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Content-Type
- X-Requested-With
- X-CSRF-Token
max_age: 3600
email:
host: "" # 鐢熶骇鐜濉啓鐪熷疄 SMTP Host
port: 18087
username: ""
password: ""
from_email: ""
from_name: "鐢ㄦ埛绠$悊绯荤粺"
sms:
enabled: false
provider: "" # aliyun, tencent锛涚暀绌鸿〃绀虹鐢ㄧ煭淇¤兘鍔? code_ttl: 5m
resend_cooldown: 1m
max_daily_limit: 10
aliyun:
access_key_id: ""
access_key_secret: ""
sign_name: ""
template_code: ""
endpoint: ""
region_id: "cn-hangzhou"
code_param_name: "code"
tencent:
secret_id: ""
secret_key: ""
app_id: ""
sign_name: ""
template_id: ""
region: "ap-guangzhou"
endpoint: ""
password_reset:
token_ttl: 15m
site_url: "http://localhost:8080"
# OAuth 绀句氦鐧诲綍閰嶇疆锛堢暀绌哄垯绂佺敤瀵瑰簲 Provider锛?
oauth:
google:
client_id: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/google/callback"
wechat:
app_id: ""
app_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/wechat/callback"
github:
client_id: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/github/callback"
qq:
app_id: ""
app_key: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/qq/callback"
alipay:
app_id: ""
private_key: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/alipay/callback"
sandbox: false
douyin:
client_key: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/douyin/callback"
# Webhook 鍏ㄥ眬閰嶇疆
webhook:
enabled: true
secret_header: "X-Webhook-Signature" # 绛惧悕 Header 鍚嶇О
timeout_sec: 30 # 鍗曟鎶曢€掕秴鏃讹紙绉掞級
max_retries: 3 # 鏈€澶ч噸璇曟鏁?
retry_backoff: "exponential" # 閫€閬跨瓥鐣ワ細exponential / fixed
worker_count: 4 # 鍚庡彴鎶曢€掑崗绋嬫暟
queue_size: 1000 # 鎶曢€掗槦鍒楀ぇ灏?
# IP 瀹夊叏閰嶇疆
ip_security:
auto_block_enabled: true # 鏄惁鍚敤鑷姩灏佺
auto_block_duration: 30m # 鑷姩灏佺鏃堕暱
brute_force_threshold: 10 # 鏆村姏鐮磋В闃堝€硷紙绐楀彛鍐呭け璐ユ鏁帮級
detection_window: 15m # 妫€娴嬫椂闂寸獥鍙?

View File

@@ -0,0 +1,33 @@
# SCA Summary
- Generated at: 2026-03-27 18:19:41 +08:00
- Project root: D:\project
## Commands
- `cd frontend/admin && npm.cmd audit --omit=dev --json --registry=https://registry.npmjs.org/`
- `cd frontend/admin && npm.cmd audit --json --registry=https://registry.npmjs.org/`
- `go run golang.org/x/vuln/cmd/govulncheck@latest -json ./...`
## Exit Codes
- npm audit production: 0
- npm audit full: 1
- govulncheck: 0
## Findings
- npm audit production: info=0 low=0 moderate=0 high=0 critical=0 total=0
- npm audit full: info=0 low=0 moderate=21 high=1 critical=0 total=22
- govulncheck reachable findings: 0
- govulncheck reachable IDs: none
## Evidence Files
- npm-audit-prod-20260327-181910.json
- npm-audit-prod-20260327-181910.stderr.txt
- npm-audit-full-20260327-181910.json
- npm-audit-full-20260327-181910.stderr.txt
- govulncheck-20260327-181910.jsonl
- govulncheck-20260327-181910.stderr.txt

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,427 @@
{
"auditReportVersion": 2,
"vulnerabilities": {
"@eslint-community/eslint-utils": {
"name": "@eslint-community/eslint-utils",
"severity": "moderate",
"isDirect": false,
"via": [
"eslint"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@eslint-community/eslint-utils"
],
"fixAvailable": true
},
"@eslint/config-array": {
"name": "@eslint/config-array",
"severity": "moderate",
"isDirect": false,
"via": [
"minimatch"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@eslint/config-array"
],
"fixAvailable": true
},
"@eslint/eslintrc": {
"name": "@eslint/eslintrc",
"severity": "moderate",
"isDirect": false,
"via": [
"minimatch"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@eslint/eslintrc"
],
"fixAvailable": true
},
"@typescript-eslint/eslint-plugin": {
"name": "@typescript-eslint/eslint-plugin",
"severity": "moderate",
"isDirect": false,
"via": [
"@typescript-eslint/parser",
"@typescript-eslint/type-utils",
"@typescript-eslint/utils",
"eslint"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@typescript-eslint/eslint-plugin"
],
"fixAvailable": true
},
"@typescript-eslint/parser": {
"name": "@typescript-eslint/parser",
"severity": "moderate",
"isDirect": false,
"via": [
"@typescript-eslint/typescript-estree",
"eslint"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@typescript-eslint/parser"
],
"fixAvailable": true
},
"@typescript-eslint/type-utils": {
"name": "@typescript-eslint/type-utils",
"severity": "moderate",
"isDirect": false,
"via": [
"@typescript-eslint/typescript-estree",
"@typescript-eslint/utils",
"eslint"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@typescript-eslint/type-utils"
],
"fixAvailable": true
},
"@typescript-eslint/typescript-estree": {
"name": "@typescript-eslint/typescript-estree",
"severity": "moderate",
"isDirect": false,
"via": [
"minimatch",
"tinyglobby"
],
"effects": [
"@typescript-eslint/parser",
"@typescript-eslint/type-utils",
"@typescript-eslint/utils",
"typescript-eslint"
],
"range": "",
"nodes": [
"node_modules/@typescript-eslint/typescript-estree"
],
"fixAvailable": false
},
"@typescript-eslint/utils": {
"name": "@typescript-eslint/utils",
"severity": "moderate",
"isDirect": false,
"via": [
"@eslint-community/eslint-utils",
"@typescript-eslint/typescript-estree",
"eslint"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@typescript-eslint/utils"
],
"fixAvailable": true
},
"@vitejs/plugin-react": {
"name": "@vitejs/plugin-react",
"severity": "moderate",
"isDirect": true,
"via": [
"vite"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@vitejs/plugin-react"
],
"fixAvailable": false
},
"@vitest/coverage-v8": {
"name": "@vitest/coverage-v8",
"severity": "moderate",
"isDirect": true,
"via": [
"vitest"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@vitest/coverage-v8"
],
"fixAvailable": false
},
"@vitest/mocker": {
"name": "@vitest/mocker",
"severity": "moderate",
"isDirect": false,
"via": [
"vite"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/@vitest/mocker"
],
"fixAvailable": true
},
"brace-expansion": {
"name": "brace-expansion",
"severity": "moderate",
"isDirect": false,
"via": [
{
"source": 1115432,
"name": "brace-expansion",
"dependency": "brace-expansion",
"title": "brace-expansion: Zero-step sequence causes process hang and memory exhaustion",
"url": "https://github.com/advisories/GHSA-f886-m6hf-6m8v",
"severity": "moderate",
"cwe": [
"CWE-400"
],
"cvss": {
"score": 6.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H"
},
"range": "<5.0.5"
}
],
"effects": [
"minimatch"
],
"range": "",
"nodes": [
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion",
"node_modules/brace-expansion"
],
"fixAvailable": false
},
"eslint": {
"name": "eslint",
"severity": "moderate",
"isDirect": true,
"via": [
"@eslint-community/eslint-utils",
"@eslint/config-array",
"@eslint/eslintrc",
"minimatch"
],
"effects": [
"@eslint-community/eslint-utils",
"@typescript-eslint/eslint-plugin",
"eslint-plugin-react-hooks",
"eslint-plugin-react-refresh"
],
"range": "",
"nodes": [
"node_modules/eslint"
],
"fixAvailable": false
},
"eslint-plugin-react-hooks": {
"name": "eslint-plugin-react-hooks",
"severity": "moderate",
"isDirect": true,
"via": [
"eslint"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/eslint-plugin-react-hooks"
],
"fixAvailable": false
},
"eslint-plugin-react-refresh": {
"name": "eslint-plugin-react-refresh",
"severity": "moderate",
"isDirect": true,
"via": [
"eslint"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/eslint-plugin-react-refresh"
],
"fixAvailable": false
},
"fdir": {
"name": "fdir",
"severity": "moderate",
"isDirect": false,
"via": [
"picomatch"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/fdir"
],
"fixAvailable": true
},
"minimatch": {
"name": "minimatch",
"severity": "moderate",
"isDirect": false,
"via": [
"brace-expansion"
],
"effects": [
"@eslint/config-array",
"@eslint/eslintrc",
"@typescript-eslint/typescript-estree",
"eslint"
],
"range": "",
"nodes": [
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch",
"node_modules/minimatch"
],
"fixAvailable": false
},
"picomatch": {
"name": "picomatch",
"severity": "high",
"isDirect": false,
"via": [
{
"source": 1115384,
"name": "picomatch",
"dependency": "picomatch",
"title": "Picomatch has a ReDoS vulnerability via extglob quantifiers",
"url": "https://github.com/advisories/GHSA-c2c7-rcm5-vvqj",
"severity": "high",
"cwe": [
"CWE-1333"
],
"cvss": {
"score": 7.5,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
},
"range": ">=4.0.0 <4.0.4"
},
{
"source": 1115396,
"name": "picomatch",
"dependency": "picomatch",
"title": "Picomatch: Method Injection in POSIX Character Classes causes incorrect Glob Matching",
"url": "https://github.com/advisories/GHSA-3v7f-55p6-f55p",
"severity": "moderate",
"cwe": [
"CWE-1321"
],
"cvss": {
"score": 5.3,
"vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N"
},
"range": ">=4.0.0 <4.0.4"
}
],
"effects": [
"fdir",
"tinyglobby",
"vite",
"vitest"
],
"range": "",
"nodes": [
"node_modules/picomatch"
],
"fixAvailable": false
},
"tinyglobby": {
"name": "tinyglobby",
"severity": "moderate",
"isDirect": false,
"via": [
"fdir",
"picomatch"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/tinyglobby"
],
"fixAvailable": true
},
"typescript-eslint": {
"name": "typescript-eslint",
"severity": "moderate",
"isDirect": true,
"via": [
"@typescript-eslint/eslint-plugin",
"@typescript-eslint/parser",
"@typescript-eslint/typescript-estree",
"@typescript-eslint/utils",
"eslint"
],
"effects": [],
"range": "",
"nodes": [
"node_modules/typescript-eslint"
],
"fixAvailable": false
},
"vite": {
"name": "vite",
"severity": "moderate",
"isDirect": true,
"via": [
"picomatch",
"tinyglobby"
],
"effects": [
"@vitejs/plugin-react",
"@vitest/mocker"
],
"range": "",
"nodes": [
"node_modules/vite"
],
"fixAvailable": false
},
"vitest": {
"name": "vitest",
"severity": "moderate",
"isDirect": true,
"via": [
"@vitest/mocker",
"picomatch",
"tinyglobby",
"vite"
],
"effects": [
"@vitest/coverage-v8"
],
"range": "",
"nodes": [
"node_modules/vitest"
],
"fixAvailable": false
}
},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 21,
"high": 1,
"critical": 0,
"total": 22
},
"dependencies": {
"prod": 83,
"dev": 297,
"optional": 34,
"peer": 8,
"peerOptional": 0,
"total": 379
}
}
}

View File

@@ -0,0 +1 @@
npm warn Unknown user config "//git@github.com/" (git config --global url."https://github.com/".insteadOf ssh://git@github.com/). This will stop working in the next major version of npm.

View File

@@ -0,0 +1,22 @@
{
"auditReportVersion": 2,
"vulnerabilities": {},
"metadata": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 0,
"high": 0,
"critical": 0,
"total": 0
},
"dependencies": {
"prod": 83,
"dev": 297,
"optional": 34,
"peer": 8,
"peerOptional": 0,
"total": 379
}
}
}

View File

@@ -0,0 +1 @@
npm warn Unknown user config "//git@github.com/" (git config --global url."https://github.com/".insteadOf ssh://git@github.com/). This will stop working in the next major version of npm.

View File

@@ -0,0 +1,37 @@
# Secret Boundary Drill
- Generated at: 2026-03-27 18:19:30 +08:00
- Source DB: D:\project\data\user_management.db
- Isolated DB: D:\project\docs\evidence\ops\2026-03-27\secret-boundary\20260327-181910\user_management.secret-boundary.db
- Isolated config: D:\project\docs\evidence\ops\2026-03-27\secret-boundary\20260327-181910\config.secret-boundary.yaml
## Template Validation
- config template jwt.secret blank: True
- config template postgresql.password blank: True
- config template mysql.password blank: True
- forbidden placeholders removed from configs/config.yaml: True
- .gitignore protects local JWT key files: True
- .gitignore protects .env files: True
## Runtime Injection Validation
- Startup path: UMS_CONFIG_PATH + UMS_JWT_ALGORITHM + UMS_JWT_SECRET
- Synthetic JWT algorithm injected: HS256
- Synthetic JWT secret length: 45
- GET /health: pass
- GET /health/ready: pass
- GET /api/v1/auth/capabilities: {"password":true,"email_activation":false,"email_code":false,"sms_code":false,"password_reset":false,"admin_bootstrap_required":false,"oauth_providers":[]}
## Scope Note
- This drill proves the repo-level secret boundary and environment injection path are executable locally.
- It does not prove external secrets manager, KMS rotation, or CI/CD environment delivery evidence.
## Evidence Files
- server.stdout.log
- server.stderr.log
- capabilities.json
- config.secret-boundary.yaml

View File

@@ -0,0 +1,12 @@
{
"password": true,
"email_activation": false,
"email_code": false,
"sms_code": false,
"password_reset": false,
"admin_bootstrap_required": false,
"oauth_providers": [
]
}

View File

@@ -0,0 +1,216 @@
server:
port: 18088
mode: release # debug, release
read_timeout: 30s
read_header_timeout: 10s
write_timeout: 30s
idle_timeout: 60s
shutdown_timeout: 15s
max_header_bytes: 1048576
database:
type: sqlite # current runtime support: sqlite
sqlite:
path: "D:/project/docs/evidence/ops/2026-03-27/secret-boundary/20260327-181910/user_management.secret-boundary.db"
postgresql:
host: localhost
port: 5432
database: user_management
username: postgres
password: ""
ssl_mode: disable
max_open_conns: 100
max_idle_conns: 10
mysql:
host: localhost
port: 3306
database: user_management
username: root
password: ""
charset: utf8mb4
max_open_conns: 100
max_idle_conns: 10
cache:
l1:
enabled: true
max_size: 10000
ttl: 5m
l2:
enabled: false
type: redis
redis:
addr: localhost:6379
password: ""
db: 0
pool_size: 50
ttl: 30m
redis:
enabled: false
addr: localhost:6379
password: ""
db: 0
jwt:
algorithm: RS256
secret: ""
private_key_path: "./data/jwt/private.pem"
public_key_path: "./data/jwt/public.pem"
private_key_pem: ""
public_key_pem: ""
access_token_expire: 2h
refresh_token_expire: 168h # 7天 = 168小时
security:
password_min_length: 8
password_require_special: true
password_require_number: true
login_max_attempts: 5
login_lock_duration: 30m
ratelimit:
enabled: true
login:
enabled: true
algorithm: token_bucket
capacity: 5
rate: 1
window: 1m
register:
enabled: true
algorithm: leaky_bucket
capacity: 3
rate: 1
window: 1h
api:
enabled: true
algorithm: sliding_window
capacity: 1000
window: 1m
monitoring:
prometheus:
enabled: true
path: /metrics
tracing:
enabled: false
endpoint: http://localhost:4318
service_name: user-management-system
logging:
level: info # debug, info, warn, error
format: json # json, text
output:
- stdout
- ./logs/app.log
rotation:
max_size: 100 # MB
max_age: 30 # days
max_backups: 10
admin:
username: ""
password: ""
email: ""
cors:
enabled: true
allowed_origins:
- "http://localhost:3000"
- "http://127.0.0.1:3000"
allowed_methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed_headers:
- Authorization
- Content-Type
- X-Requested-With
- X-CSRF-Token
max_age: 3600
email:
host: "" # 生产环境填写真实 SMTP Host
port: 18088
username: ""
password: ""
from_email: ""
from_name: "用户管理系统"
sms:
enabled: false
provider: "" # aliyun, tencent留空表示禁用短信能力
code_ttl: 5m
resend_cooldown: 1m
max_daily_limit: 10
aliyun:
access_key_id: ""
access_key_secret: ""
sign_name: ""
template_code: ""
endpoint: ""
region_id: "cn-hangzhou"
code_param_name: "code"
tencent:
secret_id: ""
secret_key: ""
app_id: ""
sign_name: ""
template_id: ""
region: "ap-guangzhou"
endpoint: ""
password_reset:
token_ttl: 15m
site_url: "http://localhost:8080"
# OAuth 社交登录配置(留空则禁用对应 Provider
oauth:
google:
client_id: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/google/callback"
wechat:
app_id: ""
app_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/wechat/callback"
github:
client_id: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/github/callback"
qq:
app_id: ""
app_key: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/qq/callback"
alipay:
app_id: ""
private_key: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/alipay/callback"
sandbox: false
douyin:
client_key: ""
client_secret: ""
redirect_url: "http://localhost:8080/api/v1/auth/oauth/douyin/callback"
# Webhook 全局配置
webhook:
enabled: true
secret_header: "X-Webhook-Signature" # 签名 Header 名称
timeout_sec: 30 # 单次投递超时(秒)
max_retries: 3 # 最大重试次数
retry_backoff: "exponential" # 退避策略exponential / fixed
worker_count: 4 # 后台投递协程数
queue_size: 1000 # 投递队列大小
# IP 安全配置
ip_security:
auto_block_enabled: true # 是否启用自动封禁
auto_block_duration: 30m # 自动封禁时长
brute_force_threshold: 10 # 暴力破解阈值(窗口内失败次数)
detection_window: 15m # 检测时间窗口