fix: 加固运行时证据凭据脱敏

This commit is contained in:
sunlei 2026-06-13 18:52:19 +08:00
parent e480c0edcb
commit 269289a859
3 changed files with 53 additions and 3 deletions

View File

@ -4,6 +4,8 @@
**Goal:** Implement Phase 1 of the approved API runtime foundation: typed runtime config, classified runtime errors, structured runtime evidence, and a lightweight `GET /health/runtime` endpoint that can be used by Jenkins/K8s/ktWorkflow in the next plan.
**Post-review amendment:** The final public `GET /health/runtime` contract intentionally returns only `service`, `checkedAt`, `status`, and `checks`. Earlier code snippets in this plan that included `config` or described the endpoint as a safe config view were superseded during code review to avoid exposing runtime topology on an unauthenticated health endpoint. The internal `RuntimeConfigService` still owns the safe snapshot used to derive checks.
**Architecture:** Add a focused `src/runtime` Nest module. It depends on `ConfigModule` and `CommonModule`, exports typed runtime primitives, and keeps business modules unchanged during this phase. The endpoint returns plain machine-readable JSON, not a Vben response wrapper, so deployment tooling can consume it directly.
**Tech Stack:** NestJS 11, TypeScript 5.9, Jest 29 with `ts-jest`, existing `@nestjs/config`, existing `ToolsService`, existing Swagger/Knife4j setup.
@ -1245,11 +1247,12 @@ git commit -m "feat: 添加API运行时健康检查"
### Runtime health
The API exposes `GET /health/runtime` for deployment and local smoke checks.
Post-review note: the public response no longer returns `config`; it exposes status and config check results only.
It returns plain JSON with:
- `status`: `live`, `ready`, `degraded`, or `blocked`.
- `checks`: process and runtime config checks.
- `config`: a safe runtime config snapshot with secrets masked.
The endpoint is machine-readable and intentionally does not use the Vben
response wrapper.

View File

@ -7,9 +7,9 @@ import {
const REDACTED_VALUE = '<redacted>';
const REDACTED_BASE64_VALUE = '<redacted-base64>';
const SENSITIVE_KEY_PATTERN =
/password|secret|token|authorization|cookie|privatekey|sshkey|ticket|randstr|replytext|base64/i;
/password|secret|token|authorization|cookie|privatekey|sshkey|accesskey|apikey|ticket|randstr|replytext|base64/i;
const SENSITIVE_TEXT_KEY_PATTERN =
'(?:[A-Za-z0-9_-]*(?:password|secret|token|authorization|cookie|private[_-]?key|ssh[_-]?key|ticket|randstr|replyText|base64)[A-Za-z0-9_-]*|sid)';
'(?:[A-Za-z0-9_-]*(?:password|secret|token|authorization|cookie|private[_-]?key|ssh[_-]?key|access[_-]?key|api[_-]?key|ticket|randstr|replyText|base64)[A-Za-z0-9_-]*|sid)';
const SENSITIVE_TEXT_REPLACEMENTS: Array<[RegExp, string]> = [
[
/data:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[a-z0-9+/=\r\n]+/gi,
@ -18,6 +18,21 @@ const SENSITIVE_TEXT_REPLACEMENTS: Array<[RegExp, string]> = [
[/\b[A-Za-z0-9+/]{120,}={0,2}\b/g, REDACTED_BASE64_VALUE],
[/\b(Authorization)\s*[:=]\s*[^\r\n]+/gi, '$1=<redacted>'],
[/\b(Cookie)\s*[:=]\s*[^\r\n]+/gi, '$1=<redacted>'],
[
/\b((?:private|ssh)[_-]?key)(\s*[:=]\s*)-----BEGIN[\s\S]*?-----END [^-]+-----/gi,
'$1$2<redacted>',
],
[
/\b((?:private|ssh)[_-]?key)(\s*[:=]\s*)-----BEGIN[\s\S]*?(?=\s+[A-Za-z0-9_-]+\s*[:=]|\s*$)/gi,
'$1$2<redacted>',
],
[
new RegExp(
`\\b(${SENSITIVE_TEXT_KEY_PATTERN})(\\s*[:=]\\s*)Bearer\\s+[^\\s,;&]+`,
'gi',
),
'$1$2<redacted>',
],
[
new RegExp(
`(["'])(${SENSITIVE_TEXT_KEY_PATTERN})\\1\\s*:\\s*(?:(["'])[^"']*\\3|[-+]?\\d+(?:\\.\\d+)?|true|false|null)`,

View File

@ -279,4 +279,36 @@ describe('RuntimeEvidenceService', () => {
expect(serialized).not.toContain('67890');
expect(serialized).toContain('KT_SCAN_SAFE');
});
it('redacts access key and api key text plus unquoted multi-word secret values', () => {
const service = new RuntimeEvidenceService();
const record = service.createRecord({
title: 'access key evidence',
taskType: 'backend',
project: 'kt-template-online-api',
environment: 'local',
operation: 'runtime-evidence',
status: 'failed',
details: {
text:
'accessKey=raw-access-key access_key=raw-snake-access-key apiKey=raw-api-key api_key=raw-snake-api-key private_key=-----BEGIN PRIVATE KEY----- raw unquoted pem token=Bearer raw-bearer-token safe=value',
jsonText:
'{"accessKey":"raw-json-access-key","api_key":"raw-json-api-key","safe":"kept"}',
},
});
const serialized = JSON.stringify(record);
expect(serialized).not.toContain('raw-access-key');
expect(serialized).not.toContain('raw-snake-access-key');
expect(serialized).not.toContain('raw-api-key');
expect(serialized).not.toContain('raw-snake-api-key');
expect(serialized).not.toContain('raw unquoted pem');
expect(serialized).not.toContain('raw-bearer-token');
expect(serialized).not.toContain('raw-json-access-key');
expect(serialized).not.toContain('raw-json-api-key');
expect(serialized).toContain('safe=value');
expect(serialized).toContain('kept');
});
});