Inspector page: static SvelteKit site with /inspect (plan step 5)
A prerendered static site (adapter-static) with a landing page and
/inspect, which runs spec §63 steps 1 to 8 on a .dkc chosen with the file
picker, dropped anywhere on the page, or taken from the official fixtures
bundled at build time. It shows every step, the decoded header, the unlock
date in UTC and local time, and each extension's id, version, criticality,
length and hex, with a text view and an informative CBOR diagnostic view,
all escaped and labelled as unauthenticated before step 15. Copiar JSON
copies the exact "datekeys inspect -json" view.
No network: a hash-mode Content-Security-Policy with connect-src 'self'
is the first element of every page, and scripts/check-build.mjs verifies
it, the fixtures and the absence of external URLs after every build.
Large files are read only up to what steps 1 to 8 need.
Reviewed for design and accessibility (WCAG AA contrast, keyboard,
focus, live status, 360 px), security and correctness; 262 tests pass,
svelte-check has no warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// Vite configuration of the SvelteKit site. The library tests run with
|
|
|
|
|
// vitest.config.ts, which vitest prefers when both files exist.
|
|
|
|
|
import { sveltekit } from '@sveltejs/kit/vite';
|
Phase 2, step 8: the open action of /inspect
After steps 1 to 8, a valid capsule whose date has passed on the device
clock can be opened in the page: steps 9 to 18 of spec section 63 with
open, loaded on demand with a dynamic import (opener.ts), so noble and
age-encryption stay out of the first load of every page.
- The release is supplied directly by the person (spec 63, step 10):
drand's JSON answer or the bare signature, pasted after opening the
drand URL the page links to, or the release in the record of an
official fixture. The page never fetches it and reads only its round
and signature (spec 11, 13). The CSP is unchanged.
- time_and_key credentials: a .dkk (readAccessKey reads at most
12 bytes + 16 MiB + 1) or age identities, one per line.
- The plaintext of the person's own file goes to a temporary OPFS file
(tempfile.ts), committed only after step 18 (spec 56), offered for
download and deleted on request, with another capsule, on pagehide
and, if left over, on the next visit. One directory and one Web Lock
per tab keep other tabs' clean-up away from files in use. Without
OPFS, or when the browser refuses it, capsules up to 64 MiB open in
memory. An opening in progress stops when another capsule is loaded.
- opening.ts builds the page model of steps 9 to 18 as the reference
records them; fixtures show their plaintext and compare its SHA-256
with their record.
- licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts),
the license of every package in the client bundle, Vite's and
rolldown's runtime code, and the site's own license. check-build now
fails if a notice is missing, or if a page loads noble, @scure/base
or age-encryption with its first load.
- The home page no longer says that the page never asks for keys.
Checked in the browser on the production build: the time_only,
time_and_key_portable (with its .dkk) and time_and_key_recipients (with
a pasted identity) fixtures open with the SHA-256 of their records; a
tampered signature fails at step 10 and a tampered STREAM chunk at step
17, with no download and no file left; an own file opens to OPFS,
downloads without a CSP violation and is deleted with its lock; a left
over directory goes on the next visit; no request leaves the origin.
An adversarial review (four dimensions, each finding checked by a
refuter) confirmed 15 findings, all fixed here.
2611 tests; coverage 100 % of the new modules, now a threshold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
Phase 2, step 2: runtime dependencies and their guards
- age-encryption 0.3.1, @noble/curves 2.4.0 (moved from dev) and
@noble/hashes 2.4.0 become exact runtime dependencies (plan section 3,
decision 5). The lockfile gains six packages: age-encryption,
@noble/ciphers 2.4.0, @scure/base 2.4.0, @noble/post-quantum 0.5.4 and
its own @noble/curves and @noble/hashes 2.0.1. No file of src/ imports
them yet, so the site does not change.
- src/lib/dependencies.test.ts guards them. package.json declares exactly
these three, pinned. The lockfile has no tlock-js, drand-client or noble
1.x, and no noble 2.x copy other than 2.4.0 at the root and 2.0.1 under
@noble/post-quantum. No file of src/ imports tlock-js or drand-client.
Only ibe.ts, release.ts and the tests name @noble/, always subpaths of
@noble/curves or @noble/hashes that resolve to the root 2.4.0 copy.
Every check also runs on bad inputs. It replaces the "only tests import
@noble/curves" test of bls12381.contrast.test.ts.
- vite.config.ts records the modules of each client chunk in
.svelte-kit/output/client-modules.json. check-build.mjs fails if the
bundle holds tlock-js, drand-client or @babel/*, or a nested copy
other than noble under @noble/post-quantum. It also reports the
JavaScript each page loads: /inspect today loads 157 KB, 58.7 KB gzip.
- Measured with a probe build (Vite 8, minified, gzip 9): the Decrypter
is 48 KB gzip, with the Encrypter 56 KB, noble BLS12-381 plus SHA-256
28 KB, and all of them 73 KB. age-encryption imports its hybrid ML-KEM
recipients statically, so post-quantum and its nested noble copy are
about 99 KB of the Decrypter's 212 KB of rendered code.
- npm audit --omit=dev: no vulnerabilities. The full audit finds two low
ones in the tooling: cookie < 0.7.0 through @sveltejs/kit 2.70.3,
which is the latest version and affects only SvelteKit's server.
npm run verify is green: 2,384 tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
import { join } from 'node:path';
|
|
|
|
|
import { defineConfig, type Plugin } from 'vite';
|
|
|
|
|
|
|
|
|
|
// Records what the client bundle is made of, for scripts/check-build.mjs:
|
|
|
|
|
// each chunk with the chunks it imports and the modules it contains, with
|
|
|
|
|
// their rendered sizes. The record goes to .svelte-kit/output, outside the
|
|
|
|
|
// site.
|
|
|
|
|
function clientModules(): Plugin {
|
|
|
|
|
let root = process.cwd();
|
|
|
|
|
return {
|
|
|
|
|
name: 'datekeys:client-modules',
|
|
|
|
|
apply: 'build',
|
|
|
|
|
configResolved(config) {
|
|
|
|
|
root = config.root;
|
|
|
|
|
},
|
|
|
|
|
generateBundle(_, bundle) {
|
|
|
|
|
if (this.environment.name !== 'client') return;
|
|
|
|
|
const chunks: Record<string, { imports: string[]; dynamicImports: string[]; modules: Record<string, number> }> = {};
|
|
|
|
|
for (const out of Object.values(bundle)) {
|
|
|
|
|
if (out.type !== 'chunk') continue;
|
|
|
|
|
chunks[out.fileName] = {
|
|
|
|
|
imports: out.imports,
|
|
|
|
|
dynamicImports: out.dynamicImports,
|
|
|
|
|
modules: Object.fromEntries(Object.entries(out.modules).map(([id, m]) => [id.replace(/\\/g, '/'), m.renderedLength])),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
const dir = join(root, '.svelte-kit', 'output');
|
|
|
|
|
mkdirSync(dir, { recursive: true });
|
|
|
|
|
writeFileSync(join(dir, 'client-modules.json'), JSON.stringify({ chunks }, null, 1));
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
Inspector page: static SvelteKit site with /inspect (plan step 5)
A prerendered static site (adapter-static) with a landing page and
/inspect, which runs spec §63 steps 1 to 8 on a .dkc chosen with the file
picker, dropped anywhere on the page, or taken from the official fixtures
bundled at build time. It shows every step, the decoded header, the unlock
date in UTC and local time, and each extension's id, version, criticality,
length and hex, with a text view and an informative CBOR diagnostic view,
all escaped and labelled as unauthenticated before step 15. Copiar JSON
copies the exact "datekeys inspect -json" view.
No network: a hash-mode Content-Security-Policy with connect-src 'self'
is the first element of every page, and scripts/check-build.mjs verifies
it, the fixtures and the absence of external URLs after every build.
Large files are read only up to what steps 1 to 8 need.
Reviewed for design and accessibility (WCAG AA contrast, keyboard,
focus, live status, 360 px), security and correctness; 262 tests pass,
svelte-check has no warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
|
Phase 2, step 8: the open action of /inspect
After steps 1 to 8, a valid capsule whose date has passed on the device
clock can be opened in the page: steps 9 to 18 of spec section 63 with
open, loaded on demand with a dynamic import (opener.ts), so noble and
age-encryption stay out of the first load of every page.
- The release is supplied directly by the person (spec 63, step 10):
drand's JSON answer or the bare signature, pasted after opening the
drand URL the page links to, or the release in the record of an
official fixture. The page never fetches it and reads only its round
and signature (spec 11, 13). The CSP is unchanged.
- time_and_key credentials: a .dkk (readAccessKey reads at most
12 bytes + 16 MiB + 1) or age identities, one per line.
- The plaintext of the person's own file goes to a temporary OPFS file
(tempfile.ts), committed only after step 18 (spec 56), offered for
download and deleted on request, with another capsule, on pagehide
and, if left over, on the next visit. One directory and one Web Lock
per tab keep other tabs' clean-up away from files in use. Without
OPFS, or when the browser refuses it, capsules up to 64 MiB open in
memory. An opening in progress stops when another capsule is loaded.
- opening.ts builds the page model of steps 9 to 18 as the reference
records them; fixtures show their plaintext and compare its SHA-256
with their record.
- licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts),
the license of every package in the client bundle, Vite's and
rolldown's runtime code, and the site's own license. check-build now
fails if a notice is missing, or if a page loads noble, @scure/base
or age-encryption with its first load.
- The home page no longer says that the page never asks for keys.
Checked in the browser on the production build: the time_only,
time_and_key_portable (with its .dkk) and time_and_key_recipients (with
a pasted identity) fixtures open with the SHA-256 of their records; a
tampered signature fails at step 10 and a tampered STREAM chunk at step
17, with no download and no file left; an own file opens to OPFS,
downloads without a CSP violation and is deleted with its lock; a left
over directory goes on the next visit; no request leaves the origin.
An adversarial review (four dimensions, each finding checked by a
refuter) confirmed 15 findings, all fixed here.
2611 tests; coverage 100 % of the new modules, now a threshold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// The package directory of a module id under node_modules, or undefined.
|
|
|
|
|
function packageDir(id: string): string | undefined {
|
|
|
|
|
const i = id.lastIndexOf('/node_modules/');
|
|
|
|
|
if (i < 0) return undefined;
|
|
|
|
|
const parts = id.slice(i + '/node_modules/'.length).split('/');
|
|
|
|
|
const name = parts[0]!.startsWith('@') ? `${parts[0]}/${parts[1]}` : parts[0]!;
|
|
|
|
|
return `${id.slice(0, i)}/node_modules/${name}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The package of a virtual module of the bundler, such as \0vite/preload-helper.js
|
|
|
|
|
// or \0rolldown/runtime.js, or undefined for any other.
|
|
|
|
|
function virtualPackageDir(id: string, root: string): string | undefined {
|
|
|
|
|
const name = /^\0(vite|rolldown)\//.exec(id)?.[1];
|
|
|
|
|
return name === undefined ? undefined : `${root}/node_modules/${name}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The license notice kept in the leading comment of a module derived from
|
|
|
|
|
* another project, such as src/lib/dkc/ibe.ts: the block of lines indented
|
|
|
|
|
* three spaces that holds a copyright line, with the paragraph that
|
|
|
|
|
* introduces it. Undefined when the module has none.
|
|
|
|
|
*/
|
|
|
|
|
function derivedNotice(source: string): string | undefined {
|
|
|
|
|
const header: string[] = [];
|
|
|
|
|
for (const line of source.split(/\r?\n/)) {
|
|
|
|
|
if (!line.startsWith('//')) break;
|
|
|
|
|
header.push(line);
|
|
|
|
|
}
|
|
|
|
|
// The block runs from its first indented line to its last, over blank
|
|
|
|
|
// comment lines; list items of the comment are indented too, so it is
|
|
|
|
|
// found by its copyright line.
|
|
|
|
|
const copyright = header.findIndex((l) => /^\/\/ {3}.*copyright/i.test(l));
|
|
|
|
|
if (copyright < 0) return undefined;
|
|
|
|
|
let start = copyright;
|
|
|
|
|
while (start > 0 && header[start - 1]!.startsWith('// ')) start--;
|
|
|
|
|
let end = copyright;
|
|
|
|
|
while (end < header.length && (header[end]!.startsWith('// ') || header[end] === '//')) end++;
|
|
|
|
|
while (header[end - 1] === '//') end--;
|
|
|
|
|
const notice = header.slice(start, end).map((l) => (l === '//' ? '' : l.slice(5)));
|
|
|
|
|
// The paragraph before it, which names the project.
|
|
|
|
|
let to = start;
|
|
|
|
|
while (to > 0 && header[to - 1] === '//') to--;
|
|
|
|
|
let from = to;
|
|
|
|
|
while (from > 0 && header[from - 1] !== '//') from--;
|
|
|
|
|
const intro = header.slice(from, to).map((l) => l.replace(/^\/\/ ?/, ''));
|
|
|
|
|
return [...intro, '', ...notice].join('\n');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const RULE = '='.repeat(72);
|
|
|
|
|
const LINE = '-'.repeat(72);
|
|
|
|
|
|
|
|
|
|
// Writes licenses.txt at the root of the site: the notices of the third-party
|
|
|
|
|
// code in the client bundle, whose minified JavaScript keeps no comments. It
|
|
|
|
|
// holds the notice of every module of src/ derived from another project
|
|
|
|
|
// (derivedNotice), the license file of every npm package with a module in
|
|
|
|
|
// the bundle, and the license of the site itself. scripts/check-build.mjs
|
|
|
|
|
// checks that nothing is missing.
|
|
|
|
|
function thirdPartyNotices(): Plugin {
|
|
|
|
|
let root = process.cwd();
|
|
|
|
|
return {
|
|
|
|
|
name: 'datekeys:third-party-notices',
|
|
|
|
|
apply: 'build',
|
|
|
|
|
configResolved(config) {
|
|
|
|
|
root = config.root.replace(/\\/g, '/');
|
|
|
|
|
},
|
|
|
|
|
generateBundle(_, bundle) {
|
|
|
|
|
if (this.environment.name !== 'client') return;
|
|
|
|
|
const ids = new Set<string>();
|
|
|
|
|
const dirs = new Set<string>();
|
|
|
|
|
for (const out of Object.values(bundle)) {
|
|
|
|
|
if (out.type !== 'chunk') continue;
|
|
|
|
|
for (const [raw, m] of Object.entries(out.modules)) {
|
|
|
|
|
const id = raw.replace(/\\/g, '/');
|
|
|
|
|
ids.add(id);
|
|
|
|
|
// The bundler's own virtual modules (\0…) are code of its package.
|
|
|
|
|
const dir = id.startsWith('\0') ? virtualPackageDir(id, root) : packageDir(id);
|
|
|
|
|
if (dir !== undefined) dirs.add(dir);
|
|
|
|
|
else if (id.startsWith('\0') && m.renderedLength > 0) this.error(`no license known for the virtual module ${JSON.stringify(id)}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const derived: string[] = [];
|
|
|
|
|
for (const id of [...ids].sort()) {
|
|
|
|
|
if (!id.startsWith(`${root}/src/`) || !/\.(ts|js|svelte)$/.test(id)) continue;
|
|
|
|
|
const notice = derivedNotice(readFileSync(id, 'utf8'));
|
|
|
|
|
if (notice !== undefined) derived.push(`${id.slice(root.length + 1)}\n\n${notice}`);
|
|
|
|
|
}
|
|
|
|
|
const packages: string[] = [];
|
|
|
|
|
for (const dir of [...dirs].sort()) {
|
|
|
|
|
const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) as { name: string; version: string; license?: string };
|
|
|
|
|
const file = readdirSync(dir).find((f) => /^(licen[cs]e|copying)(\.md|\.txt)?$/i.test(f));
|
|
|
|
|
if (file === undefined) this.error(`${pkg.name} ${pkg.version} has no license file`);
|
|
|
|
|
const nested = dir.slice(0, dir.lastIndexOf('/node_modules/')).includes('/node_modules/');
|
|
|
|
|
const where = nested ? `, anidada bajo ${dir.slice(dir.indexOf('/node_modules/') + '/node_modules/'.length, dir.lastIndexOf('/node_modules/'))}` : '';
|
|
|
|
|
let license = readFileSync(join(dir, file), 'utf8').trim();
|
|
|
|
|
// Vite's file also carries the licenses of its Node-side dependencies,
|
|
|
|
|
// none of which reaches the client: only its own part applies.
|
|
|
|
|
const bundled = license.indexOf('\n# Licenses of bundled dependencies');
|
|
|
|
|
if (pkg.name === 'vite' && bundled > 0) license = license.slice(0, bundled).trim();
|
|
|
|
|
// The code rolldown writes into the bundle derives from Rollup and
|
|
|
|
|
// esbuild, whose notices it keeps apart.
|
|
|
|
|
const third = join(dir, 'THIRD-PARTY-LICENSE');
|
|
|
|
|
if (pkg.name === 'rolldown' && existsSync(third)) license += `\n\n${readFileSync(third, 'utf8').trim()}`;
|
|
|
|
|
packages.push(`${LINE}\n${pkg.name} ${pkg.version} (${pkg.license ?? 'sin campo license'}${where})\n${LINE}\n\n${license}`);
|
|
|
|
|
}
|
|
|
|
|
const text = [
|
|
|
|
|
'Avisos de licencia de este sitio',
|
|
|
|
|
'',
|
|
|
|
|
'Este sitio es datekeys-ts, con licencia Apache-2.0 (al final de este fichero). Su',
|
Phase 2, step 8: the open action of /inspect
After steps 1 to 8, a valid capsule whose date has passed on the device
clock can be opened in the page: steps 9 to 18 of spec section 63 with
open, loaded on demand with a dynamic import (opener.ts), so noble and
age-encryption stay out of the first load of every page.
- The release is supplied directly by the person (spec 63, step 10):
drand's JSON answer or the bare signature, pasted after opening the
drand URL the page links to, or the release in the record of an
official fixture. The page never fetches it and reads only its round
and signature (spec 11, 13). The CSP is unchanged.
- time_and_key credentials: a .dkk (readAccessKey reads at most
12 bytes + 16 MiB + 1) or age identities, one per line.
- The plaintext of the person's own file goes to a temporary OPFS file
(tempfile.ts), committed only after step 18 (spec 56), offered for
download and deleted on request, with another capsule, on pagehide
and, if left over, on the next visit. One directory and one Web Lock
per tab keep other tabs' clean-up away from files in use. Without
OPFS, or when the browser refuses it, capsules up to 64 MiB open in
memory. An opening in progress stops when another capsule is loaded.
- opening.ts builds the page model of steps 9 to 18 as the reference
records them; fixtures show their plaintext and compare its SHA-256
with their record.
- licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts),
the license of every package in the client bundle, Vite's and
rolldown's runtime code, and the site's own license. check-build now
fails if a notice is missing, or if a page loads noble, @scure/base
or age-encryption with its first load.
- The home page no longer says that the page never asks for keys.
Checked in the browser on the production build: the time_only,
time_and_key_portable (with its .dkk) and time_and_key_recipients (with
a pasted identity) fixtures open with the SHA-256 of their records; a
tampered signature fails at step 10 and a tampered STREAM chunk at step
17, with no download and no file left; an own file opens to OPFS,
downloads without a CSP violation and is deleted with its lock; a left
over directory goes on the next visit; no request leaves the origin.
An adversarial review (four dimensions, each finding checked by a
refuter) confirmed 15 findings, all fixed here.
2611 tests; coverage 100 % of the new modules, now a threshold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
'JavaScript lleva, minimizado, código de terceros: módulos derivados de otros',
|
|
|
|
|
'proyectos y paquetes npm, cada uno con su aviso de copyright y su licencia.',
|
|
|
|
|
'',
|
|
|
|
|
RULE,
|
|
|
|
|
'Módulos de datekeys-ts derivados de otros proyectos',
|
Phase 2, step 8: the open action of /inspect
After steps 1 to 8, a valid capsule whose date has passed on the device
clock can be opened in the page: steps 9 to 18 of spec section 63 with
open, loaded on demand with a dynamic import (opener.ts), so noble and
age-encryption stay out of the first load of every page.
- The release is supplied directly by the person (spec 63, step 10):
drand's JSON answer or the bare signature, pasted after opening the
drand URL the page links to, or the release in the record of an
official fixture. The page never fetches it and reads only its round
and signature (spec 11, 13). The CSP is unchanged.
- time_and_key credentials: a .dkk (readAccessKey reads at most
12 bytes + 16 MiB + 1) or age identities, one per line.
- The plaintext of the person's own file goes to a temporary OPFS file
(tempfile.ts), committed only after step 18 (spec 56), offered for
download and deleted on request, with another capsule, on pagehide
and, if left over, on the next visit. One directory and one Web Lock
per tab keep other tabs' clean-up away from files in use. Without
OPFS, or when the browser refuses it, capsules up to 64 MiB open in
memory. An opening in progress stops when another capsule is loaded.
- opening.ts builds the page model of steps 9 to 18 as the reference
records them; fixtures show their plaintext and compare its SHA-256
with their record.
- licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts),
the license of every package in the client bundle, Vite's and
rolldown's runtime code, and the site's own license. check-build now
fails if a notice is missing, or if a page loads noble, @scure/base
or age-encryption with its first load.
- The home page no longer says that the page never asks for keys.
Checked in the browser on the production build: the time_only,
time_and_key_portable (with its .dkk) and time_and_key_recipients (with
a pasted identity) fixtures open with the SHA-256 of their records; a
tampered signature fails at step 10 and a tampered STREAM chunk at step
17, with no download and no file left; an own file opens to OPFS,
downloads without a CSP violation and is deleted with its lock; a left
over directory goes on the next visit; no request leaves the origin.
An adversarial review (four dimensions, each finding checked by a
refuter) confirmed 15 findings, all fixed here.
2611 tests; coverage 100 % of the new modules, now a threshold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
RULE,
|
|
|
|
|
'',
|
|
|
|
|
derived.join(`\n\n${LINE}\n\n`),
|
|
|
|
|
'',
|
|
|
|
|
RULE,
|
|
|
|
|
'Paquetes npm',
|
|
|
|
|
RULE,
|
|
|
|
|
'',
|
|
|
|
|
packages.join('\n\n'),
|
|
|
|
|
'',
|
|
|
|
|
RULE,
|
|
|
|
|
'datekeys-ts (Apache-2.0)',
|
Phase 2, step 8: the open action of /inspect
After steps 1 to 8, a valid capsule whose date has passed on the device
clock can be opened in the page: steps 9 to 18 of spec section 63 with
open, loaded on demand with a dynamic import (opener.ts), so noble and
age-encryption stay out of the first load of every page.
- The release is supplied directly by the person (spec 63, step 10):
drand's JSON answer or the bare signature, pasted after opening the
drand URL the page links to, or the release in the record of an
official fixture. The page never fetches it and reads only its round
and signature (spec 11, 13). The CSP is unchanged.
- time_and_key credentials: a .dkk (readAccessKey reads at most
12 bytes + 16 MiB + 1) or age identities, one per line.
- The plaintext of the person's own file goes to a temporary OPFS file
(tempfile.ts), committed only after step 18 (spec 56), offered for
download and deleted on request, with another capsule, on pagehide
and, if left over, on the next visit. One directory and one Web Lock
per tab keep other tabs' clean-up away from files in use. Without
OPFS, or when the browser refuses it, capsules up to 64 MiB open in
memory. An opening in progress stops when another capsule is loaded.
- opening.ts builds the page model of steps 9 to 18 as the reference
records them; fixtures show their plaintext and compare its SHA-256
with their record.
- licenses.txt: the notices of tlock-js (ibe.ts) and age (bech32.ts),
the license of every package in the client bundle, Vite's and
rolldown's runtime code, and the site's own license. check-build now
fails if a notice is missing, or if a page loads noble, @scure/base
or age-encryption with its first load.
- The home page no longer says that the page never asks for keys.
Checked in the browser on the production build: the time_only,
time_and_key_portable (with its .dkk) and time_and_key_recipients (with
a pasted identity) fixtures open with the SHA-256 of their records; a
tampered signature fails at step 10 and a tampered STREAM chunk at step
17, with no download and no file left; an own file opens to OPFS,
downloads without a CSP violation and is deleted with its lock; a left
over directory goes on the next visit; no request leaves the origin.
An adversarial review (four dimensions, each finding checked by a
refuter) confirmed 15 findings, all fixed here.
2611 tests; coverage 100 % of the new modules, now a threshold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
RULE,
|
|
|
|
|
'',
|
|
|
|
|
readFileSync(join(root, 'LICENSE'), 'utf8').trim(),
|
|
|
|
|
'',
|
|
|
|
|
].join('\n');
|
|
|
|
|
this.emitFile({ type: 'asset', fileName: 'licenses.txt', source: text });
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
Put the content first on opening, and name its download by its content
When a capsule opens, /inspect now shows its content right under the
verdict, before steps 9 to 18. A capsule does not keep the name of the
file it seals (spec §6, §55.2), and the capsula-<date>.dkc of /create
has no extension of its own, so the download was nameless for the
system: contentExtension now gives it .txt for a text, or the
extension of a common type of file by its first bytes (.pdf, .png,
.jpg, .zip…).
vite preview served the pages without Cache-Control, and a tab
reloaded after a build could keep the old page, whose chunks are gone;
a small plugin, before SvelteKit's, has the pages revalidated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// `vite preview` sends the pages without Cache-Control, so a browser may show
|
|
|
|
|
// an old page after a new build, whose chunks are gone from build/: the local
|
|
|
|
|
// preview has the pages revalidated on every load. The hashed files of
|
|
|
|
|
// _app/immutable keep the year-long cache that SvelteKit gives them. It goes
|
|
|
|
|
// before the SvelteKit plugin, which serves the prerendered pages itself and
|
|
|
|
|
// would answer first. A real host should serve the pages the same way.
|
|
|
|
|
function revalidatePages(): Plugin {
|
|
|
|
|
return {
|
|
|
|
|
name: 'datekeys:revalidate-pages',
|
|
|
|
|
configurePreviewServer(server) {
|
|
|
|
|
server.middlewares.use((req, res, next) => {
|
|
|
|
|
if (!req.url?.startsWith('/_app/immutable/')) res.setHeader('Cache-Control', 'no-cache');
|
|
|
|
|
next();
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
Inspector page: static SvelteKit site with /inspect (plan step 5)
A prerendered static site (adapter-static) with a landing page and
/inspect, which runs spec §63 steps 1 to 8 on a .dkc chosen with the file
picker, dropped anywhere on the page, or taken from the official fixtures
bundled at build time. It shows every step, the decoded header, the unlock
date in UTC and local time, and each extension's id, version, criticality,
length and hex, with a text view and an informative CBOR diagnostic view,
all escaped and labelled as unauthenticated before step 15. Copiar JSON
copies the exact "datekeys inspect -json" view.
No network: a hash-mode Content-Security-Policy with connect-src 'self'
is the first element of every page, and scripts/check-build.mjs verifies
it, the fixtures and the absence of external URLs after every build.
Large files are read only up to what steps 1 to 8 need.
Reviewed for design and accessibility (WCAG AA contrast, keyboard,
focus, live status, 360 px), security and correctness; 262 tests pass,
svelte-check has no warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
export default defineConfig({
|
Put the content first on opening, and name its download by its content
When a capsule opens, /inspect now shows its content right under the
verdict, before steps 9 to 18. A capsule does not keep the name of the
file it seals (spec §6, §55.2), and the capsula-<date>.dkc of /create
has no extension of its own, so the download was nameless for the
system: contentExtension now gives it .txt for a text, or the
extension of a common type of file by its first bytes (.pdf, .png,
.jpg, .zip…).
vite preview served the pages without Cache-Control, and a tab
reloaded after a build could keep the old page, whose chunks are gone;
a small plugin, before SvelteKit's, has the pages revalidated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
plugins: [revalidatePages(), sveltekit(), clientModules(), thirdPartyNotices()],
|
Inspector page: static SvelteKit site with /inspect (plan step 5)
A prerendered static site (adapter-static) with a landing page and
/inspect, which runs spec §63 steps 1 to 8 on a .dkc chosen with the file
picker, dropped anywhere on the page, or taken from the official fixtures
bundled at build time. It shows every step, the decoded header, the unlock
date in UTC and local time, and each extension's id, version, criticality,
length and hex, with a text view and an informative CBOR diagnostic view,
all escaped and labelled as unauthenticated before step 15. Copiar JSON
copies the exact "datekeys inspect -json" view.
No network: a hash-mode Content-Security-Policy with connect-src 'self'
is the first element of every page, and scripts/check-build.mjs verifies
it, the fixtures and the absence of external URLs after every build.
Large files are read only up to what steps 1 to 8 need.
Reviewed for design and accessibility (WCAG AA contrast, keyboard,
focus, live status, 360 px), security and correctness; 262 tests pass,
svelte-check has no warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
build: {
|
|
|
|
|
// Never inline an asset as a data: URL. The CSP allows only the page's
|
|
|
|
|
// own origin, so the official fixtures (src/lib/inspector/fixtures.ts)
|
|
|
|
|
// must be separate same-origin files, whatever their size.
|
|
|
|
|
assetsInlineLimit: 0,
|
|
|
|
|
},
|
|
|
|
|
optimizeDeps: {
|
|
|
|
|
// Every dependency the pages load on demand, pre-bundled when the dev
|
|
|
|
|
// server starts. Found only on the first import, as the opening of a
|
|
|
|
|
// capsule does, the dev server bundles it then and reloads the page, and
|
|
|
|
|
// the import in flight fails: "Failed to fetch dynamically imported
|
|
|
|
|
// module". src/lib/dependencies.test.ts checks that the list is complete.
|
|
|
|
|
include: [
|
|
|
|
|
'@noble/ciphers/chacha.js',
|
|
|
|
|
'@noble/curves/bls12-381.js',
|
|
|
|
|
'@noble/curves/ed25519.js',
|
|
|
|
|
'@noble/curves/nist.js',
|
|
|
|
|
'@noble/curves/utils.js',
|
|
|
|
|
'@noble/hashes/hkdf.js',
|
|
|
|
|
'@noble/hashes/legacy.js',
|
|
|
|
|
'@noble/hashes/sha2.js',
|
|
|
|
|
'age-encryption',
|
|
|
|
|
],
|
|
|
|
|
},
|
Inspector page: static SvelteKit site with /inspect (plan step 5)
A prerendered static site (adapter-static) with a landing page and
/inspect, which runs spec §63 steps 1 to 8 on a .dkc chosen with the file
picker, dropped anywhere on the page, or taken from the official fixtures
bundled at build time. It shows every step, the decoded header, the unlock
date in UTC and local time, and each extension's id, version, criticality,
length and hex, with a text view and an informative CBOR diagnostic view,
all escaped and labelled as unauthenticated before step 15. Copiar JSON
copies the exact "datekeys inspect -json" view.
No network: a hash-mode Content-Security-Policy with connect-src 'self'
is the first element of every page, and scripts/check-build.mjs verifies
it, the fixtures and the absence of external URLs after every build.
Large files are read only up to what steps 1 to 8 need.
Reviewed for design and accessibility (WCAG AA contrast, keyboard,
focus, live status, 360 px), security and correctness; 262 tests pass,
svelte-check has no warnings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
server: {
|
|
|
|
|
fs: {
|
|
|
|
|
// The dev server serves the official fixtures straight from testdata/,
|
|
|
|
|
// the single source of truth; the build copies them as hashed assets.
|
|
|
|
|
allow: ['testdata/fixtures'],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
});
|