| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- import fs from 'node:fs';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import os from 'node:os';
- import { fileURLToPath, pathToFileURL } from 'node:url';
- import { spawnSync } from 'node:child_process';
- const self = fileURLToPath(import.meta.url);
- const sha256 = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
- const ENTRY_HASH = '7ef128f7d022bfbc15c0ec813bfd87995c6297c6efe407a563c840f014aba477';
- const LOCK_HASH = '2605cbe8fd7be43b5969a4991f978dc0768dee5b4d65d18f7af15f75e9004a79';
- const WORKSHOP_ENTRY_HASH = '7a04eb22d3429cdced23513717f36594f639539738994d45712a8bea738100a1';
- const IDENTITY_MODULE_HASH = '9224ef89a96425890d54961fda1f4fcae3403aa7c753fe1d70ac62082f2ff77e';
- const WORKSHOP_COMMANDS = ['workshop-start','workshop-save','project-create','project-progress','concept-save','project-status','workshop-import','workshop-dedup','workshop-search','workshop-read','workshop-contract','workshop-schema','employee-read','employee-name'];
- const fail = code => { throw Object.assign(new Error(code), { code }); };
- const requireThat = (value, code) => { if (!value) fail(code); };
- export function parseArgs(argv) {
- const [command, ...rest] = argv, args = { command };
- requireThat(['status','demo','connection','ingest','report-preview','report-submit',...WORKSHOP_COMMANDS].includes(command), 'USAGE');
- for (let i = 0; i < rest.length; i += 2) {
- const key = rest[i], value = rest[i + 1];
- requireThat(['--input','--request-id','--agent-profile'].includes(key) && value && !value.startsWith('--') && !args[key], 'USAGE');
- args[key] = value;
- }
- requireThat(['ingest','report-preview','report-submit',...WORKSHOP_COMMANDS].includes(command) === Boolean(args['--input']), 'USAGE');
- if(WORKSHOP_COMMANDS.includes(command))requireThat(Boolean(args['--request-id']), 'BAD_REQUEST_ID');
- if(args['--agent-profile'])requireThat(WORKSHOP_COMMANDS.includes(command)&&/^agent-(?:registration:[a-f0-9-]{36}|account:[a-f0-9]{64})$/.test(args['--agent-profile']),'INVALID_AGENT_KEY');
- if(WORKSHOP_COMMANDS.includes(command)&&!['project-status','workshop-search','workshop-read','workshop-contract','workshop-dedup','workshop-schema'].includes(command))requireThat(Boolean(args['--agent-profile']),'AGENT_PROFILE_REQUIRED');
- if (args['--input']) requireThat(path.isAbsolute(args['--input']), 'INPUT_PATH_MUST_BE_ABSOLUTE');
- if (args['--request-id']) requireThat(/^[\p{L}\p{N}_:@.-]{1,100}$/u.test(args['--request-id']), 'BAD_REQUEST_ID');
- return args;
- }
- function readPrivateKey(directory) {
- requireThat(/^\/opt\/data\/ksf-key-receive-20261031-[A-Za-z0-9]+$/.test(directory), 'BAD_KEY_DIRECTORY');
- requireThat(fs.realpathSync(directory) === directory, 'KEY_DIRECTORY_IS_SYMLINK');
- const dir = fs.lstatSync(directory), file = path.join(directory, 'recipient-private.pem'), stat = fs.lstatSync(file);
- requireThat(dir.isDirectory() && dir.uid === process.geteuid() && (dir.mode & 0o777) === 0o700, 'BAD_KEY_DIRECTORY_PERMISSIONS');
- requireThat(stat.isFile() && !stat.isSymbolicLink() && stat.uid === process.geteuid() && (stat.mode & 0o777) === 0o600, 'BAD_KEY_FILE_PERMISSIONS');
- const bytes = fs.readFileSync(file);
- try { return crypto.createPrivateKey(bytes); } finally { bytes.fill(0); }
- }
- // Test dependencies are module arguments only; the executable accepts no override flags.
- export async function executeSecure(argv, dependencies = {}) {
- const args = parseArgs(argv);
- const skillDir = dependencies.skillDir || path.resolve(path.dirname(self), '..');
- const readBytes = dependencies.readBytes || (file => fs.readFileSync(file));
- const spawn = dependencies.spawn || spawnSync;
- const workshop = WORKSHOP_COMMANDS.includes(args.command);
- const entry = path.join(skillDir, 'scripts', workshop ? 'ksf-workshop.mjs' : 'ksf.mjs');
- const expectedEntryHash = workshop ? WORKSHOP_ENTRY_HASH : ENTRY_HASH;
- requireThat(sha256(readBytes(entry)) === expectedEntryHash, 'ENTRY_HASH_MISMATCH');
- if(workshop)requireThat(sha256(readBytes(path.join(skillDir,'scripts','ksf-identity.mjs'))) === IDENTITY_MODULE_HASH, 'IDENTITY_MODULE_HASH_MISMATCH');
- requireThat(sha256(readBytes(path.join(skillDir, 'runtime-lock.json'))) === LOCK_HASH, 'RUNTIME_LOCK_HASH_MISMATCH');
- const inheritedEnv = dependencies.inheritedEnv || process.env;
- const env = { ...inheritedEnv };
- for (const name of Object.keys(env)) if (name.startsWith('KSF_')) delete env[name];
- Object.assign(env, {
- KSF_ADAPT_DIR: dependencies.runtimeDir || '/opt/data/ksf-adapt-training-20261031',
- KSF_R7_SKILL_DIR: dependencies.r7Dir || '/opt/data/ksf-baseline/r7/unpacked/skill',
- });
- let payload = null, binding = null;
- try {
- if (args.command !== 'demo') {
- binding = JSON.parse(readBytes(path.join(skillDir, 'credential-binding.json')));
- requireThat(binding.version === 'ksf.encrypted-entry.v1' && binding.purpose === 'KSF-credential-receive-20261031', 'BAD_BINDING');
- requireThat(binding.host === (dependencies.hostname || os.hostname()), 'WRONG_CONTAINER');
- requireThat(/^\/opt\/data\/.+/.test(binding.envelopeFile)
- && /^\/opt\/data\/.+/.test(binding.decryptModule), 'BAD_BINDING_PATHS');
- requireThat(/^[a-f0-9]{64}$/.test(binding.envelopeSha256)
- && /^[a-f0-9]{64}$/.test(binding.decryptModuleSha256)
- && /^[a-f0-9]{64}$/.test(binding.publicKeySha256), 'BAD_BINDING_HASHES');
- const envelopeBytes = readBytes(binding.envelopeFile);
- requireThat(envelopeBytes.length < 32768 && sha256(envelopeBytes) === binding.envelopeSha256, 'ENVELOPE_HASH_MISMATCH');
- const envelope = JSON.parse(envelopeBytes);
- requireThat(envelope.header?.recipientHost === binding.host
- && envelope.header?.recipientDirectory === binding.keyDirectory
- && envelope.header?.publicKeySha256 === binding.publicKeySha256, 'ENVELOPE_BINDING_MISMATCH');
- requireThat(sha256(readBytes(binding.decryptModule)) === binding.decryptModuleSha256, 'DECRYPT_MODULE_HASH_MISMATCH');
- const module = await (dependencies.importDecrypt || (file => import(pathToFileURL(file).href)))(binding.decryptModule);
- const key = (dependencies.readPrivateKey || readPrivateKey)(binding.keyDirectory);
- payload = module.decryptEnvelope(envelope, key, { host: binding.host, directory: binding.keyDirectory,
- publicKeySha256: binding.publicKeySha256 });
- Object.assign(env, payload.env);
- }
- const child = spawn(process.execPath, [entry, ...argv], {
- env, encoding: 'utf8', timeout: 600000, maxBuffer: 4 * 1024 * 1024, windowsHide: true,
- });
- requireThat(!child.error && [0,1].includes(child.status), 'ENTRY_PROCESS_FAILED');
- if (payload) requireThat(!String(child.stdout).includes(payload.env.KSF_SERVICE_TOKEN)
- && !String(child.stderr).includes(payload.env.KSF_SERVICE_TOKEN), 'OUTPUT_CONTAINS_SECRET');
- const result = JSON.parse(child.status === 0 ? child.stdout : child.stderr);
- requireThat(result.skill === 'ksf-workshop-runtime' && result.entry === entry
- && result.entrySha256 === expectedEntryHash && result.command === args.command
- && typeof result.invocationId === 'string' && /^[a-f0-9-]{36}$/.test(result.invocationId)
- && (!args['--request-id'] || result.requestId === args['--request-id']), 'ENTRY_OUTPUT_MISMATCH');
- requireThat(typeof result.ok === 'boolean' && (child.status === 0) === result.ok, 'ENTRY_EXIT_STATUS_MISMATCH');
- const output = {
- skill: 'ksf-workshop-runtime', invocationId: crypto.randomUUID(), at: new Date().toISOString(),
- wrapper: self, wrapperSha256: sha256(readBytes(self)), command: args.command,
- ...(args['--request-id'] ? { requestId: args['--request-id'] } : {}),
- ok: result.ok, credentialMode: payload ? 'encrypted-file-decrypted-per-command' : 'not-needed-for-demo',
- credentialLoadedForChild: Boolean(payload), credentialWrittenToDisk: false,
- gatewayEnvironmentModified: false, gatewayRestarted: false,
- ...(payload ? { credentialExpiresAt: payload.expiresAt, envelopeSha256: binding.envelopeSha256 } : {}),
- entryExitCode: child.status, entryResult: result,
- };
- if (payload) requireThat(!JSON.stringify(output).includes(payload.env.KSF_SERVICE_TOKEN), 'OUTPUT_CONTAINS_SECRET');
- return output;
- } finally {
- delete env.KSF_SERVICE_TOKEN;
- if (payload) delete payload.env.KSF_SERVICE_TOKEN;
- }
- }
- if (process.argv[1] && path.resolve(process.argv[1]) === self) {
- const base = { skill: 'ksf-workshop-runtime', invocationId: crypto.randomUUID(), at: new Date().toISOString(),
- wrapper: self, wrapperSha256: sha256(fs.readFileSync(self)) };
- try {
- requireThat(process.platform === 'linux', 'LINUX_REQUIRED');
- const result = await executeSecure(process.argv.slice(2));
- console.log(JSON.stringify(result, null, 2));
- if (!result.ok) process.exitCode = 1;
- } catch (error) {
- console.error(JSON.stringify({ ...base, ok: false, error: {
- code: /^[A-Z][A-Z0-9_]+$/.test(error.code || '') ? error.code : 'SECURE_ENTRY_FAILED',
- message: '执行停止;未显示凭据或原始异常。凭据过期需由管理端续期或轮换;请勿改指纹或绕过校验。',
- } }, null, 2));
- process.exitCode = 1;
- }
- }
|