import test from 'node:test'; import assert from 'node:assert/strict'; import {readFile,mkdtemp,cp,writeFile,rm,mkdir} from 'node:fs/promises'; import {tmpdir} from 'node:os'; import {join} from 'node:path'; import {renderPage} from '../cf-worker/src/view.js'; import {validateConfig,parseKeys,makeKeysText,commandsFor,renderKeys,build,root,digest} from '../scripts/build.mjs'; const config = {baseUrl:'https://ssh.example.com',repositoryUrl:'https://github.com/example/keys',defaultCron:'*/15 * * * *',allowEmpty:false}; const fixture = await readFile(new URL('./fixtures/keys.pub',import.meta.url),'utf8'); test('deployment configuration rejects shell injection and ambiguous origins',()=>{ assert.deepEqual(validateConfig(config),config); for(const baseUrl of ['http://x.example','https://user@x.example','https://x.example/','https://x.example/a','https://x.example;touch /tmp/x','https://x.example\n','https://x.example$(id)','https://x.example:443']) assert.throws(()=>validateConfig({...config,baseUrl})); for(const defaultCron of ['* * * * *\nevil','61 * * * *','*/0 * * * *','*/99 * * * *','* * * 13 *','* * * * * echo x']) assert.throws(()=>validateConfig({...config,defaultCron})); assert.throws(()=>validateConfig({...config,allowEmpty:'false'})); assert.throws(()=>validateConfig({...config,sshKeyUrl:'https://github.com/user.keys'})); }); test('keys are structurally validated, canonicalized and fingerprinted',async()=>{ const keys = await parseKeys('# a comment\r\n'+fixture.replace(/\n/g,'\r\n')); assert.equal(keys.length,1);assert.match(keys[0].fingerprint,/^SHA256:/); assert.equal(keys[0].text+'\n',fixture); assert.equal((await parseKeys(fixture.trimEnd())).length,1); }); test('rejects malformed keys, options, duplicate identities, private material and NUL',async()=>{ const key = fixture.trim();const [type,blob] = key.split(' '); for(const input of ['ssh-ed25519 not-a-key','ssh-rsa '+blob,`command="evil" ${key}`,`${key}\n${type} ${blob} different-comment`,'-----BEGIN OPENSSH PRIVATE KEY-----',key+'\0',`ssh-dss ${blob}`]) await assert.rejects(parseKeys(input)); }); test('empty revocation must be explicit; protocol is deterministic',async()=>{ assert.throws(()=>makeKeysText([], 'a'.repeat(40),false),/No server public keys/); const empty = makeKeysText([], 'a'.repeat(40),true); assert.match(empty.text,/# key-count 0\n# allow-empty yes\n/);assert.equal(empty.sha256,digest('')); const keys=await parseKeys(fixture);const bundle=makeKeysText(keys,'b'.repeat(40),false); assert.ok(bundle.text.endsWith(fixture));assert.equal(bundle.sha256,digest(fixture)); assert.throws(()=>makeKeysText(keys,'main',false),/commit SHA/); }); test('HTML escapes comments; commands use explicit script endpoint and separate actions',async()=>{ const keys=await parseKeys(fixture.trim()+' '); const html=renderKeys(keys);assert.ok(html.includes('<script>'));assert.ok(!html.includes(''; const html=renderPage(template,{revision:'e'.repeat(40),publishedAt:'2026-10-12',keys,commands:[],repositoryUrl:config.repositoryUrl,sha256:'f'.repeat(64)}); const json=JSON.parse(html.match(/