From e480c0edcbda6c44545131c6aa88204c8ed728de Mon Sep 17 00:00:00 2001 From: sunlei Date: Sat, 13 Jun 2026 18:39:10 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=B6=E6=95=9B=E8=BF=90=E8=A1=8C?= =?UTF-8?q?=E6=97=B6=E5=81=A5=E5=BA=B7=E5=85=AC=E5=BC=80=E5=93=8D=E5=BA=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- API.md | 5 +- README.md | 5 +- src/common/swagger/swagger-response.ts | 67 ------------------- .../evidence/runtime-evidence.service.ts | 15 +++-- src/runtime/health/runtime-health.service.ts | 1 - src/runtime/health/runtime-health.types.ts | 3 - test/common/swagger-response.spec.ts | 3 +- test/runtime/runtime-evidence.service.spec.ts | 30 +++++++++ .../runtime/runtime-health.controller.spec.ts | 51 -------------- test/runtime/runtime-health.service.spec.ts | 6 +- 10 files changed, 50 insertions(+), 136 deletions(-) diff --git a/API.md b/API.md index d8bac0f..49402bc 100644 --- a/API.md +++ b/API.md @@ -54,7 +54,7 @@ Admin、Component、Dict、MinIO、Blog 管理、WordPress 管理和 QQBot 管 | 方法 | 路径 | 认证 | 说明 | | ----- | ----------------- | ---- | ---------------------------------- | -| `GET` | `/health/runtime` | 否 | API 运行时健康和脱敏配置快照 | +| `GET` | `/health/runtime` | 否 | API 运行时健康和配置检查状态 | 该接口返回 plain JSON,不使用 Vben 响应包装,供本地 smoke、Jenkins/K8s 和 ktWorkflow 观测脚本直接读取。接口位于 Swagger 基础能力分组 `/api/basic`。 @@ -66,7 +66,8 @@ Admin、Component、Dict、MinIO、Blog 管理、WordPress 管理和 QQBot 管 | `checkedAt` | ISO 时间字符串 | | `status` | `live`、`ready`、`degraded` 或 `blocked` | | `checks` | 进程和配置检查列表 | -| `config` | 脱敏后的运行时配置快照,不包含原始 secret/token | + +公开响应不返回数据库、WordPress、Loki、NapCat SSH 等运行拓扑配置快照;配置检查只暴露 key 级别、是否存在和缺失说明。 状态含义: diff --git a/README.md b/README.md index 13ba53d..cd15ff8 100644 --- a/README.md +++ b/README.md @@ -128,10 +128,9 @@ API 暴露 `GET /health/runtime` 作为本地 smoke、Jenkins/K8s 和 ktWorkflow 返回内容包括: - `status`:`live`、`ready`、`degraded` 或 `blocked`。 -- `checks`:进程存活和运行时配置检查。 -- `config`:已脱敏的运行时配置快照,不包含原始密码、Token、Cookie、SSH key 或验证码票据。 +- `checks`:进程存活和运行时配置检查状态。 -`blocked` 表示关键配置缺失;`degraded` 表示可选运行时配置缺失,核心 API 仍可继续工作。本地未配置 Loki、WordPress、NapCat 等可选依赖时,健康状态可能保持 `degraded`。 +该公开入口不返回数据库、WordPress、Loki、NapCat SSH 等运行拓扑配置快照;配置检查只暴露 key 级别、是否存在和缺失说明。`blocked` 表示关键配置缺失;`degraded` 表示可选运行时配置缺失,核心 API 仍可继续工作。本地未配置 Loki、WordPress、NapCat 等可选依赖时,健康状态可能保持 `degraded`。 ## 核心规则 diff --git a/src/common/swagger/swagger-response.ts b/src/common/swagger/swagger-response.ts index b2ba486..5e1e4ac 100644 --- a/src/common/swagger/swagger-response.ts +++ b/src/common/swagger/swagger-response.ts @@ -1000,73 +1000,6 @@ function runtimeHealthExample() { message: 'QQBOT_NAPCAT_IMAGE is not configured', }, ], - config: { - app: { - nodeEnv: 'production', - port: 48085, - }, - database: { - host: 'mysql', - port: 3306, - database: 'kt_template', - username: 'kt', - synchronize: false, - }, - loki: { - transportEnabled: false, - httpRequestPushEnabled: false, - queryConfigured: false, - host: '', - queryHost: '', - environment: 'production', - tenantId: '', - username: '', - passwordConfigured: false, - }, - minio: { - endpoint: 'minio', - port: 9000, - useSSL: false, - accessKey: 'mi***ey', - bucket: 'kt-template-online', - }, - wordpress: { - baseUrl: 'https://blog.example.test', - hostHeader: 'blog.example.test', - adminUsername: 'wordpress-admin', - passwordConfigured: true, - timeoutMs: 15000, - loginTimeoutMs: 3000, - availabilityTtlMs: 60000, - }, - qqbot: { - reverseWsPath: '/qqbot/onebot/reverse', - reverseWsToken: 'qq***en', - napcatRoot: '/vol1/docker/kt-qqbot/napcat-instances', - napcatImage: '', - napcatContainerMode: 'ssh', - napcatSshTarget: 'nas', - napcatSshPort: 2202, - napcatSshKeyPath: '/home/kt/.ssh/napcat', - napcatReverseWsBase: 'ws://api.example.test/qqbot/onebot/reverse', - napcatWebuiBaseUrl: 'http://127.0.0.1:6099', - napcatWebuiToken: 'na***en', - }, - checks: [ - { - key: 'DB_HOST', - level: 'required', - present: true, - maskedValue: 'my***ql', - }, - { - key: 'QQBOT_NAPCAT_IMAGE', - level: 'optional', - present: false, - message: 'QQBOT_NAPCAT_IMAGE is not configured', - }, - ], - }, }; } diff --git a/src/runtime/evidence/runtime-evidence.service.ts b/src/runtime/evidence/runtime-evidence.service.ts index 010ea4d..4df7093 100644 --- a/src/runtime/evidence/runtime-evidence.service.ts +++ b/src/runtime/evidence/runtime-evidence.service.ts @@ -16,14 +16,21 @@ const SENSITIVE_TEXT_REPLACEMENTS: Array<[RegExp, string]> = [ REDACTED_BASE64_VALUE, ], [/\b[A-Za-z0-9+/]{120,}={0,2}\b/g, REDACTED_BASE64_VALUE], - [/\b(Authorization)\s*[:=]\s*Bearer\s+[^\s,;]+/gi, '$1='], - [/\b(Cookie)\s*[:=]\s*[^\s,;]+/gi, '$1='], + [/\b(Authorization)\s*[:=]\s*[^\r\n]+/gi, '$1='], + [/\b(Cookie)\s*[:=]\s*[^\r\n]+/gi, '$1='], [ new RegExp( - `(["'])(${SENSITIVE_TEXT_KEY_PATTERN})\\1\\s*:\\s*(["'])[^"']*\\3`, + `(["'])(${SENSITIVE_TEXT_KEY_PATTERN})\\1\\s*:\\s*(?:(["'])[^"']*\\3|[-+]?\\d+(?:\\.\\d+)?|true|false|null)`, 'gi', ), - '$1$2$1:$3$3', + '$1$2$1:""', + ], + [ + new RegExp( + `\\b(${SENSITIVE_TEXT_KEY_PATTERN})(\\s*[:=]\\s*)(["'])[^"']*\\3`, + 'gi', + ), + '$1$2$3$3', ], [ new RegExp( diff --git a/src/runtime/health/runtime-health.service.ts b/src/runtime/health/runtime-health.service.ts index 712e512..e95f29b 100644 --- a/src/runtime/health/runtime-health.service.ts +++ b/src/runtime/health/runtime-health.service.ts @@ -34,7 +34,6 @@ export class RuntimeHealthService { checkedAt: new Date().toISOString(), status: this.aggregateStatus(checks), checks, - config, }; } diff --git a/src/runtime/health/runtime-health.types.ts b/src/runtime/health/runtime-health.types.ts index b035e23..be515d4 100644 --- a/src/runtime/health/runtime-health.types.ts +++ b/src/runtime/health/runtime-health.types.ts @@ -1,5 +1,3 @@ -import type { RuntimeSafeConfigSnapshot } from '../config/runtime-config.types'; - export type RuntimeHealthStatus = 'live' | 'ready' | 'degraded' | 'blocked'; export interface RuntimeHealthCheck { @@ -15,5 +13,4 @@ export interface RuntimeHealthReport { checkedAt: string; status: RuntimeHealthStatus; checks: RuntimeHealthCheck[]; - config: RuntimeSafeConfigSnapshot; } diff --git a/test/common/swagger-response.spec.ts b/test/common/swagger-response.spec.ts index 604f401..7b285eb 100644 --- a/test/common/swagger-response.spec.ts +++ b/test/common/swagger-response.spec.ts @@ -29,19 +29,18 @@ describe('applySwaggerResponseExamples', () => { service: 'kt-template-online-api', status: 'degraded', checks: expect.any(Array), - config: expect.any(Object), }), ); expect(jsonContent.example).not.toHaveProperty('code'); expect(jsonContent.example).not.toHaveProperty('msg'); expect(jsonContent.example).not.toHaveProperty('data'); + expect(jsonContent.example).not.toHaveProperty('config'); expect(jsonContent.schema).toEqual( expect.objectContaining({ type: 'object', properties: expect.objectContaining({ service: expect.any(Object), checks: expect.any(Object), - config: expect.any(Object), }), }), ); diff --git a/test/runtime/runtime-evidence.service.spec.ts b/test/runtime/runtime-evidence.service.spec.ts index cadcd4f..371e2b0 100644 --- a/test/runtime/runtime-evidence.service.spec.ts +++ b/test/runtime/runtime-evidence.service.spec.ts @@ -249,4 +249,34 @@ describe('RuntimeEvidenceService', () => { expect(serialized).not.toContain('raw-json-client-secret'); expect(serialized).toContain('KT_SCAN_SAFE'); }); + + it('redacts cookie headers, quoted private key values, and non-string JSON secrets', () => { + const service = new RuntimeEvidenceService(); + + const record = service.createRecord({ + title: 'realistic secret text evidence', + taskType: 'api', + project: 'kt-template-online-api', + environment: 'local', + operation: 'runtime-evidence', + status: 'failed', + details: { + cookieHeader: + 'Cookie: admin_access_token=raw-cookie-token; wordpress_logged_in=raw-wordpress-cookie; theme=light', + quotedPrivateKey: + 'private_key="-----BEGIN PRIVATE KEY----- raw pem body -----END PRIVATE KEY-----"', + jsonText: + '{"accessToken":12345,"sid":67890,"client_secret":true,"sessionId":"KT_SCAN_SAFE"}', + }, + }); + + const serialized = JSON.stringify(record); + + expect(serialized).not.toContain('raw-cookie-token'); + expect(serialized).not.toContain('raw-wordpress-cookie'); + expect(serialized).not.toContain('raw pem body'); + expect(serialized).not.toContain('12345'); + expect(serialized).not.toContain('67890'); + expect(serialized).toContain('KT_SCAN_SAFE'); + }); }); diff --git a/test/runtime/runtime-health.controller.spec.ts b/test/runtime/runtime-health.controller.spec.ts index 60394a9..67a8298 100644 --- a/test/runtime/runtime-health.controller.spec.ts +++ b/test/runtime/runtime-health.controller.spec.ts @@ -8,57 +8,6 @@ describe('RuntimeHealthController', () => { checkedAt: '2026-06-13T00:00:00.000Z', status: 'ready', checks: [], - config: { - app: { nodeEnv: 'test', port: 48085 }, - database: { - host: 'mysql', - port: 3306, - database: 'kt', - username: 'root', - synchronize: false, - }, - loki: { - transportEnabled: false, - httpRequestPushEnabled: false, - queryConfigured: false, - host: '', - queryHost: '', - environment: 'test', - tenantId: '', - username: '', - passwordConfigured: false, - }, - minio: { - endpoint: 'minio', - port: 9000, - useSSL: false, - accessKey: 'mi***ey', - bucket: 'kt-template-online', - }, - wordpress: { - baseUrl: 'https://blog.example.test', - hostHeader: 'blog.example.test', - adminUsername: 'wordpress-admin', - passwordConfigured: true, - timeoutMs: 15000, - loginTimeoutMs: 3000, - availabilityTtlMs: 60000, - }, - qqbot: { - reverseWsPath: '/qqbot/onebot/reverse', - reverseWsToken: 'qq***en', - napcatRoot: '/vol1/docker/napcat', - napcatImage: 'mlikiowa/napcat-docker:latest', - napcatContainerMode: 'ssh', - napcatSshTarget: 'nas', - napcatSshPort: 2202, - napcatSshKeyPath: '/home/kt/.ssh/napcat', - napcatReverseWsBase: 'ws://api.example.test/onebot', - napcatWebuiBaseUrl: 'http://127.0.0.1:6099', - napcatWebuiToken: 'na***en', - }, - checks: [], - }, }; const service = { getRuntimeHealth: jest.fn(() => report) }; const controller = new RuntimeHealthController(service as any); diff --git a/test/runtime/runtime-health.service.spec.ts b/test/runtime/runtime-health.service.spec.ts index 4c492f5..b6c7d4b 100644 --- a/test/runtime/runtime-health.service.spec.ts +++ b/test/runtime/runtime-health.service.spec.ts @@ -78,11 +78,11 @@ describe('RuntimeHealthService', () => { expect.objectContaining({ service: 'kt-template-online-api', status: 'ready', - config: expect.objectContaining({ - minio: expect.objectContaining({ bucket: 'kt-template-online' }), - }), }), ); + expect(report).not.toHaveProperty('config'); + expect(JSON.stringify(report)).not.toContain('mysql'); + expect(JSON.stringify(report)).not.toContain('/vol1/docker/napcat'); expect(new Date(report.checkedAt).toISOString()).toBe(report.checkedAt); expect(report.checks).toContainEqual({ name: 'process',