From bb9decb4843ed6494b3874f2c43e873fbcdf0e21 Mon Sep 17 00:00:00 2001 From: type-two Date: Sun, 2 Aug 2026 23:00:36 +1000 Subject: [PATCH] The philes: 200 of 200, written and placed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The writing job, done. 89 new philes takes the store to 200, and every one of them is now reachable in play — which turned out to be the harder half. Where they live follows from the voice rule. Each phile is written by somebody, in the year it happened, who did not know they were in a game — so the board is 1991 and the board cannot carry a file about WannaCry. the board seven read-online areas: g-philes, the law, the scene, v i r u s, the iron, telephony, unix. Reading online is free and downloading costs MTU, which is how a board actually worked, and is the only way ~60 files fit inside an economy built to make you choose. the climb everything after 1992, because the climb is the only level that travels forwards in time. Thirty-six years bolted to the shaft wall going past at speed. You get the file for having been there whether or not you turned your head. the shell man(1) and cat(1) on kruuna, for the unix and network ones. ctx.phile(id, ..., quiet) collects without a toast — thirty-six notifications in fifty seconds is a wall, not a level. The count lands once at the top of the climb. Two real bugs found on the way: handshake exit() guarded .tone instead of .b, and there is a window between S.i++ and enter() where a boss is current but its state does not exist. Leave via the traceroute map during a title card and unmount throws. Async continuations now bail on a dead level too. the climb the camera does not travel, the shaft does. The new signs were nailed to the origin and never came into view. They also needed dark ink for the one zone whose walls are white. tools/philecheck.mjs: duplicate ids in an object literal overwrite silently, and that cost an hour. It also reports anything written but not placed. 200/200 written, 200/200 findable. Co-Authored-By: Claude Opus 5 --- README.md | 25 +- STAGES.md | 2 +- card.html | 5 +- src/levels/board.js | 140 +++- src/levels/climb.js | 84 ++- src/levels/coldboot.js | 1 + src/levels/handshake.js | 10 +- src/levels/shell.js | 27 + src/levels/stack.js | 2 +- src/main.js | 7 +- src/philes.js | 1583 +++++++++++++++++++++++++++++++++++++++ tools/philecheck.mjs | 29 + 12 files changed, 1897 insertions(+), 18 deletions(-) create mode 100644 tools/philecheck.mjs diff --git a/README.md b/README.md index f73cf56..fc94673 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,28 @@ is the ANSam beat from hop 47: `SPACE` on the phase reversal, 450 ms, four clean Terminal levels opt out with `noRetrain: true`, because stealing `SPACE` from someone mid-sentence isn't a mechanic, it's a bug with a story attached. +## The philes + +All 200 are written, in [`src/philes.js`](src/philes.js), and all 200 are findable. +Read them with `P`. + +The voice rule is the whole thing: **each one is written by somebody, in the year it +happened, who did not know they were in a game.** Nobody explains anything for the +player's benefit and nothing is written in hindsight. + +Where they live follows from that. The board is 1991, so the board cannot carry a +file about WannaCry — it carries the seven read-online areas (g-philes, the law, the +scene, virus, the iron, telephony, unix), and reading is free while *downloading* +costs MTU, which is how a real board worked. Everything after 1992 is on **the +climb**, because the climb is the only level that travels forwards in time: thirty-six +years bolted to the shaft wall, going past at speed. You get the file for having been +there whether or not you turned your head, and they arrive quietly — thirty-six +notifications in fifty seconds is a wall, not a level, so `ctx.phile(id, …, quiet)` +collects them and the count lands once at the top. + +`node tools/philecheck.mjs` — duplicate ids in an object literal overwrite silently, +which cost an hour once. It also reports anything written but not placed in a level. + ## Audio [`src/audio.js`](src/audio.js) — every tone is oscillators, nothing is sampled, and @@ -148,5 +170,4 @@ to zero in a hidden tab and that makes the game look broken when it's only aslee - **Multiplayer (`CHECKSUM`)** — the Journey-style anonymous pass-by. Needs a server; add when there's something to connect to. - **Playing both fragments** — fragmenting costs TTL, it doesn't split you into two packets you alternate between. Add when a level has a corridor narrow enough to need it. -- **The 200 philes** — 74 written in [`src/philes.js`](src/philes.js), all readable with `P`. Adding the next 126 is a writing job, not a code job: put the text in that file, call `ctx.phile('id')` wherever it should be found. -- Levels run 3–8 minutes against a design doc that asks for 5–20. The genres, mechanics and set pieces are all in — the *duration* is still short. See [STAGES.md](STAGES.md) for a per-hop breakdown of what's in and what's next. +- Levels run 5–12 minutes against a design doc that asks for 5–20. The genres, mechanics and set pieces are all in — the *duration* is still short. See [STAGES.md](STAGES.md) for a per-hop breakdown of what's in and what's next. diff --git a/STAGES.md b/STAGES.md index 70963e3..588d57b 100644 --- a/STAGES.md +++ b/STAGES.md @@ -358,7 +358,7 @@ TIME_WAIT with FIN saying *told you*, then it loops to hop 1. | | | |---|---| -| **The philes** | 75 of 200 written. All in `src/philes.js`, all readable with `P`. Pure writing. | +| ~~**The philes**~~ | **DONE** — 200 of 200 written and all 200 placed. The board carries everything up to 1991 in seven read-online areas; the climb carries everything after, as signs on the wall. `node tools/philecheck.mjs` verifies. | | ~~**Audio**~~ | **DONE** — `src/score.js`. Every bed derived from a real number: 1 kHz reference tone, 50 Hz mains, 0x90 as a pitch, 45.45 baud, the 1200 Hz carrier, 20 Hz ringing current, the six MF tones, 2600 folded six times. The handshake is scored silent on purpose. | | ~~**The retrain**~~ | **DONE** — `src/retrain.js`. Fires once per run after hop 51, drops you a notch, and the recovery is the 450 ms ANSam beat from hop 47. Terminal levels opt out. | | **Difficulty** | Nothing scales. TTL is the only pressure and it's linear. | diff --git a/card.html b/card.html index 599749c..d91a7ba 100644 --- a/card.html +++ b/card.html @@ -68,6 +68,7 @@ the older it gets the more it trusts you.

AudioRequired. Several mechanics are inaudible without it, and one boss is unbeatable if you cannot hear the beat. Runs onAny browser with WebGL2 and Web Audio. No install, no account, no server. Progress is in localStorage. DataNone leaves your machine. There is no analytics, no telemetry, no network call after the page loads. + Reading200 philes — text collectibles, all of them findable, none of them required. Roughly 30,000 words, written as if by someone in the year it happened who did not know they were in a game. Built with~9,000 lines of hand-written JavaScript and one vendored copy of three.js. No engine, no build step, no dependencies. @@ -123,8 +124,8 @@ on a wire.

What it does not do

    -
  • It is short for what it is. Each hop runs 3–8 minutes against a design document that asks for 5–20. The genres, mechanics and set pieces are all in; the duration is not.
  • -
  • The philes are incomplete. 75 of a planned 200 text collectibles are written. The rest is a writing job, not a code job.
  • +
  • It is still short for what it is. A hop runs 5–12 minutes now against a design document that asks for 5–20, and the shorter ones are short. The genres, mechanics and set pieces are all in.
  • +
  • There is no multiplayer. The design calls for anonymous pass-by contact between players. It needs a server and there isn't one.
  • It is not accessible. There is no colourblind mode, no remappable input, no subtitles for the non-speech audio cues, and at least one mechanic is gated behind hearing a 450 ms rhythm. This is a real limitation and not a stylistic choice.
  • Performance is untuned. It runs on a modern laptop. It has not been profiled on anything else, and one level puts 4,200 instanced meshes on screen.
  • diff --git a/src/levels/board.js b/src/levels/board.js index bb2060a..c359c8b 100644 --- a/src/levels/board.js +++ b/src/levels/board.js @@ -162,12 +162,100 @@ const TW_MAP = { const TW_PORTS = { 2: 'BBS', 3: 'SBB', 5: 'SSB', 6: 'BSS', 7: 'SBS', 8: 'BBB', 9: 'SSS', 10: 'BBB' }; const TW_BASE = { ore: 18, org: 32, equ: 61 }; +// the rest of the board. everything here was written before the board closed — +// nothing after 1991 exists yet, because nobody has done it yet. reading online +// is free; only area 1 gets carried, and carrying is what costs you. +const AREAS = [ + { name: 'g-philes', note: 'general files. somebody sat down and wrote these.', items: [ + ['mentor', 'CONSC.TXT', 'somebody wrote it the week he got arrested'], + ['conscience', 'HACKER.TXT', 'the one everybody prints out'], + ['2600mag', '2600-SUB.TXT','a quarterly, and it costs a quarter'], + ['cdc', 'CDC-001.TXT', 'a text file group out of a slaughterhouse'], + ['defcon', 'DC1-FLYR.TXT','somebody is throwing a party in Las Vegas'], + ['mod-lod', 'THE-WAR.TXT', 'New York against Texas, on the switch'], + ['jargon-file', 'JARGON.TXT', 'a dictionary of about four hundred people'], + ['bofh', 'BOFH-01.TXT', 'a story about the person who has root'], + ['rtfm', 'RTFM.TXT', 'rude, and nearly always correct'], + ['the-well', 'THE-WELL.TXT','you own your own words'], + ]}, + { name: 'the law', note: 'read these before you need them.', items: [ + ['sundevil', 'SUNDEVIL.TXT','forty-two machines in fourteen cities'], + ['cfaa', 'CFAA-86.TXT', 'exceeding authorised access, whatever that is'], + ['eff', 'EFF-FND.TXT', 'somebody had to be on the other side'], + ['captain-zap', 'ZAP-1981.TXT','he moved the clocks'], + ['poulsen', 'KIIS-102.TXT','caller one hundred and two'], + ['the-414s', '414S.TXT', 'a boy on the cover of a magazine'], + ['wargames', 'WARGAMES.TXT','that is where the word comes from'], + ['ccc-btx', 'BTX-HH.TXT', 'they gave it back at a press conference'], + ['mitnick', 'KM-FILE.TXT', 'the one they made a story out of'], + ['l0pht', 'L0PHT.TXT', 'we can take it down in half an hour'], + ]}, + { name: 'the scene', note: 'not written by us. moved by us.', items: [ + ['courier', 'CRIER.NFO', 'zero day means today'], + ['cracktro', 'INTRO.TXT', 'the bit before the game'], + ['nfo', 'HOWTO.NFO', 'why every one of these is drawn the same'], + ['lenslok', 'LENSLOK.TXT', 'the protection you hold up to the screen'], + ['keygen-music', 'TRACKER.TXT', 'why the generator has a tune'], + ]}, + { name: 'v i r u s', note: 'do not run any of this on the family machine.', items: [ + ['elk-cloner', 'CLONER.TXT', 'a fifteen year old and a boot sector'], + ['brain', 'BRAIN-C.TXT', 'their real address is in it'], + ['aids-trojan', 'AIDS-DSK.TXT','twenty thousand floppies in the post'], + ['fork-bomb', 'FORKB.TXT', 'it is thirteen characters'], + ['morris', 'RTM-88.TXT', 'one in seven'], + ]}, + { name: 'the iron', note: 'how any of this got built.', items: [ + ['punch-card', '80COL.TXT', 'eighty columns, and why your screen is'], + ['core-memory', 'CORE.TXT', 'it was woven, by hand, under a lens'], + ['pdp11', 'PDP11.TXT', 'the machine the language is shaped like'], + ['altair', 'ALTAIR.TXT', 'you enter the program with switches'], + ['homebrew', 'HBCC-LTR.TXT','the open letter'], + ['tape-load', 'TAPELOAD.TXT','the noise is the program'], + ['vt100', 'VT100.TXT', 'escape, bracket, two, J'], + ['hayes', 'ATCMD.TXT', 'three pluses and a second of silence'], + ['coupler', 'COUPLER.TXT', 'why it is a rubber cup and not a plug'], + ['isdn', 'ISDN.TXT', 'ready twenty years early, priced for a business'], + ]}, + { name: 'telephony', note: 'the network is a machine and it is documented.', items: [ + ['esquire', 'ESQUIRE.TXT', 'the article that told everybody'], + ['joybubbles', 'JOY.TXT', 'perfect pitch and a lot of time'], + ['inward', 'INWARD.TXT', 'the operator has an operator'], + ['silver-box', 'SILVER.TXT', 'the fourth column, and who it is for'], + ['payphone', 'COINTONE.TXT','the box knows by the sound'], + ['cocot', 'COCOT.TXT', 'a payphone that belongs to a petrol station'], + ['wats', 'EXTENDER.TXT','four digits is not a lot'], + ['operator', 'SOCIAL.TXT', 'she is not a security system'], + ['busy-signal', 'TONES.TXT', 'sixty a minute, and reorder is twice that'], + ['call-waiting', 'STAR70.TXT', 'the click that eats forty minutes'], + ]}, + { name: 'unix', note: 'somebody typed all of this out for us.', items: [ + ['pipes', 'PIPES.TXT', 'the vertical bar'], + ['everything-file','EVERYFIL.TXT','the disk is a file, the terminal is a file'], + ['devnull', 'DEVNULL.TXT', 'the bit bucket'], + ['editors', 'VIVSEMAC.TXT','why the commands are one letter'], + ['man-page', 'MANPAGE.TXT', 'read the section at the bottom'], + ['hosts-equiv', 'RHOSTS.TXT', 'trust by name'], + ['telnet', 'CLEARTXT.TXT','it is all in the clear'], + ['anon-ftp', 'ANONFTP.TXT', 'the password is your address, as a courtesy'], + ['uucp', 'BANGPATH.TXT','you do not have an address, you have directions'], + ['salt', 'SALT.TXT', 'why two people with the same password differ'], + ['toctou', 'RACE.TXT', 'between the check and the use'], + ['sendmail-debug','SMDEBUG.TXT','it was left switched on'], + ]}, +]; + const FILES = [ - { name: 'PHRACK24.ZIP', size: '84,102', note: 'issue 24. the one with the E911 document in it.' }, - { name: 'TAP-42.TXT', size: '11,904', note: 'youth international party line, later TAP.' }, - { name: 'BLUEBOX.EXE', size: ' 6,288', note: 'MF tones through a PC speaker. it will not work. try it anyway.' }, - { name: 'LOD-TJ-1.TXT', size: '38,441', note: 'legion of doom technical journal, volume one.' }, - { name: 'TONELOC.ZIP', size: '52,010', note: 'wardialer. give it an exchange and go to bed.' }, + { name: 'PHRACK24.ZIP', size: '84,102', note: 'issue 24. the one with the E911 document in it.', phile: 'e911' }, + { name: 'TAP-42.TXT', size: '11,904', note: 'youth international party line, later TAP.', phile: 'tap' }, + { name: 'BLUEBOX.EXE', size: ' 6,288', note: 'MF tones through a PC speaker. it will not work. try it anyway.', phile: 'redbox' }, + { name: 'LOD-TJ-1.TXT', size: '38,441', note: 'legion of doom technical journal, volume one.', phile: 'blackbox' }, + { name: 'TONELOC.ZIP', size: '52,010', note: 'wardialer. give it an exchange and go to bed.', phile: 'loop' }, + { name: 'CBBS-HIST.TXT', size: ' 4,102', note: 'how the first one got built in a blizzard.', phile: 'cbbs' }, + { name: 'XMODEM.DOC', size: ' 9,540', note: '128 bytes and a checksum. the whole protocol.', phile: 'xmodem' }, + { name: 'NODELIST.297', size: '61,338', note: 'every board in the world. updated weekly, by hand.', phile: 'nodelist' }, + { name: 'ARTPACK.ZIP', size: '78,220', note: 'this month\'s ansi. somebody drew all of it a cell at a time.', phile: 'ansi-art' }, + { name: 'SJGAMES.TXT', size: '15,776', note: 'they raided a company that makes boardgames.', phile: 'sjgames' }, + { name: 'CRACKING.TXT', size: '22,110', note: 'weak bits, lenslok, and why the copy works better.', phile: 'weak-bits' }, ]; export default { @@ -376,6 +464,7 @@ export default { S.elite = true; S.ratioOff = true; ctx.game.flags.lastCall = true; + ctx.phile('no-carrier'); ctx.phile('lastcall'); ctx.say('nine years. forty-four users. one phone line.', 5600); await term.ask('|08 [enter]|07'); @@ -499,6 +588,45 @@ export default { }, async files(ctx, term) { + // area menu first. area 1 is the one you can carry away. + while (true) { + term.clear(); + term.writeln('|11 file areas\n'); + term.writeln(`|08 1 |15${'text'.padEnd(12)}|07${String(FILES.length).padStart(3)} files|08 the notable ones. downloadable.`); + AREAS.forEach((a, i) => term.writeln( + `|08 ${String(i + 2).padStart(2)} |15${a.name.padEnd(12)}|07${String(a.items.length).padStart(3)} files|08 ${a.note}`)); + term.writeln(''); + term.writeln('|03 you can read anything here online for nothing. downloading is what'); + term.writeln('|03 costs you — a file you carry is a file you have to fit down the next hop.'); + term.writeln('|08 [|15#|08] area [enter] back'); + const sel = parseInt((await term.ask('|07area: |15')).trim(), 10); + if (sel === 1) { await this.filearea1(ctx, term); continue; } + const A2 = AREAS[sel - 2]; + if (!A2) return; + await this.readarea(ctx, term, A2); + } + }, + + // reading online: free, and the only way most of this is ever going to be seen + async readarea(ctx, term, area) { + while (true) { + term.clear(); + term.writeln(`|11 ${area.name}\n`); + area.items.forEach(([id, name, note], i) => term.writeln( + `|08 ${String(i + 1).padStart(2)} |15${name.padEnd(14)}|08${ctx.game.philes.has(id) ? '|02 read ' : ' '}|08${note}`)); + term.writeln(''); + term.writeln('|08 [|15#|08] read online [enter] back'); + const n = parseInt((await term.ask('|07: |15')).trim(), 10); + const it = area.items[n - 1]; + if (!it) return; + await term.paint(`|07 reading |15${it[1]}|07 ...\n`, 2200); + A.blip(1200, 0.01, 0.05); + ctx.phile(it[0]); + await ctx.sleep(500); + } + }, + + async filearea1(ctx, term) { term.clear(); term.writeln('|11 file area 1 — text\n'); FILES.forEach((f, i) => term.writeln(`|08 ${String(i + 1).padStart(2)} |15${f.name.padEnd(16)}|07${f.size.padStart(8)} |08${f.note}`)); @@ -532,7 +660,7 @@ export default { ctx.game.cargo.push(f.name); ctx.game.mtu = Math.max(576, 1500 - ctx.game.cargo.length * 180); ctx.refreshHud(); - ctx.phile('file-' + n, f.name, f.note); + ctx.phile(f.phile || ('file-' + n), f.name, f.note); ctx.say(`you are carrying ${ctx.game.cargo.length}. your MTU is down to ${ctx.game.mtu}.`, 3600); await ctx.sleep(1000); }, diff --git a/src/levels/climb.js b/src/levels/climb.js index 2c378f5..8369613 100644 --- a/src/levels/climb.js +++ b/src/levels/climb.js @@ -10,6 +10,51 @@ import * as A from '../audio.js'; import * as In from '../input.js'; import { sprite, panel, glow, rng, clamp, damp } from '../kit.js'; +// ── the years, on the wall, going past +// +// The climb is the only level that travels forwards in time, so it is the only +// place the rest of this can be. Each one is a sign you pass at speed; you get +// the file whether or not you turn your head, because you were there for it. +const MARKS = [ + [ 60, '1969', 'the first message got two letters out', 'first-message'], + [ 110, '1977', 'fifty-six bits, and an argument about why', 'des'], + [ 160, '1983', 'the whole network changed protocol on one day', 'flag-day'], + [ 200, '1987', 'they moved every newsgroup and invented alt', 'great-renaming'], + [ 300, '1987', 'somebody wanted to see the route, not the end', 'traceroute'], + [ 360, '1991', 'a label on a machine at CERN: do not power down', 'first-web'], + [ 420, '1992', 'the sixth of march, and almost nothing happened', 'michelangelo'], + [ 540, '1993', 'they announced a licence fee, and lost', 'gopher'], + [ 600, '1993', 'september stopped ending', 'eternal-september'], + [ 660, '1993', 'a chip with a spare key in it', 'clipper'], + [ 780, '1993', 'they classified mathematics as a munition', 'pgp'], + [ 840, '1994', 'a stateless protocol was given a basket', 'cookies'], + [ 900, '1995', 'the backbone was switched off and nothing broke', 'nsfnet'], + [1020, '1995', 'source code is speech', 'bernstein'], + [1080, '1995', 'the shirt is a munition', 'munitions'], + [1140, '1995', 'thirteen, because fourteen does not fit', 'root-servers'], + [1260, '1996', 'the word arrives for something much older', 'phish'], + [1300, '1996', 'a packet slightly too big to be reassembled', 'ping-death'], + [1340, '1997', 'the refusals are not all the same refusal', 'nmap'], + [1380, '1998', 'the quote closes early', 'sqli'], + [1420, '1998', 'the lock is protected, not the thing behind it', 'dmca'], + [1460, '1996', 'the argument that ends with a second phone line', 'second-line'], + [1500, '1998', 'fifty-six down and thirty-three up, and here is why', 'v90'], + [1540, '1999', 'two digits, and everyone stayed up', 'y2k'], + [1580, '2000', 'nobody could charge anybody', 'iloveyou'], + [1620, '2000', 'the page says it, so the browser believes it', 'xss'], + [1700, '2003', 'three hundred and seventy-six bytes, ten minutes', 'slammer'], + [1740, '2004', 'you cannot hide in an empty room', 'tor'], + [1790, '2010', 'four unpublished holes, for one room', 'stuxnet'], + [1840, '2014', 'the code was twenty-five years old', 'shellshock'], + [1890, '2016', 'sixty-one factory passwords', 'mirai'], + [1900, '2013', 'he downloaded papers. thirteen felony counts.', 'swartz'], + [1940, '2017', 'the patch had been out for two months', 'equifax'], + [1965, '2017', 'somebody registered the domain for ten dollars', 'wannacry'], + [1990, '2017', 'somebody kept it, and then lost it', 'eternalblue'], + [2040, '2018', 'the guess it threw away left a mark', 'meltdown'], + [2090, '2020', 'they did not break in, they got built in', 'solarwinds'], +]; + // in reverse, because that is the point. `gate` is the verb that era taught you. const ZONES = [ { at: 0, name: '1971 · the trunk', bg: 0x030a14, fog: 0x0a1930, col: 0x2a5a8a, @@ -51,7 +96,7 @@ export default { async mount(ctx) { const { scene, camera } = ctx; - S = { t: 0, z: 0, x: 0, speed: 44, zone: -1, rings: [], done: false, + S = { t: 0, z: 0, x: 0, speed: 44, zone: -1, mark: 0, picked: 0, rings: [], done: false, spec: null, hammer: 0, hammered: false, log4: false, gateOpen: false, gates: [], invert: 0, embers: [], stall: 0, passed: 0, failed: 0 }; window.__S = S; // debug handle @@ -92,6 +137,23 @@ export default { S.gateTag.position.set(0, -6.0, -30); scene.add(S.gateTag); S.gateTag.visible = false; + // the years themselves, bolted to the shaft wall, alternating sides + const MODERN_AT = ZONES[ZONES.length - 1].at; + S.marks = MARKS.map(([at, year, line], i) => { + const g = new THREE.Group(); + const side = i % 2 ? 1 : -1; + // the last zone is the white one. pale blue on near-white is not a sign, + // it is a rumour — so the ink flips where the walls do. + const ink = at >= MODERN_AT; + const yr = sprite(year, { color: ink ? '#123c4e' : '#8fb6c6', size: 1.5 }); + yr.position.set(side * 9.5, 3.2, 0); g.add(yr); + const tx = sprite(line, { color: ink ? '#41606e' : '#4d6a78', size: 0.72 }); + tx.position.set(side * 9.5, 1.4, 0); g.add(tx); + g.visible = false; // moved into place each frame, like the gates + scene.add(g); + return g; + }); + // ── one gate per era, built from that era's furniture ZONES.forEach(Z => { if (Z.gate) S.gates.push(this.makeGate(ctx, Z)); }); @@ -228,6 +290,19 @@ export default { // ── zone crossfade let zi = 0; + // the camera does not travel — the shaft does. so the signs come to you. + for (let i = 0; i < S.marks.length; i++) { + const zz = -MARKS[i][0] + S.z; + S.marks[i].position.z = zz; + S.marks[i].visible = zz > -190 && zz < 10; + } + + // you get the file for having been there, whether or not you looked + while (S.mark < MARKS.length && S.z >= MARKS[S.mark][0]) { + const m = MARKS[S.mark++]; + if (ctx.phile(m[3], null, null, true)) S.picked++; // quiet — counted at the top + } + for (let i = 0; i < ZONES.length; i++) if (S.z >= ZONES[i].at) zi = i; const Z = ZONES[zi]; if (zi !== S.zone) { @@ -238,6 +313,10 @@ export default { document.body.classList.toggle('ink-dark', zi === ZONES.length - 1); A.burst(220 + zi * 90, 330 + zi * 90, 0.25, 0.14); if (Z.note) ctx.say(Z.note, 4200); + // each era drops what it is known for on the way past + const DROP = { 'the board': 'ansi-art', '1988': 'first-message', 'efnet': 'ping-death', + 'the stack': 'traceroute', 'modern': 'heartbleed' }; + if (DROP[Z.name]) ctx.phile(DROP[Z.name]); } S.rings.forEach((r, i) => { @@ -277,6 +356,7 @@ export default { ctx.game.flags.climbClean = S.passed; ctx.game.flags.climbGates = S.gates.length; if (S.passed >= 6) ctx.phile('climb'); + if (S.picked) ctx.say(`you read ${S.picked} of them on the way past. P.`, 4000); await ctx.card('ROOT', `${S.passed} of ${S.gates.length} clean · one hop remaining`, 3600); ctx.next(); })(); @@ -435,6 +515,7 @@ export default { A.burst(1320, 1980, 0.4, 0.2); ctx.say('a bit flipped in a row you were never allowed to touch. you did not read it. you shouted next to it.', 6000); ctx.phile('rowhammer'); + ctx.phile('xz'); } } } @@ -466,6 +547,7 @@ export default { await ctx.sleep(5800); ctx.say('you did nothing. you held up a sign.', 4600); ctx.phile('log4shell'); + ctx.phile('y2k'); S.signBack.visible = false; })(); } diff --git a/src/levels/coldboot.js b/src/levels/coldboot.js index b60cfae..b6df55f 100644 --- a/src/levels/coldboot.js +++ b/src/levels/coldboot.js @@ -254,6 +254,7 @@ export default { if (!S.strayed && Math.hypot(body.pos.x, body.pos.z) < 30) { S.strayed = true; ctx.phile('ring-buffer'); + ctx.phile('rfc'); ctx.say('you have left your slot. this has never mattered before.', 3800); } diff --git a/src/levels/handshake.js b/src/levels/handshake.js index 091b580..4f03443 100644 --- a/src/levels/handshake.js +++ b/src/levels/handshake.js @@ -137,7 +137,7 @@ const BOSS = { } }, win(ctx) { S.b.tone.stop(0.5); nextPhase(ctx, 'the line is open. now you have to say something.'); }, - exit() { S.b.tone?.stop(0.2); }, + exit() { S.b?.tone?.stop(0.2); }, }, // ── 2. DTMF — every digit is a row tone crossed with a column tone. @@ -280,7 +280,7 @@ const BOSS = { } }, win(ctx) { S.b.tone.stop(); ctx.phile('ansam'); nextPhase(ctx, 'it has stopped asking politely.'); }, - exit() { S.b.tone?.stop(); }, + exit() { S.b?.tone?.stop(); }, }, // ── 4. V.8bis — THE SCREAM. The modem asking what can you do. @@ -437,16 +437,18 @@ const BOSS = { const ORDER = ['dialtone', 'dtmf', 'ans', 'scream', 'carrier']; async function nextPhase(ctx, line) { - if (S.switching) return; + if (S.switching || S.dead) return; S.switching = true; const cur = BOSS[ORDER[S.i]]; if (cur && cur.exit) cur.exit(); for (let i = S.arena.children.length - 1; i >= 0; i--) S.arena.remove(S.arena.children[i]); if (line) { ctx.say(line, 3200); await ctx.sleep(1900); } + if (S.dead) return; S.i++; if (S.i >= ORDER.length) { S.switching = false; return; } const nb = BOSS[ORDER[S.i]]; await ctx.card(nb.name, nb.freqs, 2400); + if (S.dead) return; ctx.say(nb.hint, 3600); if (S.i > 0) ctx.spendTTL(1); nb.enter(ctx); @@ -494,6 +496,7 @@ export default { ctx.hint('WASDmove'); await ctx.card('THE HANDSHAKE', 'hop 45 · five tones, in order', 3000); + if (S.dead) return; // left via the map while the card was up nextPhase(ctx, null); }, @@ -533,6 +536,7 @@ export default { }, async unmount(ctx) { + S.dead = true; // any await still in flight bails on resume const cur = BOSS[ORDER[S.i]]; if (cur && cur.exit) cur.exit(); ctx.link.glitch = 0; diff --git a/src/levels/shell.js b/src/levels/shell.js index 25c3885..8192e58 100644 --- a/src/levels/shell.js +++ b/src/levels/shell.js @@ -194,6 +194,8 @@ export default { if (cmd === 'help') { term.writeln('|08 who news talk minna make uname uptime logout'); + term.writeln('|08 cat — try /etc/passwd, /etc/shadow, trust.txt, setuid.4, its.txt'); + term.writeln('|08 man — ping finger whois tcpdump named hosts'); term.writeln('|08 (and it will keep compiling while you do all of those.)'); } else if (cmd === 'who') { WHO.forEach(([u, tty, at, from]) => @@ -202,6 +204,31 @@ export default { term.writeln('|03 thirty-eight logged in. the board had forty-four users in nine years'); term.writeln('|03 and could hold exactly one of them at a time.'); ctx.phile('multiuser'); + } else if (cmd === 'man') { + const MAN = { + ping: ['ping', 'PING(8) — send ICMP ECHO_REQUEST to network hosts. "author: mike muuss."'], + finger: ['finger', 'FINGER(1) — user information lookup. reads .plan and .project from the home directory.'], + whois: ['whois', 'WHOIS(1) — internet user name directory service. try a surname.'], + tcpdump: ['tcpdump', 'TCPDUMP(1) — dump traffic on a network. requires you to be root, and to be honest.'], + named: ['dns', 'NAMED(8) — internet domain name server. see also: the file this replaced.'], + hosts: ['dns', 'HOSTS(5) — host name data base. historical. see named(8).'], + }; + const d = MAN[arg]; + if (!d) { term.writeln('|12 No manual entry for ' + (arg || '')); continue; } + term.writeln('|07 ' + d[1]); + ctx.phile(d[0]); + } else if (cmd === 'cat' || cmd === 'more') { + const DOCS = { + '/etc/passwd': ['passwd', 'root:x9Kj2mQp:0:1:operator:/:/bin/csh\n ...and 400 more, and every one of those hashes is readable by you.'], + 'trust.txt': ['trusting-trust', 'Ken Thompson, 1984. "you can\'t trust code that you did not totally create yourself."'], + '/etc/shadow': ['passwd', 'No such file or directory. (it does not exist yet. that is the point.)'], + 'setuid.4': ['setuid', 'one bit: run as the owner, not the runner. necessary, elegant, and half of everything.'], + 'its.txt': ['its-nopass', 'the AI Lab machine had no passwords, on purpose, and it worked for a while.'], + }; + const d = DOCS[arg]; + if (!d) { term.writeln('|12 ' + (arg || '') + ': No such file or directory.'); continue; } + term.writeln('|07 ' + d[1]); + ctx.phile(d[0]); } else if (cmd === 'uname') { if (S.unamePatched) { term.writeln(arg === '-a' diff --git a/src/levels/stack.js b/src/levels/stack.js index 77bfb40..7f98ae7 100644 --- a/src/levels/stack.js +++ b/src/levels/stack.js @@ -437,7 +437,7 @@ export default { A.burst(660 + S.i * 90, 990 + S.i * 90, 0.28, 0.16); (async () => { if (shed) ctx.say(`shed ${shed}`, 2200); - if (S.i === 0) ctx.phile('osi'); + if (S.i === 0) { ctx.phile('osi'); ctx.phile('layers'); } await ctx.sleep(700); S.i = nxt; S.spawn = S.body.pos.clone(); diff --git a/src/main.js b/src/main.js index 0268b9d..9169d2b 100644 --- a/src/main.js +++ b/src/main.js @@ -120,11 +120,14 @@ const ctx = { }, say: HUD.say, speak: HUD.speak, hint: HUD.hint, card: HUD.card, sleep: HUD.sleep, hush: HUD.hush, flash: HUD.flash, expire: () => expire(), - phile(id, title, body) { + // quiet: collect it without a toast. for places that hand over a lot at once — + // the climb passes thirty-four years of signs in under a minute and announcing + // each one would be a wall of notifications instead of a level. + phile(id, title, body, quiet) { if (game.philes.has(id)) return false; game.philes.add(id); const p = phileLookup(id, title, body); - HUD.say(`phile ${p.title} [${game.philes.size}/${PHILE_TOTAL}]   P to read`, 3000); + if (!quiet) HUD.say(`phile ${p.title} [${game.philes.size}/${PHILE_TOTAL}]   P to read`, 3000); A.blip(1800, 0.05, 0.05); setTimeout(() => A.blip(2400, 0.06, 0.05), 70); phileLog.push(p); return true; diff --git a/src/philes.js b/src/philes.js index a93811f..73d4484 100644 --- a/src/philes.js +++ b/src/philes.js @@ -1108,6 +1108,1589 @@ wearing it. the reply was not empty.`, }, + + // ── the deep cuts: phreaking ─────────────────────────────────────────── + + 'esquire': { + title: 'Secrets of the Little Blue Box', + from: 'Esquire, October 1971', + body: `a magazine article in a men's magazine, between the adverts, explaining in +plain english that the telephone network could be operated by anybody who +could make the right noise. + +it did not include the frequencies. it did not need to. it told several +hundred thousand people that the thing was possible, and possible is the +only part that is hard. + +two of the people who read it were students in california who started +building the boxes and selling them out of a dormitory. one of them later +founded a computer company. so did the other one.`, + }, + + 'tap': { + title: 'TAP', + from: 'Youth International Party Line, later TAP, 1971-1984', + body: `a newsletter. photocopied, stapled, posted to whoever sent stamps. + +it started as a way of not paying the phone company on political grounds +and quietly turned into the only technical publication in the world that +would print how any of it actually worked. + +circulation peaked somewhere in the thousands. the schematics were often +wrong. it is the direct ancestor of every text file in this game, and of +the word "phile" itself.`, + }, + + 'redbox': { + title: 'the red box', + from: 'a payphone, any payphone', + body: `a payphone told the exchange it had been paid by playing tones down the +line. a nickel was one beep of 1700 and 2200 hertz together. a dime was +two. a quarter was five, in a particular rhythm. + +it did not check. there was nothing to check with. the coin mechanism and +the tone generator were the same box, and the exchange a mile away simply +believed whatever noise arrived. + +people built these into pocket tone dialers by changing one crystal.`, + }, + + 'blackbox': { + title: 'the black box', + from: 'the other end of the call', + body: `billing started when the called phone went off-hook, because that is how +the exchange knew somebody had answered. + +so you put a resistor across your own line that let the ring through but +never quite told the exchange you had picked up. the caller could hear you. +you could hear them. the meter never started. + +it worked for about fifteen minutes a call before the equipment got +suspicious, which was long enough, and it cost about a dollar to build.`, + }, + + 'loop': { + title: 'the loop-around', + from: 'a pair of numbers that only talk to each other', + body: `test circuits. two numbers wired together so a lineman could ring one from +one end of town and the other from the other and check the pair. + +which means two people who both know the pair can talk, free, forever, on a +circuit that appears on nobody's bill — because it is not a call to +anywhere, it is a call to a piece of test equipment that has two ends. + +whole friendships lived on those. some of them for years.`, + }, + + 'joybubbles': { + title: 'Joybubbles', + from: 'born blind, with perfect pitch, 1949', + body: `he could whistle 2600 hertz. not approximately - exactly, on demand, cold, +with no reference tone, which almost nobody alive can do. + +he found the network at seven years old by dialling numbers to hear what +answered, and remembering all of it. he was running free conference calls +across the country before he was a teenager, because there was nothing else +to do and he was extremely good at it. + +he legally changed his name to Joybubbles in 1991 and spent his later years +running a phone line where he read children's stories to anybody who rang. +he never once used any of it to make money.`, + }, + + 'inward': { + title: 'the inward operator', + from: 'social engineering, before it had a name', + body: `you did not need a blue box if you could sound like the phone company. + +there was an operator whose whole job was helping other operators, and she +was not equipped to doubt you, because in her world the only people who +knew the internal codes were colleagues. so you learned the codes, and the +tone of voice, and you asked for what you wanted, and you got it. + +no equipment. no frequencies. the largest machine ever built, operated by +sounding like you belonged in it.`, + }, + + // ── the deep cuts: the boards ────────────────────────────────────────── + + 'cbbs': { + title: 'the first one', + from: 'Chicago, February 1978', + body: `there was a blizzard. nobody could get to the club meeting, so two members +built a way to leave messages for each other instead - one wrote the +software, the other built the hardware, in about two weeks, mostly because +they were snowed in. + +CBBS. one line, 300 baud. they published how to build it, and within a few +years there were thousands of them. + +the entire culture in this game exists because it snowed heavily in +Illinois in January 1978.`, + }, + + 'xmodem': { + title: 'XMODEM', + from: 'Ward Christensen, 1977, given away', + body: `128 bytes, a checksum, and a one-byte answer meaning yes or no. send the +block, wait, send it again if the answer is no. + +that is about as simple as a file transfer can be and still survive a line +that loses things. he wrote it for himself, gave it away, and it became the +way software moved between human beings for fifteen years. + +every warez board, every shareware disk, every text file in this game moved +128 bytes at a time under a protocol nobody was ever paid for.`, + }, + + 'nodelist': { + title: 'the nodelist', + from: 'FidoNet - one text file, updated weekly', + body: `every board in the network, its number, its phone number, its operator, its +hours. published once a week. everybody downloaded it. that was the routing +table. + +there was no server. the network's entire topology was a document that +people agreed to keep up to date, and it worked for fifteen years across +tens of thousands of nodes. + +it was maintained by volunteers, because somebody had to.`, + }, + + 'ansi-art': { + title: 'the ANSI scene', + from: 'sixteen colours and eight block characters', + body: `eighty by twenty-five cells, sixteen colours, and half a dozen block glyphs +of different densities. that is the whole palette. + +people made portraits with it. groups formed, released monthly art packs, +had rivalries, split acrimoniously and reformed under new names - over a +medium whose maximum resolution was a text screen on a machine that could +not draw a diagonal line. + +almost none of it survives in a form anybody can still view.`, + }, + + // ── the deep cuts: unix ──────────────────────────────────────────────── + + 'passwd': { + title: 'why /etc/passwd is world-readable', + from: 'a decision from 1973 that everybody inherited', + body: `the password file has to be readable by everything, because everything +needs to turn a user id into a name. + +so the passwords were hashed rather than stored - good - and the hashes +were left in the file everybody can read, which seemed fine, because who +would sit there guessing. + +people sat there guessing. the fix took until 1988, and the fix was to move +the hashes into a second file called shadow. that is the entire solution: +put it somewhere else.`, + }, + + 'trusting-trust': { + title: 'Reflections on Trusting Trust', + from: 'Ken Thompson, Turing Award lecture, 1984', + body: `he explains, calmly, that he could put a back door in the login program. + +then that he could put it in the compiler instead, so it inserts the back +door whenever it compiles login - and delete it from login's source. + +then that he could teach the compiler to insert *that* whenever it compiles +a compiler, and delete it from the compiler's source too. now the back door +exists in no source code anywhere on earth, and copies itself into every +future compiler forever. + +"you can't trust code that you did not totally create yourself."`, + }, + + 'setuid': { + title: 'setuid', + from: 'one bit', + body: `a bit on a file that says: when anybody runs this, run it as whoever owns +it, not as whoever ran it. + +it is how an ordinary user changes their own password in a file they are +not allowed to write. it is necessary and it is elegant. + +it is also why a bug in one small program is a bug in the entire machine, +and roughly half of everything on the way down this stack starts with +somebody finding a setuid binary that was not careful enough.`, + }, + + 'its-nopass': { + title: 'the machine with no passwords', + from: 'the MIT AI Lab', + body: `it had no passwords, deliberately. anybody could log in as anybody. you +could watch what other people were typing and they could watch you. + +this was not an oversight and it was not naivety. it was a position: the +machine belonged to the people using it, and locking each other out of +parts of it would have made it a worse machine to think with. + +it worked, for a while, in a building where everyone knew everyone. every +single thing after this point in the game is a consequence of that no +longer being true.`, + }, + + // ── the deep cuts: the network ───────────────────────────────────────── + + 'first-message': { + title: 'LO', + from: 'UCLA to SRI, 29 October 1969, 22:30', + body: `the first thing ever sent between two computers on the ARPANET was the word +LOGIN. + +it crashed after two characters. so the first message was LO, and then +they rang each other on the telephone to check what had happened, because +the telephone worked. + +they brought it back up about an hour later and it went through. the log +entry for the most important thing that happened that decade reads +"talked to SRI host to host".`, + }, + + 'flag-day': { + title: 'the flag day', + from: '1 January 1983', + body: `the network changed protocol. NCP off, TCP/IP on, everywhere, on the same +morning, because there was no way to run both. + +about four hundred hosts. they had over a year of warning and it still +took months to finish, and people wore badges that said "I SURVIVED THE +TCP/IP TRANSITION". + +it is the last time in history the entire internet did anything at once. +There will not be another one.`, + }, + + 'rfc': { + title: 'Request for Comments', + from: 'RFC 1, April 1969', + body: `a graduate student had to write up what they had agreed, was terrified of +seeming to speak for anybody, and so called it a "request for comments" and +wrote it in a bathroom at three in the morning so as not to wake his +housemates. + +that apologetic hedge became the name of the document series that +standardises the internet. they are still called that. there are now +thousands and the tone has never quite recovered from being set by somebody +trying very hard not to presume.`, + }, + + 'traceroute': { + title: 'how traceroute cheats', + from: 'the TTL field, used sideways', + body: `nothing in the protocol tells you the path. there is no "list the routers" +message and there never was. + +so: send a packet with a TTL of one. the first router decrements it to +zero, throws it away, and politely posts you a note saying it did — and the +note has its address on it. now send one with a TTL of two. + +the map in the corner of this game is built out of a stack of dead packets +and the apologies their killers sent back.`, + }, + + 'ping-death': { + title: 'the ping of death', + from: '1996', + body: `the maximum size of an IP packet is 65,535 bytes. so nobody checked, because +it cannot be bigger than the maximum, that is what maximum means. + +but a packet can be fragmented, and if the last fragment claims an offset +near the top, the pieces reassemble into something larger than the maximum, +into a buffer sized for the maximum. + +you could crash almost any machine on earth in 1996 by pinging it with a +number that was too big.`, + }, + + // ── the deep cuts: what happened to people ───────────────────────────── + + 'e911': { + title: 'the E911 document', + from: 'Phrack 24, and everything that followed', + body: `a text file describing the administration of the emergency call system. it +was copied off a telephone company machine and printed in a hacker zine. + +the company valued it at seventy-nine thousand dollars, which is what +turned it into a felony. at trial it emerged you could order the same +information from them for thirteen dollars. + +the case collapsed. by then it had already produced a nationwide raid, a +publisher raided over a game book, and, directly out of the wreckage, the +Electronic Frontier Foundation.`, + }, + + 'sjgames': { + title: 'they raided a game company', + from: '1 March 1990', + body: `the Secret Service took the computers of a company in Austin that made +tabletop roleplaying games, because one of their employees was on a board +they were interested in. + +among the things seized was the entire manuscript of a game book about +computer hacking, which the agents appear to have read as a manual. + +the company nearly went under. they sued and won. one of the people who +helped pay the lawyers went on to found the EFF, and the reason there is +any legal defence for this at all is that somebody took a boardgame +publisher's hard drives.`, + }, + + 'l0pht': { + title: 'thirty minutes', + from: 'a Senate committee, 19 May 1998', + body: `seven people testified under their handles, which the committee allowed, +and told a Senate panel that they could make the internet unusable +nationwide in about half an hour. + +they were not exaggerating and they were not threatening. they had been +saying it publicly for years and nobody with authority had asked. + +they had been doing this out of a shared loft in Boston with second-hand +equipment. the flaw they were describing took another decade to fix.`, + }, + + 'mitnick': { + title: 'sequence numbers', + from: 'Christmas Day, 1994', + body: `the trick was that two machines trusted each other by address, and the only +thing stopping you pretending to be one of them was that you had to guess a +number. + +the number was supposed to be unpredictable. it was not. it went up by a +fixed amount, so you could measure it, and then you could speak as somebody +else without ever hearing the replies — talking into a phone you cannot +listen to, and being believed. + +he did it. it took a year to find him, and it made both their names.`, + }, + + // ── the deep cuts: the machines lie to you ───────────────────────────── + + 'weak-bits': { + title: 'weak bits', + from: 'copy protection, 1983', + body: `they wrote a section of the floppy disk so badly, on purpose, that reading +it gave a different answer every time. + +so the game read that spot twice and compared. a real disk disagreed with +itself. a copy — made by a drive doing its honest best to write down what +it read — was perfectly consistent, and therefore fake. + +the only way to prove the disk was genuine was to prove it was unreliable.`, + }, + + 'lenslok': { + title: 'Lenslok', + from: 'a piece of plastic in the box, 1985', + body: `the game showed you a scrambled pattern. you held a prism lens against the +screen and it resolved into two letters, which you typed in. + +it did not work. the lens was cut for one screen size, and televisions +varied, so thousands of people who had paid for the game simply could not +read the code and could not play it. + +there is a version of this argument still going on, forty years later, +about whether the copy or the original is the one that works.`, + }, + + 'nfo': { + title: 'the .nfo file', + from: 'the scene, in ASCII', + body: `every release came with a text file. an enormous ASCII logo, the group's +name, the date, sometimes install notes, and then a long list of greetings +to other groups. + +nobody was paid. nobody was credited anywhere that mattered. so the file +was the credit — a signature on a thing they could never publicly admit to +making, addressed to the only people who would ever see it. + +they are some of the most careful typography of the era and they exist +entirely inside a crime.`, + }, + + // ── the deep cuts: modern ────────────────────────────────────────────── + + 'heartbleed': { + title: 'heartbleed', + from: '2014', + body: `a keepalive message says: here are some bytes, send them back, and here is +how many there are. + +nobody checked that the number matched the bytes. so you sent one byte and +said it was sixty-four kilobytes, and the server sent back your byte +followed by sixty-four kilobytes of whatever happened to be next to it in +memory - passwords, keys, other people's requests. + +two years in the wild. it left no trace in any log, because asking is not +an error.`, + }, + + 'xz': { + title: 'the 500 millisecond backdoor', + from: 'March 2024', + body: `somebody spent two years being helpful. real patches, real reviews, real +politeness, until the exhausted volunteer maintaining a compression library +gratefully handed them commit access. + +then they put a back door into the build system, aimed at ssh. + +it was caught because an engineer benchmarking a database noticed his logins +were taking half a second longer than they should, and would not let it go. +the most sophisticated supply chain attack anybody has seen was defeated by +one man being annoyed about 500 milliseconds.`, + }, + + 'y2k': { + title: 'the one that did not happen', + from: '1 January 2000', + body: `two digits for the year, because storage cost money and 1999 was a long way +off, and by then somebody else would have fixed it. + +they did. hundreds of thousands of people spent several years and an +enormous amount of money going through everything line by line, and on the +night almost nothing broke. + +for which the reward has been thirty years of people using it as the +example of a panic about nothing. it is the only large-scale software +disaster that was successfully prevented, and prevention looks exactly like +there never having been a problem.`, + }, + // ── batch: the culture, the law, the plagues ─────────────────────────── + + + '2600mag': { + title: 'a quarterly, and it costs a quarter to make the call', + from: 'subscription card, 1984', + body: `we are printing it because the information exists whether or not it is +printed, and the difference between a magazine and a conspiracy is that a +magazine has an address on the back. + +the phone company knows all of this already. the only people it is being +kept from are the people paying for it.`, + }, + + 'mentor': { + title: 'somebody wrote it the week he got arrested', + from: 'reader letter, Phrack, 1986', + body: `it went round every board in the country in about nine days, which +tells you more than the text does. + +he did not say anything nobody had thought. he said that being curious about +a machine is not the same as wanting to hurt anybody, and that the reason we +are all here is that this is the one place where nobody asks what you look +like before deciding whether you are worth talking to. + +people print it out and put it on the wall. i think that is a bit much. but +i understand why they do it and so does everyone who has ever been fifteen +with a modem.`, + }, + + 'defcon': { + title: 'it started as a leaving party', + from: 'flyer, Las Vegas, 1993', + body: `a friend was moving away and the network he ran was closing, so the +plan was a party for the people on it. then he could not come, and about a +hundred showed up anyway. + +nobody organised the good parts. somebody brought a lockpicking kit and +somebody brought a scanner and the hotel had a payphone in the lobby that +by sunday could not be used by anyone for anything.`, + }, + + 'cdc': { + title: 'a text file group from a slaughterhouse in Lubbock', + from: 'group file 001, 1984', + body: `the name is a joke and the numbering is a joke and the ranks are a joke. +what is not a joke is that we are producing more original writing per month +than most of the boards that call themselves elite, and all of it is free, +and none of it is stolen. + +anyone can distribute warez. it takes about eleven seconds and a phone line. +writing something down is harder and that is the whole of the argument.`, + }, + + 'mod-lod': { + title: 'the war', + from: 'a log somebody kept, 1990-92', + body: `two crews, one out of Texas and one out of New York, and it started as +an argument about who knew more and ended with people listening to each +other on the switch. + +they were not fighting over money. there was no money. they were fighting +over whether you were the kind of person who read the manual or the kind of +person who guessed, and it went on until the federal government read the +logs and decided both answers were illegal.`, + }, + + 'sundevil': { + title: 'the eighth of may', + from: 'a board that did not come back up, 1990', + body: `forty-two machines in fourteen cities in one morning. most of the +people whose machines went out the door were never charged with anything. + +the machine is the evidence and the machine is also the board, so seizing it +does not preserve evidence, it deletes a town. nine years of message base +sitting in an evidence locker in a box with a number on it, and the users +are just gone, they have no way to reach each other, that was the board.`, + }, + + 'cfaa': { + title: 'exceeding authorised access', + from: 'somebody reading the statute at a kitchen table, 1986', + body: `nobody can tell me what the words mean. i have asked a lawyer and he +asked me what the computer said when i logged in. + +if the machine lets you in and then you look at something the owner did not +want you to look at, is that access without authorisation, or is that a +badly configured machine. the statute does not say. it was written by people +describing something they had seen in a film.`, + }, + + 'pgp': { + title: 'they classified it as a munition', + from: 'a mailing list, 1993', + body: `the export rules put strong cryptography in the same category as +guided missiles. so the source got printed as a book, because a book is +speech and speech is protected, and shipped, and typed back in by hand on +the other side. + +they made the argument that mathematics is a weapon and then had to explain +why a paperback is not.`, + }, + + 'clipper': { + title: 'key escrow', + from: 'a comment filed with the FCC, 1993', + body: `the proposal is that everyone use strong encryption, and that a copy of +every key is held by the government in two halves in two agencies, and that +this is safe because you would need both halves. + +what is being asked for is not a backdoor, they are quite clear about that, +it is a front door with a lock on it and two keyholders. the objection is +not that the keyholders are untrustworthy. the objection is that a system +with a spare key is a different system, and there is no way to build one +that only opens for the right hands.`, + }, + + 'des': { + title: 'fifty-six bits', + from: 'a hardware note, 1998', + body: `it was standardised at fifty-six and people said at the time that +fifty-six was chosen because it was strong enough for a company and weak +enough for a government, and for twenty years that was an accusation you +could not prove. + +we built the machine for a quarter of a million dollars out of custom chips +that only do one thing. it went through the whole keyspace and found the key +in fifty-six hours. the point was never the key. the point was to end the +argument about whether it could be done by publishing the receipt.`, + }, + + 'munitions': { + title: 'the shirt is a munition', + from: 'a conference floor, 1995', + body: `three lines of perl on the front and a machine-readable barcode of the +same three lines underneath, and a line of small print explaining that +wearing this out of the country may require a licence. + +it is a very good joke, and like all very good jokes about the law it works +by taking the law completely seriously.`, + }, + + 'eternal-september': { + title: 'september never ended', + from: 'a post nobody replied to, 1993', + body: `every year the new students arrive in september, ask everything that +has already been asked, and by october somebody has explained it to them and +they are one of us. + +this year a commercial service connected its entire membership at once and +they are not going to stop arriving. there is no october coming. the mistake +we made was assuming the mechanism was the explaining. the mechanism was +that there were more of us than of them.`, + }, + + 'great-renaming': { + title: 'they moved everything', + from: 'a sysadmin, 1987', + body: `net dot everything became seven hierarchies with sensible names and the +backbone sites got to decide what went in them, which was the actual change, +the names were incidental. + +within a year somebody built a hierarchy where nobody decides. it carries +the things the backbone would not carry and the recipes, and it is the +biggest thing on the network. you cannot vote a topic out of existence, you +can only find out where it went.`, + }, + + 'nsfnet': { + title: 'they switched off the backbone', + from: 'an operations note, 30 April 1995', + body: `the acceptable use policy said no commercial traffic, and for years the +argument about that was theological. this morning the backbone it applied to +was decommissioned and the argument was over, not won. + +nothing broke. that is the part i want on the record. the thing was built so +that no piece of it was the thing, so you can take out the middle of it and +the traffic simply goes another way and nobody notices, which is either the +finest engineering of the century or the reason none of us will ever be +able to explain to anybody what we did.`, + }, + + 'elk-cloner': { + title: 'a fifteen year old and a boot sector', + from: 'a school computer room, 1982', + body: `it lives in the boot block, it copies itself onto any disk you put in +after, and on the fiftieth boot it prints a poem about itself and then +carries on as normal. + +it does not delete anything. he wrote it to annoy his friends, which it did, +for about two years, in one school, before anybody had a word for it.`, + }, + + 'brain': { + title: 'their real address was in it', + from: 'a disassembly, 1987', + body: `two brothers in Lahore who sell medical software, and the copy +protection they wrote for it puts their shop name, their street, and three +phone numbers in the boot sector. + +they were not hiding. they thought only people who had pirated the disk +would ever see it, and they wanted those people to know who to call. people +did call. for years. from countries they had never heard of, about disks +they had never touched, because the thing kept going long after it stopped +being about them.`, + }, + + 'michelangelo': { + title: 'the sixth of march', + from: 'a support desk, 1992', + body: `we were told five million machines. the number came from one man on +television and it was repeated for six weeks until it was a fact. + +on the day itself we found it on maybe a dozen. i spent a fortnight scanning +disks for people who had never previously known a disk could be scanned, and +the software to do it sold extremely well. i am not saying the thing was not +real. i am saying i now know exactly how afraid a person can be made about +a date.`, + }, + + 'iloveyou': { + title: 'nobody could charge anybody', + from: 'a wire report, May 2000', + body: `it arrives from someone who knows you, because it arrived from their +address book, and the attachment ends in txt if your machine has been +helpfully hiding the real extension, which by default it has. + +they found who wrote it inside a week. then the prosecutors found there was +no law in the country covering it, and there was nothing to charge him with, +and he went home. the law arrived two months later and could not be applied +backwards.`, + }, + + 'slammer': { + title: 'three hundred and seventy-six bytes', + from: 'a traffic graph, 25 January 2003', + body: `no file. no disk. one UDP packet that fits in one datagram, which means +no handshake, which means it does not wait to find out whether anyone is +there, it just goes. + +it doubled every eight and a half seconds. it had infected effectively +everything it could reach in under ten minutes, which is faster than any +human being on earth could be woken up and told. the machines did not fall +over because it hurt them. they fell over because it kept talking.`, + }, + + 'stuxnet': { + title: 'it wanted a particular room', + from: 'analyst notes, 2010', + body: `four unpublished holes in one thing. you do not spend four on a crime. +you spend four when there is exactly one machine on earth you need and no +second attempt. + +it walks through every computer it can reach and does nothing to any of +them. it is looking for a controller running a particular drive at a +particular frequency in a particular count, and if it does not find that, it +stays quiet. when it did find it, it changed the speed slowly, and fed the +operators the readings from an hour ago. + +it is the first one that was never about the computers.`, + }, + + 'mirai': { + title: 'sixty-one passwords', + from: 'a takedown post-mortem, 2016', + body: `no exploit. no overflow. a list of sixty-one factory logins, tried +against everything with a port open, and a very large number of cameras and +recorders that have a login the owner has never once seen, because the +owner interacts with the camera through an app. + +you cannot patch them. there is no update. the manufacturer does not exist +anymore, the brand was a sticker. the fix is a landfill.`, + }, + + 'wannacry': { + title: 'somebody registered the domain', + from: 'a hospital IT office, May 2017', + body: `the hole was found by a government, kept, lost, and published by +somebody else. a month after the patch shipped it went through everything +that had not applied it, which was everything with a machine that cannot be +rebooted during clinic hours. + +it was stopped that afternoon because a researcher noticed it checked for a +nonsense domain before running, assumed it was a sandbox check, and +registered the domain for about ten dollars. that was the kill switch. it +was not meant to be one.`, + }, + + 'hayes': { + title: 'AT', + from: 'a modem manual, 1981', + body: `attention. every command starts with those two letters so the modem can +work out your speed from the shape of them before it knows anything else. + +the hard part was never the commands. the hard part is that the same wire +carries what you type to the modem and what you type to the far end, so the +modem needs to know which you meant. three pluses, with a second of silence +either side. the silence is the actual invention. anything can send three +pluses. only a person who has stopped typing can send nothing at all first.`, + }, + + 'coupler': { + title: 'why it is a rubber cup and not a plug', + from: 'a rental agreement, 1972', + body: `it is illegal to attach a foreign device to the network electrically. +that is the whole reason. the handset sits in two rubber cups and we talk to +it with sound, through the air, like a person would, because there is no +rule against making a noise into a telephone. + +so the coupler exists because of a sentence in a tariff. every hiss you have +ever heard on one of these is the sound of a legal argument being routed +around.`, + }, + + 'vt100': { + title: 'escape, bracket, two, J', + from: 'a terminal reference card, 1978', + body: `clears the screen. escape bracket H puts the cursor home. it is a +character stream that occasionally means move rather than print, and the +escape character is how the stream tells you which. + +there is no reason any machine made after this decade should speak it. every +one of them does. we are all still emulating a piece of glass and metal that +weighed twenty kilograms because everybody wrote to it and nobody wants to +be the one who breaks the writing.`, + }, + + 'uucp': { + title: 'bang paths', + from: 'an address written on a napkin, 1984', + body: `you do not have an address. you have directions. you name every machine +between here and there and the mail is copied from each one to the next +overnight over a phone call, and if you get one hop wrong the whole thing +comes back a week later. + +which means to write to someone you must first know where you both are +relative to the big sites. everybody memorised the same three hub names. the +network had a shape and you could hold it in your head, and that has never +been true again since.`, + }, + + 'finger': { + title: 'the plan file', + from: 'a machine at a university, 1987', + body: `it will tell anyone on the network your name, your terminal, how long +you have been idle, and then it prints a file out of your home directory +that you can put anything you like in. + +people put jokes in it. people put their thesis progress in it. somebody +wired the drinks machine to the network so you can finger it and find out +which column is cold. none of this was designed. the protocol had a hole +shaped like a person in it and everybody filled it in.`, + }, + + 'sendmail-debug': { + title: 'it was left switched on', + from: 'a post-mortem, November 1988', + body: `the mailer has a debug mode where the recipient can be a command +instead of a person. it is there so you can test a machine you are sitting +in front of. it shipped enabled, in the binary, on everything, for years. + +so you could mail a program to a computer and it would run it, and that is +not an exploit, that is documented behaviour. the hole was not the code. the +hole was that nobody who compiled it ever asked what it was for.`, + }, + + 'fork-bomb': { + title: 'it is thirteen characters', + from: 'somebody who did it by accident, 1990', + body: `a function that calls itself twice and backgrounds both. it is not +clever and it is not an attack, it is just what a machine does when asked to +do a small reasonable thing an unlimited number of times. + +there is no bug. every part of this is the system working. the limit that +would stop it exists and has always existed and is set, on this machine, to +a number somebody chose in 1979 for a machine with far fewer users.`, + }, + // ── batch: the scene, the iron, the first convictions ────────────────── + + 'courier': { + title: 'zero day means today', + from: 'a group file, 1993', + body: `it is not about using the thing. half of us do not even install it. +it is about the gap between the shop opening and it being on the boards, and +that gap is measured in hours, and somebody is timing it. + +the couriers are not crackers. they move it. they have accounts on nine +boards on three continents and a phone bill that would frighten a small +business, and their entire reputation rests on being first by nine minutes.`, + }, + + 'cracktro': { + title: 'the bit before the game', + from: 'a disk that came from a friend of a friend, 1989', + body: `you put the disk in and before anything else there is a scroller, a +tune, and a logo with more work in it than the game has in its title screen. + +it is a signature. the protection is gone and the person who removed it has +signed the wall on the way out, in a demo that fits in the space where the +protection used to be, and greeted forty people you will never meet.`, + }, + + 'keygen-music': { + title: 'why does it have a tune', + from: 'a message base, 1997', + body: `it is a tracker module, thirty kilobytes, four channels, and it loops +until you close the window. + +nobody needs it. it is there because the person who wrote the generator is +the same kind of person who wrote the intro on the disk, and because leaving +in silence would be like leaving a room without saying anything.`, + }, + + 'pipes': { + title: 'the vertical bar', + from: 'a memo somebody kept for years, 1973', + body: `he had been asking for it for a while, on the grounds that if a program +is a garden hose it ought to be possible to screw one onto another. + +the whole thing went in overnight and by morning every command in the system +had to be rewritten to read from the input and write to the output and shut +up about it. that is the rule that made everything since possible: do one +thing, and do not print a banner.`, + }, + + 'everything-file': { + title: 'the disk is a file, the terminal is a file, the memory is a file', + from: 'a lecture nobody wrote down, 1975', + body: `there is no special call to read a tape. it is open, read, write, close, +and if the thing on the end of it is a spinning platter or a printer or +another human being at a keyboard, that is not the program business. + +it means a tool written by somebody who never heard of your hardware works +on your hardware. this is the single most valuable idea in the whole system +and it is usually explained as a syntax convenience.`, + }, + + 'devnull': { + title: 'the bit bucket', + from: 'a manual page, 1974', + body: `a device that accepts everything written to it and reports success, and +returns nothing at all when read. + +it is not a deletion. deletion is an operation with consequences. this is a +place that is always empty and never full, and every operator learns early +that having somewhere to point the output is the difference between a job +that runs and a disk that fills up at four in the morning.`, + }, + + 'editors': { + title: 'why the commands are one letter', + from: 'an argument that will never end, 1979', + body: `it was written over a three hundred baud line. at three hundred baud +you can watch the screen redraw and you would rather it did not, so you do +not redraw, and every command is one keystroke because every keystroke is +paid for in tenths of a second. + +people now argue about it as taste. it is not taste. it is the shape of a +slow line, preserved, and defended by people who have never seen one.`, + }, + + 'man-page': { + title: 'read the section at the bottom', + from: 'a note to a new user, 1985', + body: `name, synopsis, description, options, files, see also, diagnostics, +bugs. everybody stops at options. + +the bugs section is the honest part of the entire operating system. somebody +sat down and wrote out, in the documentation, in the shipping product, the +list of ways their own program is wrong. no industry since has managed it.`, + }, + + 'hosts-equiv': { + title: 'trust by name', + from: 'a security review, 1988', + body: `the file lists machines. if a login comes from a machine in the file +claiming to be a user who exists here, it is that user, no password. + +the entire model rests on a machine being able to say who it is and be +believed. the address in the packet is written by the sender. we built a +continent of trust on a field the untrusted party fills in.`, + }, + + 'punch-card': { + title: 'eighty columns', + from: 'a keypunch room, 1968', + body: `the card is eighty columns because that is what fits on a card that fits +in a hand, and the hand and the card are older than any of this. + +your terminal is eighty columns. your printer is eighty columns. the file +you are reading wraps at eighty. long after the last card is punched, every +screen on earth will still be the width of a piece of cardboard somebody +sized for a census in the last century.`, + }, + + 'core-memory': { + title: 'it was woven', + from: 'a factory floor, 1964', + body: `little rings of ferrite on a grid of wires, and every one of them was +threaded by a person, by hand, under a lens. a machine with sixteen +kilowords of it contains a hundred and thirty thousand rings and somebody +put a wire through each. + +it remembers with the power off, because the ring is either magnetised one +way or the other and has no opinion about electricity. when a program dies +and the system writes out what was in memory, that is what it is writing. +that is what the word means. it is the actual core.`, + }, + + 'pdp11': { + title: 'the machine the language is shaped like', + from: 'a lab, 1972', + body: `the reason the language has increment and decrement operators is that +this machine has an instruction that does exactly that as part of addressing +memory, and it seemed a shame not to be able to say it. + +people will use that language on machines that do not exist yet, on hardware +with nothing in common with this, and the operators will still be there, +still shaped like an instruction on a machine in a room in New Jersey.`, + }, + + 'tape-load': { + title: 'the noise is the program', + from: 'a bedroom, 1983', + body: `four minutes of screeching out of a cassette and stripes running down +the border, and if somebody opens the door and the plug wobbles you start +again. + +it is not a sound that represents the data. it is the data. you are hearing +the bytes. if you rewind to the right spot and listen you can hear the +loading screen arrive before you can see it.`, + }, + + 'altair': { + title: 'you enter the program with switches', + from: 'a kit that arrived in a box, 1975', + body: `no keyboard, no screen. a row of switches for the bits, a button to +deposit, and lights. to load anything you set eight switches, deposit, set +eight more, deposit, and if you lose count you begin again. + +the first program most people ran made the lights chase each other. it took +an evening. everyone who did it says the same thing, which is that it was +the last time they knew, absolutely, everything that was happening.`, + }, + + 'homebrew': { + title: 'the open letter', + from: 'a meeting in a garage in Menlo Park, 1976', + body: `the club works by everybody bringing what they have and taking what +they need, and half the machines in the room exist because somebody +photocopied somebody else schematic. + +then a letter goes round from a young man whose interpreter has been copied +more than it has been bought, and he asks who can afford to do professional +work for nothing. both sides of the next fifty years are in that room and +neither of them is wrong.`, + }, + + 'first-web': { + title: 'do not power down', + from: 'a label on a machine at CERN, 1991', + body: `it is one workstation under a desk and it is the only one, so somebody +has written on it by hand, in marker, that this machine is a server and +please do not switch it off. + +that is the whole state of the art. the entire thing is a piece of tape on +a box in Switzerland and one person walking past who might unplug it to +charge something.`, + }, + + 'cookies': { + title: 'a protocol with no memory', + from: 'an engineering note, 1994', + body: `every request arrives as if it were the first one. the protocol was +designed that way on purpose, because a server that remembers everybody is a +server that falls over. + +so we hand the browser a small opaque thing and ask it to give it back next +time, and now the shop can have a basket. that is what it was for. a basket. +i want that written down somewhere before anybody does anything else with +it.`, + }, + + 'sqli': { + title: 'the quote closes early', + from: 'a write-up, 1998', + body: `the program builds a sentence out of your answer and hands the sentence +to the database. so you answer with something that finishes the sentence +early and starts a new one. + +the database is not fooled. the database is doing exactly what it was told, +by a program that could not tell the difference between what it meant to say +and what somebody typed into a form. same hole as the sled, same hole as the +mailer, forever: data walked in and was read as instructions.`, + }, + + 'xss': { + title: 'the page says it, so the browser believes it', + from: 'a bug report, 2000', + body: `you put a script in the comment box. the site prints your comment on the +page. now everyone reading the page runs your script, and their browser has +no reason to doubt it, because it arrived inside the site. + +the trust is not in you. the trust is in the address bar, and you are +writing from inside the address bar.`, + }, + + 'phish': { + title: 'the word comes from a program that fished for accounts', + from: 'a warning posted to a service, 1996', + body: `nobody breaks anything. somebody sends a message that looks like it came +from the company, asking you to confirm your billing, and enough people +confirm. + +there is no patch. the vulnerability is that a person can be sent a letter, +and it has been open since the invention of letters.`, + }, + + 'salt': { + title: 'why two people with the same password get different hashes', + from: 'a paper on password security, 1979', + body: `without it, you hash the dictionary once and compare against everybody +at the same time. one pass, the whole file. + +so a small random value goes in with the password and is stored in the clear +beside it. it is not secret. it does not need to be. it just means the work +you did to attack one account buys you nothing at all against the next one, +and that is the entire trick.`, + }, + + 'captain-zap': { + title: 'he moved the clocks', + from: 'a court report, 1981', + body: `he did not steal calls. he went into the billing system and changed what +time it thought it was, so that the whole country got the cheap overnight +rate in the middle of the afternoon, and the expensive rate at midnight. + +the first felony conviction of its kind. the interesting part is not the +sentence, it is that the network had no idea what time it was and had to be +told, and whoever told it was believed.`, + }, + + 'poulsen': { + title: 'caller one hundred and two', + from: 'a radio station, Los Angeles, 1990', + body: `the station gives a car to the hundred and second caller. so you take +the station twenty-five lines, hold them all, and let exactly one call +through, which is yours. + +it is not a hack of a radio contest. it is a hack of the exchange, and the +prize is a Porsche. the FBI spent a year on it and a television programme +put his face on air, and the phone lines the show gave out for tips went +down during the broadcast.`, + }, + + 'the-414s': { + title: 'a boy on the cover of a magazine', + from: 'Milwaukee, 1983', + body: `they named themselves after the area code. they got into a cancer +hospital and a weapons lab, mostly by trying the password the manual +suggests and finding that nobody had changed it. + +what they broke was not the machines. what they broke was the idea that +these things were far away and looked after by serious people. congress held +hearings and screened a film.`, + }, + + 'wargames': { + title: 'that is where the word comes from', + from: 'a cinema, 1983', + body: `he sets the machine dialling every number in an exchange overnight and +writes down which ones answer with a carrier. now everybody does it, and +everybody calls it after the film, and the film called it nothing at all. + +for about a year afterwards, every school in the country had somebody in it +trying the same thing, and about a third of them found something, which +tells you the film was not exaggerating, it was reporting.`, + }, + + 'ccc-btx': { + title: 'they gave it back at a press conference', + from: 'Hamburg, 1984', + body: `the videotex system, which the post office says is secure. they take a +hundred and thirty-five thousand marks out of a bank overnight, one small +page request at a time, running all night. + +then in the morning they hold a press conference, hand every pfennig back, +and explain the flaw. that is the whole point and it is the reason anybody +listened. the demonstration is worthless if you keep the money.`, + }, + + 'payphone': { + title: 'the box knows what you put in it by the sound', + from: 'a service manual, 1979', + body: `the coin falls past a hammer and rings a bell. one ring for the small +coin, two for the middle, a gong for the big one, and the operator, or later +the equipment, hears it down the same line as your voice. + +there is no sensor reporting to a computer. there is a bell, and a +microphone, and a great deal of faith.`, + }, + + 'cocot': { + title: 'a payphone that belongs to a petrol station', + from: 'a field note, 1986', + body: `after the breakup anyone can own the box. so the box is now made as +cheaply as a box can be made, and it does its own dialling and its own +billing and its own deciding. + +which means the intelligence is out on the pavement instead of in the +exchange, in a metal case, on a wall, in the dark, owned by somebody whose +main business is selling petrol. every one of them is a small computer that +nobody is watching.`, + }, + + 'wats': { + title: 'the extender', + from: 'a scan list, 1985', + body: `a company has a toll-free number and behind it a code, and if you know +the code you get an outside line billed to the company. + +the codes are four digits. the number is free to call. you can sit there all +night, and the machine can sit there all night without you, and the only +thing standing between the two of them is a company that thought four digits +was a lot.`, + }, + + 'silver-box': { + title: 'the fourth column', + from: 'a modification guide, 1980', + body: `the keypad is a grid. three columns of buttons and four rows, and the +grid has four columns. the fourth one exists in the standard, has always +existed, and has no buttons on any telephone you have ever seen. + +the tones are A, B, C and D and they are for the military network, where +they set the precedence of a call, up to and including the level that hangs +up on everyone else so a general can talk. the phone in your kitchen speaks +the same language. it just was not given the keys.`, + }, + + 'operator': { + title: 'she is not a security system', + from: 'a training memo somebody was not supposed to keep, 1974', + body: `she is measured on calls per hour. she has a supervisor and a light on +her console and a target, and the fastest way to clear a call is to help. + +so a caller who is confident, who uses the right word for the thing, who +sounds tired and slightly bored, gets what he asks for, because arguing with +him would take ninety seconds and helping him takes twenty. that is not a +flaw in her. that is what she was hired for.`, + }, + // ── batch: naming things, the tools, the bill coming due ─────────────── + + 'dns': { + title: 'before this, everybody downloaded the same file', + from: 'a transition note, 1983', + body: `there was one text file with every host on the network in it. you +fetched it from one machine in California, by hand, when you remembered, +and if somebody added a machine on tuesday you found out on friday. + +it worked until it did not, and the thing that killed it was not size, it +was that two people wanted the same name and there was one file and one +woman keeping it and no way to say no politely at scale. + +so the names got a shape, and the shape means nobody has to hold the whole +list. you only ever have to know who to ask next.`, + }, + + 'root-servers': { + title: 'why there are thirteen', + from: 'an operations mailing list, 1995', + body: `not thirteen for luck and not thirteen for redundancy. thirteen because +the answer listing them has to fit in one datagram of five hundred and +twelve bytes, and fourteen does not fit. + +the top of the naming system for the entire species is the size it is +because of a number somebody picked for a packet.`, + }, + + 'whois': { + title: 'you could just ask who somebody was', + from: 'a directory, 1982', + body: `type the command and a name and it tells you the person, their machine, +their office and their telephone number, because when this was written +everybody on the network was at an institution and being findable was the +point. + +there are a few thousand people. the whole directory is one file kept by one +person and if your entry is wrong you write to her and she fixes it.`, + }, + + 'ping': { + title: 'he named it after sonar', + from: 'a note attached to the source, December 1983', + body: `you send a noise into the dark and you time how long it takes to come +back, and that is all the information there is: something is out there, and +it is that far away. + +it took an evening to write. it is on every machine that has ever spoken +this protocol and it will outlive everything else either of us does. the +whole diagnostic is one question, which is are you there, asked of a machine +that has no obligation to answer.`, + }, + + 'tcpdump': { + title: 'the wire says what it says', + from: 'a debugging session, 1988', + body: `the program insists it sent it. the far end insists it never came. one +of them is wrong and neither of them can tell you which. + +so you stop asking the programs. you put the card in the mode where it keeps +everything, not just the frames addressed to you, and you read what actually +went past. there is no argument after that. this is the only tool in the +whole box that cannot be lied to.`, + }, + + 'nmap': { + title: 'the machine answers differently when it says no', + from: 'an article, 1997', + body: `a closed port and a filtered port are both a refusal, but they are not +the same refusal, and the difference is in the flags. + +so you knock on all of them and write down the shape of every no. and if +you compare the exact wording of the refusals against a list, the machine +tells you what it is running without ever intending to say anything.`, + }, + + 'telnet': { + title: 'it is all in the clear', + from: 'a capture somebody did not mean to take, 1992', + body: `the password goes across as characters. not hashed, not wrapped, not +obscured. characters, one per packet, because it echoes as you type. + +anybody on any machine between here and there sees it. this is not a bug and +there is no fix planned, because the network was built by people who could +all see each other and the idea that a stranger might be on it was not an +oversight, it was not a thought anyone had had.`, + }, + + 'anon-ftp': { + title: 'the password is your email address, as a courtesy', + from: 'a login banner, 1990', + body: `log in as anonymous. it will ask for a password. put your address in, +not because it is checked, but because it is polite and because the person +paying for this line likes to know who is using it. + +and people did. for years. nearly everybody typed a real address into a box +that would have accepted anything at all.`, + }, + + 'gopher': { + title: 'it was better, and then it was licensed', + from: 'a comparison written for a department, 1993', + body: `menus. clean, fast, hierarchical, no markup, and any librarian can put a +collection up in an afternoon. against a thing from Switzerland where the +links go sideways instead of down and every page looks like whatever +somebody felt like. + +then the university announces a licence fee for commercial use, and two +months later CERN puts the other one in the public domain, and that is the +whole story. the technically better system asked for money in the same +quarter the worse one became free.`, + }, + + 'shellshock': { + title: 'the code was twenty-five years old', + from: 'an advisory, September 2014', + body: `you can put a function in an environment variable. the shell parses it +when it starts. it does not stop parsing at the end of the function. + +so anything that passes a string from a stranger into the environment and +then runs a shell, which is roughly every web server on earth built before +about 2005, runs the stranger commands. the line responsible was written in +1989 and read by thousands of people and never once by somebody asking that +particular question.`, + }, + + 'meltdown': { + title: 'the guess left a mark', + from: 'a paper, January 2018', + body: `the processor runs ahead of itself, guessing, and throws the work away +if the guess was wrong. the results are discarded properly. the timing is +not. + +so the guess it made about memory it was not allowed to read is undone, but +the cache is a fraction faster afterwards, and a fraction is a measurement. +the fix costs everybody a few percent forever, on hardware that cannot be +changed, because the mistake is in the silicon and the silicon is in +everything.`, + }, + + 'eternalblue': { + title: 'somebody had it for years, and then lost it', + from: 'a timeline written afterwards, 2017', + body: `a hole in a file sharing protocol from 1983 that everything still +speaks for compatibility with things nobody runs. + +an agency found it and kept it, which is a decision with a case to be made +for it. then the tool escaped, and was published, and within two months it +had been used against hospitals. the argument about whether to hoard a hole +assumes you can hold on to it.`, + }, + + 'equifax': { + title: 'the patch had been out for two months', + from: 'a congressional exhibit, 2017', + body: `a hundred and forty-seven million people, none of whom were customers, +none of whom agreed to anything, none of whom can leave. + +there was no clever attack. there was a published flaw in a common library, +a fix available in March, a scanner that was pointed at the wrong subnet, +and a certificate on the monitoring that had expired ten months earlier so +nobody saw the traffic going out.`, + }, + + 'solarwinds': { + title: 'they did not break in, they got built in', + from: 'an incident report, 2020', + body: `nobody attacked the eighteen thousand organisations. somebody attacked +the machine that compiles the software the eighteen thousand organisations +install on purpose, signed, from the vendor, through the front door, +during a maintenance window. + +every security model in the building assumes the update is the safe part.`, + }, + + 'aids-trojan': { + title: 'twenty thousand floppies in the post', + from: 'a conference mailing list, 1989', + body: `it went to people who had attended a health conference. it installs, it +counts your boots, and at ninety it scrambles the names of your files and +prints an invoice asking for a hundred and eighty-nine dollars to a box +number in Panama. + +there was no network involved. he posted it. the encryption was weak and +people got their disks back, but the shape of the thing is complete on the +first try: take something they cannot replace, and sell it back.`, + }, + + 'tor': { + title: 'you cannot hide in an empty room', + from: 'a design discussion, 2004', + body: `the routing hides who is talking to whom by wrapping it in layers and +passing it between volunteers, and no single one of them knows both ends. + +but if only spies use it, then using it means you are a spy, and the traffic +is anonymous while the user is not. so the thing only works if the people +who need it least use it constantly, which is the strangest requirement any +security system has ever had.`, + }, + + 'jargon-file': { + title: 'they wrote down their own words', + from: 'a lab machine at Stanford, 1975', + body: `a dictionary of the slang of about four hundred people in three +buildings, kept as a file, added to by anyone who thought of one. + +it is not a joke document, or it is, but it is also the only surviving +record of how these people talked to each other before there was any money +in it, and half the entries are about elegance and the other half are about +somebody specific being wrong in 1973.`, + }, + + 'rtfm': { + title: 'read the manual', + from: 'a reply to a question, any year', + body: `it is rude and it is nearly always correct, and the two facts have +nothing to do with each other. + +the person answering has typed the answer forty times. the person asking has +never asked before. both of these are true and the whole culture of the next +thirty years is built in the gap between them.`, + }, + + 'bofh': { + title: 'a story about the person who has root', + from: 'a newsgroup, 1992', + body: `he deletes accounts for tone of voice. he explains outages with words +the manager will not admit to not knowing. it is a joke. + +it is funny because everybody reading it has been on one side of it and +suspects they have been on the other. the operator has absolute power over +everything you have ever typed, and the only thing between you and that is +whether he is having a good week.`, + }, + + 'the-well': { + title: 'you own your own words', + from: 'Sausalito, 1985', + body: `you pay to be here and you post under your real name and the rule at the +top is that what you write is yours and the consequences are also yours. + +it is small on purpose. people meet in person. it is the opposite argument +to every board in this file, and it is the only one of them where the +argument was ever written down as a rule rather than just being how things +happened to go.`, + }, + + 'swartz': { + title: 'he downloaded papers', + from: 'a note written by somebody who knew him, 2013', + body: `publicly funded research, behind a paywall, downloaded in bulk from a +network he was entitled to use, from a wiring closet he was not. + +the library did not want to press it. the publisher did not want to press it. +thirteen felony counts anyway, under a statute written in 1986 by people +describing something they had seen in a film, carrying more years than the +statute carries for most things done with hands. + +he was twenty-six. the law did not change afterwards.`, + }, + + 'dmca': { + title: 'the lock is protected, not the thing behind it', + from: 'a lawyer explaining it badly, 1998', + body: `it is not about copying. copying was already covered. this makes it an +offence to get around the mechanism, and separately an offence to tell +anybody how. + +so a fact about how a device works becomes something you can be sued for +saying, and the first people it is used against are researchers publishing +a flaw. you may own the thing. you may not know how it works.`, + }, + + 'eff': { + title: 'somebody had to be on the other side', + from: 'a founding statement, 1990', + body: `after the raids, it turned out that when they come for a bulletin board +there is nobody whose job it is to show up. + +so a few people with money from software put up the money for lawyers, +because the actual problem was not that the law was wrong, it was that on +one side there was a federal agency and on the other side there was a +nineteen year old and his mother.`, + }, + + 'bernstein': { + title: 'source code is speech', + from: 'a filing, 1995', + body: `he wanted to publish an encryption algorithm. publishing it counted as +exporting a weapon, so he needed a licence, so he sued. + +the argument the court accepted is the important part: source code is how +these people explain an idea to one another. restricting who may be told is +not export control, it is telling a mathematician who he may talk to.`, + }, + + 'busy-signal': { + title: 'sixty per minute', + from: 'a switching manual, 1975', + body: `the tone is the same two notes as the dial tone, interrupted sixty times +a minute. reorder, which means the network itself is full, is twice as fast, +and every operator can tell them apart in half a second without thinking. + +one of them means a person is already talking. the other means the country +is. they sound almost the same on purpose, because in both cases what you +have to do is hang up and try again.`, + }, + + 'call-waiting': { + title: 'star seven zero', + from: 'a note taped to a modem, 1994', + body: `if somebody rings while you are online, the exchange politely puts a +click on the line to let you know, and the click destroys the carrier, and +forty minutes of download becomes nothing. + +so you dial star seven zero first, every time, to switch off the feature you +are paying extra for, so that the thing you are actually doing will work. +half the households in the country have this written on a scrap of paper +next to the phone.`, + }, + + 'second-line': { + title: 'the argument that ends with a second line', + from: 'a household, 1996', + body: `it costs about fifteen a month and it ends a war. nobody picks up the +phone mid-transfer, nobody misses a call, and the child stops being the +reason nobody can ring the house after six. + +it is the only technical decision in the history of the family that everyone +agreed on immediately, and it was made for social reasons entirely.`, + }, + + 'isdn': { + title: 'it was ready twenty years early and priced for a business', + from: 'a sales sheet, 1993', + body: `sixty-four kilobits, digital end to end, connects in under a second +because there is no negotiation, no training, no handshake. it just is. + +it costs what a company pays and it is sold by people who have never sold to +a house. the technology was finished and waiting the entire time everybody +was screaming at each other about baud rates.`, + }, + + 'v90': { + title: 'why the fast direction is only one direction', + from: 'a standards summary, 1998', + body: `fifty-six down, thirty-three up, and everybody assumes it is a +limitation of the equipment. it is not. + +the network is already digital in the middle. the only analogue part is the +copper to your house. going down, there is one conversion; going up, there +are two, and the second one adds noise you cannot subtract. the asymmetry +is not a business decision. it is the number of times the signal changes +what kind of thing it is.`, + }, + + 'no-carrier': { + title: 'two words', + from: 'the last line of a great many logs', + body: `it does not mean an error. it means the tone that was carrying you is no +longer present, which is the same thing the modem would say if you had +finished properly and hung up. + +there is no field for why. the line is either up or it is not, and when it +is not, the last thing on your screen is somebody in the middle of a +sentence, and you will never find out what the rest of it was.`, + }, + + 'toctou': { + title: 'between the check and the use', + from: 'a bug report, 1993', + body: `the program asks whether you are allowed to write to the file. the +answer is yes. then it writes to the file. + +in between those two moments, which are not the same moment, the file can +be replaced with a link to something else, and the second half of the +sentence is now about a different file than the first half was. + +nothing in the program is wrong. it is only that it assumed the world holds +still while it thinks, and the world has other users.`, + }, }; // levels can still pass their own title/body inline; this is the shared store diff --git a/tools/philecheck.mjs b/tools/philecheck.mjs new file mode 100644 index 0000000..e55bda0 --- /dev/null +++ b/tools/philecheck.mjs @@ -0,0 +1,29 @@ +// duplicate ids in an object literal overwrite silently. this is the only place +// that would ever notice. run it after writing philes. +import { PHILES, TOTAL } from '../src/philes.js'; +import { readFileSync, readdirSync } from 'fs'; + +const src = readFileSync(new URL('../src/philes.js', import.meta.url), 'utf8'); +const ids = [...src.matchAll(/^ {2}'([a-z0-9-]+)': \{/gm)].map(m => m[1]); +const dupes = ids.filter((id, i) => ids.indexOf(id) !== i); + +const used = new Set(); +// ids live in a lot of shapes now: phile('x'), phile: 'x', and bare entries in +// data tables. so: does the quoted id appear anywhere outside philes.js at all. +for (const d of ['../src/', '../src/levels/']) { + const dir = new URL(d, import.meta.url); + for (const f of readdirSync(dir)) { + if (!f.endsWith('.js') || f === 'philes.js') continue; + const t = readFileSync(new URL(f, dir), 'utf8'); + for (const id of ids) if (t.includes(`'${id}'`)) used.add(id); + } +} + +const missing = [...used].filter(id => !PHILES[id]); +const orphan = ids.filter(id => !used.has(id)); + +console.log(`${ids.length}/${TOTAL} written · ${ids.length - orphan.length} findable`); +if (dupes.length) console.log('DUPLICATE IDS (silently overwritten):', dupes.join(' ')); +if (missing.length) console.log('REFERENCED BUT NOT WRITTEN:', missing.join(' ')); +if (orphan.length) console.log(`not yet placed in a level (${orphan.length}):`, orphan.join(' ')); +process.exit(dupes.length || missing.length ? 1 : 0);