feat(ql3): export redacted cluster evidence bundle

This commit is contained in:
whyour
2026-08-16 07:07:32 +08:00
parent 7955d55629
commit 4f62a86d15
17 changed files with 1308 additions and 25 deletions
+54 -1
View File
@@ -15,6 +15,7 @@ const REQUIRED_FILES = Object.freeze([
CLIENT_FILE,
ASSET_ROOT + '/index.html',
ASSET_ROOT + '/app.css',
ASSET_ROOT + '/evidence-bundle.js',
ASSET_ROOT + '/app.js',
DEPLOYMENT_ROOT + '/README.md',
DEPLOYMENT_ROOT + '/client-config.example.json',
@@ -130,6 +131,8 @@ function auditClusterCopilotConsole(options = {}) {
"'/api/v1/observe/run-list': 'run_list'",
"'/api/v1/observe/task-list': 'task_list'",
"'/api/v1/observe/workflow-list': 'workflow_list'",
"request.url === '/evidence-bundle.js'",
'assets.evidenceBundle as string',
"default-src 'none'",
"frame-ancestors 'none'",
"'cache-control': 'no-store'",
@@ -168,12 +171,17 @@ function auditClusterCopilotConsole(options = {}) {
'读取 Workflow Runs',
'显式读取诊断内容',
'模型文本是不可信内容',
'导出脱敏包',
'/evidence-bundle.js',
]);
expectFragments(ASSET_ROOT + '/app.js', [
"credentials: 'omit'",
"cache: 'no-store'",
'output.textContent = JSON.stringify(fact, null, 2)',
'nextPage(operation, request, fact)',
'measureClusterConsoleEvidenceRecord(record)',
'createClusterConsoleEvidenceBundle',
'URL.revokeObjectURL(objectUrl)',
"sessionToken = ''",
]);
rejectFragments(ASSET_ROOT + '/app.js', [
@@ -188,6 +196,35 @@ function auditClusterCopilotConsole(options = {}) {
'cancel',
'http://',
'https://',
'navigator.',
'setTimeout(',
]);
expectFragments(ASSET_ROOT + '/evidence-bundle.js', [
'qinglong/cluster-console-redacted-evidence-bundle@v1',
'maximumRecords: 16',
'maximumRawBytes: 8 * 1024 * 1024',
'maximumBundleBytes: 512 * 1024',
"generatedBy: 'browser_local'",
"actionAuthority: 'none'",
"attestation: 'none'",
"policy: 'fixed_allowlist_v1'",
'freeTextIncluded: false',
'copilotOutputIncluded: false',
'createClusterConsoleEvidenceBundle',
'verifyClusterConsoleEvidenceBundle',
]);
rejectFragments(ASSET_ROOT + '/evidence-bundle.js', [
'fetch(',
'XMLHttpRequest',
'WebSocket',
'EventSource',
'navigator.',
'localStorage',
'sessionStorage',
'setTimeout(',
'setInterval(',
'http://',
'https://',
]);
expectFragments(ASSET_ROOT + '/app.css', [
'@media (max-width: 520px)',
@@ -202,6 +239,11 @@ function auditClusterCopilotConsole(options = {}) {
'--port=0',
'TLS 1.3 `GET /readyz`',
'excluded from small router Edge/Standalone artifacts',
'Export a redacted evidence bundle',
'Export performs zero BFF or Cluster',
'8 MiB',
'512 KiB',
'server signature',
]);
rejectFragments(DEPLOYMENT_ROOT + '/README.md', [
'--host=0.0.0.0',
@@ -222,6 +264,8 @@ function auditClusterCopilotConsole(options = {}) {
'runPublishedConsoleContract(image);',
'consoleLoopback: true',
'consoleAssets: true',
'consoleEvidenceBundle: true',
"started.origin + '/evidence-bundle.js'",
"consolePublishedHostAddress: '127.0.0.1'",
'consoleDistributionEmbedded: true',
]);
@@ -341,7 +385,16 @@ function auditClusterCopilotConsole(options = {}) {
]),
legacyUiCoupled: false,
kubernetesResident: false,
assetCount: 3,
assetCount: 4,
evidenceBundle: Object.freeze({
lifecycle: 'browser-local-explicit-export',
maximumRecords: 16,
maximumRawBytes: 8 * 1024 * 1024,
maximumBundleBytes: 512 * 1024,
upstreamReadsOnExport: 0,
attestation: 'none',
actionAuthority: 'none',
}),
sourceFileCount: 4,
findings: Object.freeze(findings),
compatible: findings.length === 0,