mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 00:17:47 +08:00
152 lines
4.3 KiB
JavaScript
152 lines
4.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
require('ts-node/register/transpile-only');
|
|
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
const {
|
|
createLegacyShadowPrimaryGateReceipt,
|
|
} = require('../back/runtime/domain/legacyShadowPrimaryGate');
|
|
|
|
const MAX_EVIDENCE_BYTES = 1024 * 1024;
|
|
|
|
function parseArguments(argv) {
|
|
const options = { profile: 'edge' };
|
|
for (const argument of argv) {
|
|
if (argument === '--') continue;
|
|
if (argument.startsWith('--profile=')) {
|
|
options.profile = argument.slice('--profile='.length);
|
|
} else if (argument.startsWith('--capture=')) {
|
|
options.capturePath = path.resolve(argument.slice('--capture='.length));
|
|
} else if (argument.startsWith('--terminal=')) {
|
|
options.terminalPath = path.resolve(argument.slice('--terminal='.length));
|
|
} else if (argument.startsWith('--resource=')) {
|
|
options.resourcePath = path.resolve(argument.slice('--resource='.length));
|
|
} else if (argument.startsWith('--output=')) {
|
|
options.outputPath = path.resolve(argument.slice('--output='.length));
|
|
} else if (argument.startsWith('--generated-at-ms=')) {
|
|
const raw = argument.slice('--generated-at-ms='.length);
|
|
if (!/^\d+$/u.test(raw)) {
|
|
throw new TypeError('--generated-at-ms must be an integer');
|
|
}
|
|
options.generatedAtMs = Number(raw);
|
|
} else {
|
|
throw new TypeError(`Unsupported argument: ${argument}`);
|
|
}
|
|
}
|
|
if (options.profile !== 'edge' && options.profile !== 'standalone') {
|
|
throw new TypeError('--profile must be edge or standalone');
|
|
}
|
|
for (const name of [
|
|
'capturePath',
|
|
'terminalPath',
|
|
'resourcePath',
|
|
'outputPath',
|
|
]) {
|
|
if (!options[name] || !path.isAbsolute(options[name])) {
|
|
throw new TypeError(`--${name.replace('Path', '')} is required`);
|
|
}
|
|
}
|
|
if (
|
|
options.generatedAtMs !== undefined &&
|
|
(!Number.isSafeInteger(options.generatedAtMs) || options.generatedAtMs < 0)
|
|
) {
|
|
throw new TypeError('--generated-at-ms is invalid');
|
|
}
|
|
return options;
|
|
}
|
|
|
|
function readEvidence(sourcePath) {
|
|
const descriptor = fs.openSync(
|
|
sourcePath,
|
|
fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW ?? 0),
|
|
);
|
|
try {
|
|
const stat = fs.fstatSync(descriptor);
|
|
if (
|
|
!stat.isFile() ||
|
|
(stat.mode & 0o077) !== 0 ||
|
|
stat.size < 2 ||
|
|
stat.size > MAX_EVIDENCE_BYTES
|
|
) {
|
|
throw new TypeError('Primary gate evidence file shape is invalid');
|
|
}
|
|
const bytes = Buffer.alloc(stat.size);
|
|
let offset = 0;
|
|
while (offset < bytes.length) {
|
|
const count = fs.readSync(
|
|
descriptor,
|
|
bytes,
|
|
offset,
|
|
bytes.length - offset,
|
|
offset,
|
|
);
|
|
if (count === 0) throw new Error('Primary gate evidence read stalled');
|
|
offset += count;
|
|
}
|
|
return {
|
|
value: JSON.parse(bytes.toString('utf8')),
|
|
};
|
|
} finally {
|
|
fs.closeSync(descriptor);
|
|
}
|
|
}
|
|
|
|
function writeReceipt(outputPath, receipt) {
|
|
const descriptor = fs.openSync(outputPath, 'wx', 0o600);
|
|
try {
|
|
fs.writeFileSync(descriptor, `${JSON.stringify(receipt)}\n`, 'utf8');
|
|
fs.fsyncSync(descriptor);
|
|
} finally {
|
|
fs.closeSync(descriptor);
|
|
}
|
|
}
|
|
|
|
function run(options) {
|
|
const capture = readEvidence(options.capturePath);
|
|
const terminal = readEvidence(options.terminalPath);
|
|
const resource = readEvidence(options.resourcePath);
|
|
const receipt = createLegacyShadowPrimaryGateReceipt({
|
|
profile: options.profile,
|
|
generatedAtMs: options.generatedAtMs ?? Date.now(),
|
|
capture: capture.value,
|
|
terminal: terminal.value,
|
|
resource: resource.value,
|
|
});
|
|
if (receipt.assessment !== 'eligible') {
|
|
const error = new Error(
|
|
`Legacy Shadow Primary gate is ineligible: ${receipt.violations.join(
|
|
',',
|
|
)}`,
|
|
);
|
|
error.code = 'QL3_PRIMARY_GATE_INELIGIBLE';
|
|
throw error;
|
|
}
|
|
writeReceipt(options.outputPath, receipt);
|
|
return receipt;
|
|
}
|
|
|
|
function main() {
|
|
const receipt = run(parseArguments(process.argv.slice(2)));
|
|
process.stdout.write(`${JSON.stringify(receipt)}\n`);
|
|
}
|
|
|
|
module.exports = {
|
|
MAX_EVIDENCE_BYTES,
|
|
parseArguments,
|
|
readEvidence,
|
|
run,
|
|
writeReceipt,
|
|
};
|
|
|
|
if (require.main === module) {
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
process.stderr.write(
|
|
`${error instanceof Error ? error.message : String(error)}\n`,
|
|
);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|