mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
feat(ql3): export redacted cluster evidence bundle
This commit is contained in:
@@ -260,14 +260,22 @@ child.stdout.on('data', (chunk) => {
|
||||
response.on('data', (chunk) => chunks.push(chunk));
|
||||
response.once('end', () => {
|
||||
const body = Buffer.concat(chunks).toString('utf8');
|
||||
if (response.statusCode !== 200 || !body.includes('Cluster field ledger') || !body.includes('/app.css') || !body.includes('/app.js')) { finish(45); return; }
|
||||
child.once('close', (status, signal) => {
|
||||
if (status !== 0 || signal !== null) { finish(46); return; }
|
||||
settled = true;
|
||||
clearTimeout(timeout);
|
||||
process.stdout.write(JSON.stringify({ loopback: true, assets: true, cleanShutdown: true }));
|
||||
});
|
||||
child.kill('SIGTERM');
|
||||
if (response.statusCode !== 200 || !body.includes('Cluster field ledger') || !body.includes('/app.css') || !body.includes('/evidence-bundle.js') || !body.includes('/app.js')) { finish(45); return; }
|
||||
get(started.origin + '/evidence-bundle.js', (assetResponse) => {
|
||||
const assetChunks = [];
|
||||
assetResponse.on('data', (chunk) => assetChunks.push(chunk));
|
||||
assetResponse.once('end', () => {
|
||||
const asset = Buffer.concat(assetChunks).toString('utf8');
|
||||
if (assetResponse.statusCode !== 200 || !asset.includes('qinglong/cluster-console-redacted-evidence-bundle@v1') || !asset.includes('createClusterConsoleEvidenceBundle')) { finish(48); return; }
|
||||
child.once('close', (status, signal) => {
|
||||
if (status !== 0 || signal !== null) { finish(46); return; }
|
||||
settled = true;
|
||||
clearTimeout(timeout);
|
||||
process.stdout.write(JSON.stringify({ loopback: true, assets: true, evidenceBundle: true, cleanShutdown: true }));
|
||||
});
|
||||
child.kill('SIGTERM');
|
||||
});
|
||||
}).once('error', () => finish(49));
|
||||
});
|
||||
}).once('error', () => finish(47));
|
||||
});
|
||||
@@ -307,6 +315,7 @@ child.stdout.on('data', (chunk) => {
|
||||
if (
|
||||
result?.loopback !== true ||
|
||||
result?.assets !== true ||
|
||||
result?.evidenceBundle !== true ||
|
||||
result?.cleanShutdown !== true
|
||||
) {
|
||||
fail('Console live contract drifted');
|
||||
@@ -442,13 +451,17 @@ process.once('SIGINT', () => child.kill('SIGINT'));
|
||||
process.execPath,
|
||||
[
|
||||
'-e',
|
||||
"require('node:http').get(process.argv[1],(r)=>{const c=[];r.on('data',(x)=>c.push(x));r.on('end',()=>{const b=Buffer.concat(c).toString('utf8');if(r.statusCode!==200||!b.includes('Cluster field ledger'))process.exit(2);process.stdout.write(JSON.stringify({status:r.statusCode,assets:b.includes('/app.css')&&b.includes('/app.js')}));});}).on('error',()=>process.exit(3));",
|
||||
"const h=require('node:http');h.get(process.argv[1],(r)=>{const c=[];r.on('data',(x)=>c.push(x));r.on('end',()=>{const b=Buffer.concat(c).toString('utf8');if(r.statusCode!==200||!b.includes('Cluster field ledger')||!b.includes('/evidence-bundle.js'))process.exit(2);h.get(process.argv[1]+'/evidence-bundle.js',(a)=>{const d=[];a.on('data',(x)=>d.push(x));a.on('end',()=>{const s=Buffer.concat(d).toString('utf8');if(a.statusCode!==200||!s.includes('createClusterConsoleEvidenceBundle'))process.exit(4);process.stdout.write(JSON.stringify({status:r.statusCode,assets:b.includes('/app.css')&&b.includes('/app.js'),evidenceBundle:true}));});}).on('error',()=>process.exit(5));});}).on('error',()=>process.exit(3));",
|
||||
origin,
|
||||
],
|
||||
{ encoding: 'utf8', timeout: 5_000 },
|
||||
);
|
||||
const probeFact = JSON.parse(probe);
|
||||
if (probeFact.status !== 200 || probeFact.assets !== true) {
|
||||
if (
|
||||
probeFact.status !== 200 ||
|
||||
probeFact.assets !== true ||
|
||||
probeFact.evidenceBundle !== true
|
||||
) {
|
||||
fail('published Console host read drifted');
|
||||
}
|
||||
|
||||
@@ -536,6 +549,7 @@ function main() {
|
||||
contextReadiness: true,
|
||||
consoleLoopback: true,
|
||||
consoleAssets: true,
|
||||
consoleEvidenceBundle: true,
|
||||
consolePublishedHostAddress: '127.0.0.1',
|
||||
consoleDistributionEmbedded: true,
|
||||
isolation: Object.freeze({
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user