SOURCE / PINNED RELEASE
Made of little things.
Powder Tool V600Billion
- Release
- 142767edcab8…
- Author-recorded commit
- 6d92971effd0…
- License
- LICENSE
- Author’s source reference
- nostr://npub1fllw8kw0thjj55wds0uugcnp5kej2nfxd36eruq39d56wwz8r44q5q78wj/wss%3A%2F%2Fgit.napplet.soy%2F/powder-toy
Archive hash verified: ed7d6a8ea7083197…. The source-to-build association is the author’s claim; it has not been independently rebuilt.
/**
* The built napplet (dist/), checked offline: one document with nothing to
* fetch; exactly the pinned single-thread build inlined (the glue with the
* known patch, the wasm); nothing that needs threads; its own no-network
* policy and the requires meta; the manifest template pinning the file. What
* the Hangar pins and grants is checked in nappelin.com
* (apps/hangar/test/powder-toy.test.mjs), against the docked copy.
*
* Skipped until there is a build: npm run wasm && npm run build.
*/
import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { existsSync, readFileSync, statSync } from 'node:fs';
import { gunzipSync } from 'node:zlib';
import test from 'node:test';
import { GLUE_PATCHES, upstream } from '../scripts/upstream.mjs';
const dist = new URL('../dist/', import.meta.url);
const built = existsSync(new URL('index.html', dist)) && existsSync(new URL('.nip5a-manifest.json', dist));
const skip = built ? false : 'no build in dist/ (npm run wasm && npm run build)';
const D_TAG = 'powder-toy';
const REQUIRES = ['identity', 'link', 'outbox', 'storage'];
const html = built ? readFileSync(new URL('index.html', dist)) : Buffer.alloc(0);
const text = html.toString('utf8');
const sha256 = createHash('sha256').update(html).digest('hex');
const manifest = built ? JSON.parse(readFileSync(new URL('.nip5a-manifest.json', dist), 'utf8')) : null;
const inlinedWasm = () => gunzipSync(Buffer.from(/id="powder-wasm" data-encoding="gzip\+base64">([A-Za-z0-9+/=]+)</.exec(text)[1], 'base64'));
test('one document under 10 MiB (napplet.soy\'s limit) with nothing to fetch', { skip }, () => {
assert.ok(statSync(new URL('index.html', dist)).size <= 10 * 1024 * 1024);
assert.doesNotMatch(text, /<script\b[^>]*\bsrc=/i, 'no external script');
assert.doesNotMatch(text, /<link\b[^>]*\brel="stylesheet"/i, 'no external stylesheet');
assert.match(text, /<meta name="napplet-requires" content="identity,link,outbox,storage">/);
assert.match(text, /connect-src 'none'|connect-src 'none'/);
assert.ok(text.indexOf('Content-Security-Policy') < text.indexOf('<script'), 'the policy comes before any script');
});
test('the pinned build is inlined: the glue with exactly the known patches, the wasm gzip-compressed', { skip }, () => {
const glue = /<script id="powder-glue">\n([\s\S]*?)\n<\/script>/.exec(text);
assert.ok(glue, 'glue inlined');
assert.match(glue[1], /var create_powder = /);
let original = glue[1];
for (const patch of GLUE_PATCHES) {
assert.equal(original.split(patch.replace).length - 1, patch.count ?? 1, `patch applied ${patch.count ?? 1} time(s): ${patch.find}`);
assert.equal(original.split(patch.find).length - 1, 0, `original left in: ${patch.find}`);
}
for (const patch of [...GLUE_PATCHES].reverse()) original = original.split(patch.replace).join(patch.find);
assert.equal(createHash('sha256').update(original).digest('hex'), upstream.build.files['powder.js'], 'the glue, unpatched, is the pinned build');
assert.match(text, /<script type="application\/octet-stream" id="powder-wasm" data-encoding="gzip\+base64">[A-Za-z0-9+/=]+<\/script>/);
assert.equal(createHash('sha256').update(inlinedWasm()).digest('hex'), upstream.build.files['powder.wasm'], 'the inlined wasm is the pinned build');
assert.match(text, new RegExp(`<meta name="powder-toy-upstream" content="${upstream.version} ${upstream.commit}">`));
});
/** napplet-conformance's static scan (boot/no-forbidden-globals), over the whole page. */
const FORBIDDEN = {
'window.nostr': /\bwindow\s*\.\s*nostr\b/,
'globalThis.nostr': /\bglobalThis\s*\.\s*nostr\b/,
fetch: /\b(?:window\s*\.\s*|globalThis\s*\.\s*)?fetch\s*\(/,
XMLHttpRequest: /\b(?:new\s+)?XMLHttpRequest\s*\(/,
WebSocket: /\b(?:new\s+)?WebSocket\s*\(/,
localStorage: /\b(?:window\s*\.\s*)?localStorage\b/,
sessionStorage: /\b(?:window\s*\.\s*)?sessionStorage\b/,
indexedDB: /\b(?:window\s*\.\s*)?indexedDB\b/,
'document.cookie': /\bdocument\s*\.\s*cookie\b/,
};
test('the page holds no browser authority: no network, no browser storage, no signer', { skip }, () => {
const found = Object.entries(FORBIDDEN).filter(([, re]) => re.test(text)).map(([label]) => label);
assert.deepEqual(found, [], 'forbidden surfaces in the page');
});
test('nothing in the page needs threads, workers or cross-origin isolation', { skip }, () => {
const module = inlinedWasm();
// The wasm defines its own, unshared memory: the memory section is there and no memory is imported.
const sections = new Set();
for (let at = 8; at < module.length;) {
const id = module[at++];
let size = 0, shift = 0, byte;
do { byte = module[at++]; size |= (byte & 0x7f) << shift; shift += 7; } while (byte & 0x80);
sections.add(id);
at += size;
}
assert.ok(sections.has(5), 'the module has its own memory section');
assert.doesNotMatch(text, /SharedArrayBuffer|new Worker\(|mainScriptUrlOrBlob|crossOriginIsolated/);
const policy = /http-equiv="Content-Security-Policy" content="([^"]+)"/.exec(text)[1].replaceAll(''', "'");
const directives = new Map(policy.split(';').map((part) => part.trim().split(/\s+/)).map(([name, ...values]) => [name, values]));
assert.deepEqual(directives.get('script-src'), ["'unsafe-inline'", "'wasm-unsafe-eval'"], 'no blob: scripts, no eval');
assert.deepEqual(directives.get('worker-src'), ["'none'"]);
});
test('the manifest template pins the file and says what the napplet requires', { skip }, () => {
assert.equal(manifest.kind, 35129);
const tags = new Map(manifest.tags.map((tag) => [tag[0], tag]));
assert.equal(tags.get('d')[1], D_TAG);
assert.deepEqual(tags.get('path'), ['path', '/index.html', sha256]);
assert.deepEqual(manifest.tags.filter((tag) => tag[0] === 'requires').map((tag) => tag[1]).sort(), REQUIRES);
// @napplet/vite-plugin's aggregate: sorted "<sha256> <path>\n" lines, hashed.
const aggregate = createHash('sha256').update(`${sha256} /index.html\n`).digest('hex');
assert.deepEqual(tags.get('x'), ['x', aggregate, 'aggregate']);
});
