Automerge backed Markdown editor for the Atmosphere
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288import { after, test } from 'node:test'import assert from 'node:assert/strict'import { createServer } from 'node:https'import { createHash, generateKeyPairSync, randomUUID, sign } from 'node:crypto'import { mkdtemp, mkdir, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises'import { tmpdir } from 'node:os'import { basename, dirname, join, relative, resolve } from 'node:path'import { execFileSync } from 'node:child_process'import { createRequire } from 'node:module'import { pathToFileURL } from 'node:url'
// This is a provider capability probe, not a shipped assertion service. A green// run means the observations and ordinary-auth controls are valid; inspect// bindingVerdict. A denial alone does not establish why a provider rejected it.const root = process.env.AUTOMERGE_ATPROTO_ROOTif (process.env.RUN_CLIENT_ASSERTION_PROBE !== '1' || !root) throw new Error('Set RUN_CLIENT_ASSERTION_PROBE=1 and AUTOMERGE_ATPROTO_ROOT to run the real-service probe')const scope = 'atproto repo:* blob:application/vnd.sealight.automerge.amch' // Disposable harness accounts only.const proposalCommit = 'b46b8414da725eb0867ac605a76f6603e4ed623e'const runId = randomUUID(), startedAt = new Date().toISOString(), profiles = []const output = resolve(process.env.CLIENT_ASSERTION_EVIDENCE_DIR ?? '.invariant-artifacts/client-assertion-binding')const probeSha256 = createHash('sha256').update(await readFile(new URL(import.meta.url))).digest('hex')function gitIdentity(directory) { return { commit: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: directory, encoding: 'utf8' }).trim(), dirty: execFileSync('git', ['status', '--porcelain'], { cwd: directory, encoding: 'utf8' }).trim().length > 0, }}const harnessSha256 = createHash('sha256').update(await readFile(join(root, 'test/happyview/happyview-harness.mjs'))).digest('hex')const happyviewSource = resolve(process.env.HAPPYVIEW_SOURCE ?? join(root, 'tmp/happyview'))const commonProvenance = { proposalCommit, probeSha256, harnessSha256, companion: gitIdentity(root), reams: gitIdentity(process.cwd()), companionLockSha256: createHash('sha256').update(await readFile(join(root, 'package-lock.json'))).digest('hex'), happyview: { ...gitIdentity(happyviewSource), trackedDiffSha256: createHash('sha256').update(execFileSync('git', ['diff', 'HEAD'], { cwd: happyviewSource })).digest('hex') }, nodeVersion: process.version, platform: process.platform, arch: process.arch,}after(async () => { // Only advertise a completed pair. Each run's files have a unique directory, // so a later interrupted run cannot replace half of a previous result. if (profiles.length !== 2) return const manifest = { runId, startedAt, completedAt: new Date().toISOString(), provenance: commonProvenance, profiles } const temporary = join(output, `${runId}.tmp`) await writeFile(temporary, JSON.stringify(manifest, null, 2)) await rename(temporary, join(output, 'latest.json'))})const b64 = value => Buffer.from(value).toString('base64url')const keyPair = () => generateKeyPairSync('ec', { namedCurve: 'P-256' })const publicJwk = pair => pair.publicKey.export({ format: 'jwk' })const thumb = pair => { const { crv, kty, x, y } = publicJwk(pair) return b64(createHash('sha256').update(JSON.stringify({ crv, kty, x, y })).digest())}function jwt(header, payload, privateKey) { const input = `${b64(JSON.stringify(header))}.${b64(JSON.stringify(payload))}` return `${input}.${sign('sha256', Buffer.from(input), { key: privateKey, dsaEncoding: 'ieee-p1363' }).toString('base64url')}`}
for (const variant of ['nix-fixture', 'installed-pds']) { test(`${variant}: real PAR/code/refresh assertion binding and ordinary-auth controls`, { timeout: 180_000 }, async t => { const { startHappyViewStack, createPdsAccount } = await import(pathToFileURL(join(root, 'test/happyview/happyview-harness.mjs')).href) const dir = await mkdtemp(join(tmpdir(), 'reams-cab-binding-')) const cert = join(dir, 'tls.crt'), tlsKey = join(dir, 'tls.key') execFileSync('openssl', ['req', '-x509', '-newkey', 'rsa:2048', '-nodes', '-keyout', tlsKey, '-out', cert, '-days', '1', '-subj', '/CN=lvh.me', '-addext', 'subjectAltName=DNS:lvh.me'], { stdio: 'ignore' }) const savedEnv = Object.fromEntries(['NODE_EXTRA_CA_CERTS', 'PDS_DISABLE_SSRF_PROTECTION', 'PDS_LEXICON_AUTHORITY_DID'].map(key => [key, process.env[key]])) process.env.NODE_EXTRA_CA_CERTS = cert process.env.PDS_DISABLE_SSRF_PROTECTION = 'true' delete process.env.PDS_LEXICON_AUTHORITY_DID let stack, server, verifiedEvidence try { let pdsBinary, pdsPackage if (variant === 'installed-pds') { pdsPackage = await realpath(join(root, 'node_modules/@atproto/pds')) const runner = join(dir, 'pds.mjs') await writeFile(runner, ` import { PDS, readEnv, envToCfg, envToSecrets } from ${JSON.stringify(pathToFileURL(join(pdsPackage, 'dist/index.js')).href)}; const env = readEnv(); env.version = 'client-assertion-probe'; const pds = await PDS.create(envToCfg(env), envToSecrets(env)); await pds.start(); process.on('SIGTERM', async () => { await pds.destroy(); process.exit(0); }); `) pdsBinary = join(dir, 'pds') await writeFile(pdsBinary, `#!/bin/sh\nexec ${JSON.stringify(process.execPath)} ${JSON.stringify(runner)}\n`, { mode: 0o755 }) } else { pdsBinary = execFileSync('sh', ['-c', 'command -v pds'], { encoding: 'utf8' }).trim() pdsPackage = await realpath(join(dirname(dirname(pdsBinary)), 'lib/pds/node_modules/@atproto/pds')) } const pds = JSON.parse(await readFile(join(pdsPackage, 'package.json'), 'utf8')) const require = createRequire(pathToFileURL(join(pdsPackage, 'package.json'))) let providerEntry try { providerEntry = require.resolve('@atproto/oauth-provider/provider') } catch (error) { if (error.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw error providerEntry = require.resolve('@atproto/oauth-provider') } const providerPath = dirname(dirname(providerEntry)) const provider = JSON.parse(await readFile(join(providerPath, 'package.json'), 'utf8')) const consentAccountField = { '0.18.1': 'sub', '0.20.2': 'did' }[provider.version] assert.ok(consentAccountField, `Add an explicit consent API profile for provider ${provider.version}`) const lock = variant === 'installed-pds' ? JSON.parse(await readFile(join(root, 'package-lock.json'), 'utf8')) : null const dependencyIntegrity = lock ? { pds: lock.packages[relative(root, pdsPackage)]?.integrity, oauthProvider: lock.packages[relative(root, providerPath)]?.integrity, } : { nixRunner: pdsBinary } if (lock) assert.ok(dependencyIntegrity.pds && dependencyIntegrity.oauthProvider, 'Record the installed provider integrity from its lockfile') const clientKey = keyPair(), otherSigningKey = keyPair() let clientId, redirectUri server = createServer({ cert: await readFile(cert), key: await readFile(tlsKey) }, (req, res) => { if (req.url !== '/oauth-client-metadata.json') { res.writeHead(404).end(); return } res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify({ client_id: clientId, client_name: 'Disposable client-assertion probe', client_uri: new URL(clientId).origin, redirect_uris: [redirectUri], response_types: ['code'], grant_types: ['authorization_code', 'refresh_token'], scope, token_endpoint_auth_method: 'private_key_jwt', token_endpoint_auth_signing_alg: 'ES256', dpop_bound_access_tokens: true, jwks: { keys: [{ ...publicJwk(clientKey), kid: 'probe', alg: 'ES256', use: 'sig' }] }, })) }) await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)) clientId = `https://lvh.me:${server.address().port}/oauth-client-metadata.json` redirectUri = `https://lvh.me:${server.address().port}/callback` stack = await startHappyViewStack({ workspace: root, pdsBinary, plcFallbackUrl: 'https://plc.directory', pdsPlcHostname: 'lvh.me' }) const account = await createPdsAccount(stack, 'cabprobe') const metadata = await (await fetch(`${stack.pdsUrl}/.well-known/oauth-authorization-server`)).json() const rows = []
async function request(stage, device, fields, { binding = thumb(device), signer = clientKey } = {}) { const endpoint = stage === 'par' ? metadata.pushed_authorization_request_endpoint : metadata.token_endpoint const send = nonce => { const now = Math.floor(Date.now() / 1000) const assertion = jwt({ typ: 'JWT', alg: 'ES256', kid: 'probe' }, { iss: clientId, sub: clientId, aud: metadata.issuer, iat: now, exp: now + 60, jti: randomUUID(), cnf: { jkt: binding }, }, signer.privateKey) const proof = jwt({ typ: 'dpop+jwt', alg: 'ES256', jwk: publicJwk(device) }, { htm: 'POST', htu: new URL(endpoint).origin + new URL(endpoint).pathname, iat: now, jti: randomUUID(), ...(nonce ? { nonce } : {}), }, device.privateKey) return fetch(endpoint, { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(10_000), headers: { 'content-type': 'application/x-www-form-urlencoded', dpop: proof }, body: new URLSearchParams({ ...fields, client_id: clientId, client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer', client_assertion: assertion }) }) } let response = await send() if (!response.ok && response.headers.get('dpop-nonce')) { const nonce = response.headers.get('dpop-nonce') await response.body?.cancel() response = await send(nonce) } return { status: response.status, payload: await response.json() } } function record(stage, kind, result) { const accepted = stage === 'par' ? typeof result.payload.request_uri === 'string' : typeof result.payload.access_token === 'string' const row = { stage, kind, status: result.status, accepted, error: result.payload.error ?? null } rows.push(row) return row } function requireSuccess(stage, result) { assert.ok(result.status === (stage === 'par' ? 201 : 200), `${stage} control failed: HTTP ${result.status}, error ${result.payload.error ?? 'none'}`) if (stage !== 'par') { assert.equal(result.payload.sub, account.did) assert.equal(result.payload.token_type, 'DPoP') assert.ok(typeof result.payload.refresh_token === 'string') } } function parFields(device) { const verifier = b64(createHash('sha256').update(randomUUID()).digest()) return { verifier, fields: { redirect_uri: redirectUri, response_type: 'code', scope, state: randomUUID(), login_hint: account.did, code_challenge: b64(createHash('sha256').update(verifier).digest()), code_challenge_method: 'S256', dpop_jkt: thumb(device) } } } async function completeAuthorization(authorizationUrl) { const url = new URL(authorizationUrl) const cookieHeader = response => response.headers.getSetCookie().map(value => value.split(';')[0]).join('; ') const page = await fetch(url, { redirect: 'manual', signal: AbortSignal.timeout(10_000), headers: { 'sec-fetch-site': 'same-origin', 'sec-fetch-mode': 'navigate', 'sec-fetch-dest': 'document', origin: url.origin, accept: 'text/html', } }) await page.text() let cookie = cookieHeader(page) const headers = () => ({ 'content-type': 'application/json', cookie, 'x-csrf-token': cookie.match(/(?:^|; )csrf-token=([^;]+)/)?.[1], 'sec-fetch-site': 'same-origin', 'sec-fetch-mode': 'same-origin', origin: url.origin, referer: url.href }) const requestUri = encodeURIComponent(url.searchParams.get('request_uri')) const signIn = await fetch(`${url.origin}/@atproto/oauth-provider/~api/sign-in?request_uri=${requestUri}`, { method: 'POST', mode: 'same-origin', signal: AbortSignal.timeout(10_000), headers: headers(), body: JSON.stringify({ username: account.handle, password: account.password, locale: 'en-US', remember: true }), }) assert.equal(signIn.status, 200, 'Fixture sign-in must succeed') await signIn.json() cookie = cookieHeader(signIn) || cookie // Provider 0.20 changed its real consent API from sub to did. This is // a driver adaptation, not a fabricated authorization response. const consent = await fetch(`${url.origin}/@atproto/oauth-provider/~api/consent?request_uri=${requestUri}`, { method: 'POST', mode: 'same-origin', signal: AbortSignal.timeout(10_000), headers: headers(), body: JSON.stringify({ [consentAccountField]: account.did, scope }), }) assert.equal(consent.status, 200, 'Fixture consent must succeed') const result = await consent.json() const returned = new URL(result.url, url.origin) if (returned.origin === new URL(redirectUri).origin) { assert.equal(returned.pathname, new URL(redirectUri).pathname) return returned.href } assert.equal(returned.origin, url.origin) assert.equal(returned.searchParams.get('redirect_uri'), redirectUri) const callback = new URL(redirectUri) const params = new URLSearchParams() for (const field of ['code', 'state', 'iss', 'error']) { const value = returned.searchParams.get(field) if (value !== null) params.set(field, value) } callback.hash = params.toString() return callback.href } async function login(device) { const { verifier, fields } = parFields(device) const par = await request('par', device, fields) requireSuccess('par', par) const url = new URL(metadata.authorization_endpoint) url.searchParams.set('client_id', clientId) url.searchParams.set('request_uri', par.payload.request_uri) const callback = new URL(await completeAuthorization(url.href)) const params = new URLSearchParams(callback.hash.slice(1)) for (const [key, value] of callback.searchParams) params.append(key, value) assert.equal(params.get('state'), fields.state) assert.equal(params.get('iss'), metadata.issuer) assert.ok(params.get('code')) return { grant_type: 'authorization_code', code: params.get('code'), redirect_uri: redirectUri, code_verifier: verifier } } async function token(device) { const result = await request('code', device, await login(device)) requireSuccess('code', result) return result.payload }
// Each negative changes only the assertion's confirmation binding. Token // and proof remain bound to the genuinely authorized original device. for (const stage of ['par', 'code', 'refresh']) { for (const kind of ['matched', 'mismatched-assertion']) { const device = keyPair() let fields, previousToken if (stage === 'par') fields = parFields(device).fields else if (stage === 'code') fields = await login(device) else { previousToken = await token(device); fields = { grant_type: 'refresh_token', refresh_token: previousToken.refresh_token } } const result = await request(stage, device, fields, { binding: kind === 'matched' ? thumb(device) : thumb(keyPair()) }) const row = record(stage, kind, result) if (kind === 'matched') { requireSuccess(stage, result) assert.equal(row.accepted, true) if (previousToken) assert.ok(previousToken.refresh_token !== result.payload.refresh_token, 'Refresh must rotate the token') } else if (row.accepted) requireSuccess(stage, result) else assert.ok([400, 401].includes(result.status), `Inconclusive binding denial: ${result.status}`) } } const device = keyPair() const badSignature = await request('par', device, parFields(device).fields, { signer: otherSigningKey }) record('par', 'invalid-client-signature', badSignature) assert.ok([400, 401].includes(badSignature.status), 'The provider must reject an invalid application signature') assert.equal(rows.at(-1).accepted, false) assert.equal(badSignature.payload.error, 'invalid_client') for (const stage of ['code', 'refresh']) { const originalDevice = keyPair(), otherDevice = keyPair() const fields = stage === 'code' ? await login(originalDevice) : { grant_type: 'refresh_token', refresh_token: (await token(originalDevice)).refresh_token } const result = await request(stage, otherDevice, fields) record(stage, 'wrong-session-dpop', result) assert.ok([400, 401].includes(result.status), 'The provider must reject an independently valid proof from the wrong session key') assert.equal(rows.at(-1).accepted, false) assert.equal(result.payload.error, 'invalid_grant') }
const evidence = { runId, startedAt, provenance: commonProvenance, variant, provider: { pdsPackage: pds.version, oauthProviderPackage: provider.version, runner: variant === 'nix-fixture' ? basename(dirname(dirname(pdsBinary))) : 'installed SDK launcher' }, dependencyIntegrity, consentAccountField, scope, rows, allTestedMismatchesRejected: rows.filter(row => row.kind === 'mismatched-assertion').every(row => !row.accepted), bindingVerdict: rows.some(row => row.kind === 'mismatched-assertion' && row.accepted) ? 'not-enforced' : 'inconclusive-denial', boundary: 'Disposable real local PDS/PLC/HappyView; not live-provider or browser/service deployment acceptance', } const runOutput = join(output, runId) await mkdir(runOutput, { recursive: true }) await writeFile(join(runOutput, `${variant}.json`), JSON.stringify(evidence, null, 2)) verifiedEvidence = evidence t.diagnostic(JSON.stringify(evidence)) } finally { try { await stack?.stop() } finally { if (server) await new Promise(resolve => server.close(resolve)) for (const [key, value] of Object.entries(savedEnv)) { if (value === undefined) delete process.env[key]; else process.env[key] = value } await rm(dir, { recursive: true, force: true }) } } profiles.push(verifiedEvidence) // Publish only after successful teardown too. })}