diff --git a/.gitignore b/.gitignore index 879add9b..9b9e01da 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,11 @@ dist/hacking/ # faces under public/cards/security/ are the committed record. # node scripts/security-pros.mjs all dist/security/ + +# Reference photographs the portraits are painted from. NOT committed: several are +# ordinary all-rights-reserved bio photos used with permission as references, and +# redistributing them in the repo is a different act from painting from them. +# data/security-references.json records where each came from, and +# node scripts/security-refs.mjs fetch +# re-downloads the lot. +assets/references/ diff --git a/README.md b/README.md index f04580ea..8b3254f3 100644 --- a/README.md +++ b/README.md @@ -87,8 +87,30 @@ pnpm security all 1 5 12 # ...only these card numbers pnpm security render # re-render faces from the template, no API calls ``` -Same stages and the same resumability as Series Two, with one deliberate -difference: **`all` does not run `enhance`.** On Series Two that image-to-image +Portraits are painted **from a real photograph of the person**: + +```bash +node scripts/security-refs.mjs resolve # find + verify a reference photo per card +node scripts/security-refs.mjs fetch # download them (gitignored) +node scripts/security-refs.mjs report # who still has no face +``` + +The first art pass generated faces from text prompts alone and produced an +invented stranger for every name. That art was withdrawn. Generation is now +image-to-image off `assets/references/security-pros/`, and **a card with no +verified reference photo gets no face** — the front renders a plate saying so. +40 of 50 currently have one; `data/security-references.json` records the source, +photographer and licence for each, and the card page prints the credit. + +Identity is verified, not assumed. A name search returns humans, not the right +human: "Michael Howard" resolves to the British politician, "Robert M. Lee" to a +Confederate general, "Mark Dowd" to a Liverpool councillor. The resolver requires +a surname match, rejects pre-1950 photographs, requires a Commons filename to +*open* with the person's full name, and carries an explicit reject list with +reasons. Everything it accepts was still checked by eye. + +The same stages and resumability as Series Two, with one deliberate difference: +**`all` does not run `enhance`.** On Series Two that image-to-image finish pass rewrote text on the card faces despite the prompt forbidding it — it fabricated quotes attributed to living people, invented stat panels that were not on the card, and misspelled a name. Nothing in the pipeline catches that, because diff --git a/assets/portraits/security-pros/card_001.png b/assets/portraits/security-pros/card_001.png index a984533f..d904bce0 100644 Binary files a/assets/portraits/security-pros/card_001.png and b/assets/portraits/security-pros/card_001.png differ diff --git a/assets/portraits/security-pros/card_002.png b/assets/portraits/security-pros/card_002.png index 33e3bf0e..c80a3625 100644 Binary files a/assets/portraits/security-pros/card_002.png and b/assets/portraits/security-pros/card_002.png differ diff --git a/assets/portraits/security-pros/card_003.png b/assets/portraits/security-pros/card_003.png index 99951f1a..08b95b2b 100644 Binary files a/assets/portraits/security-pros/card_003.png and b/assets/portraits/security-pros/card_003.png differ diff --git a/assets/portraits/security-pros/card_004.png b/assets/portraits/security-pros/card_004.png index 43e7367f..08cedcd7 100644 Binary files a/assets/portraits/security-pros/card_004.png and b/assets/portraits/security-pros/card_004.png differ diff --git a/assets/portraits/security-pros/card_005.png b/assets/portraits/security-pros/card_005.png index f4708450..5a876b38 100644 Binary files a/assets/portraits/security-pros/card_005.png and b/assets/portraits/security-pros/card_005.png differ diff --git a/assets/portraits/security-pros/card_006.png b/assets/portraits/security-pros/card_006.png index 7ddc4c60..36847797 100644 Binary files a/assets/portraits/security-pros/card_006.png and b/assets/portraits/security-pros/card_006.png differ diff --git a/assets/portraits/security-pros/card_007.png b/assets/portraits/security-pros/card_007.png index d790e1fc..8ed44fc6 100644 Binary files a/assets/portraits/security-pros/card_007.png and b/assets/portraits/security-pros/card_007.png differ diff --git a/assets/portraits/security-pros/card_008.png b/assets/portraits/security-pros/card_008.png index d9ab2566..b864b846 100644 Binary files a/assets/portraits/security-pros/card_008.png and b/assets/portraits/security-pros/card_008.png differ diff --git a/assets/portraits/security-pros/card_009.png b/assets/portraits/security-pros/card_009.png index debf88de..bdd32f19 100644 Binary files a/assets/portraits/security-pros/card_009.png and b/assets/portraits/security-pros/card_009.png differ diff --git a/assets/portraits/security-pros/card_010.png b/assets/portraits/security-pros/card_010.png index 795a6021..cb59e4e6 100644 Binary files a/assets/portraits/security-pros/card_010.png and b/assets/portraits/security-pros/card_010.png differ diff --git a/assets/portraits/security-pros/card_011.png b/assets/portraits/security-pros/card_011.png index e2787a28..17e688bf 100644 Binary files a/assets/portraits/security-pros/card_011.png and b/assets/portraits/security-pros/card_011.png differ diff --git a/assets/portraits/security-pros/card_012.png b/assets/portraits/security-pros/card_012.png index 22054c3a..6867f819 100644 Binary files a/assets/portraits/security-pros/card_012.png and b/assets/portraits/security-pros/card_012.png differ diff --git a/assets/portraits/security-pros/card_013.png b/assets/portraits/security-pros/card_013.png index 76ce6dc9..a917e03d 100644 Binary files a/assets/portraits/security-pros/card_013.png and b/assets/portraits/security-pros/card_013.png differ diff --git a/assets/portraits/security-pros/card_014.png b/assets/portraits/security-pros/card_014.png index 2770667e..99796cef 100644 Binary files a/assets/portraits/security-pros/card_014.png and b/assets/portraits/security-pros/card_014.png differ diff --git a/assets/portraits/security-pros/card_015.png b/assets/portraits/security-pros/card_015.png index 584188f7..d07daa8f 100644 Binary files a/assets/portraits/security-pros/card_015.png and b/assets/portraits/security-pros/card_015.png differ diff --git a/assets/portraits/security-pros/card_016.png b/assets/portraits/security-pros/card_016.png index bca299f2..780cd938 100644 Binary files a/assets/portraits/security-pros/card_016.png and b/assets/portraits/security-pros/card_016.png differ diff --git a/assets/portraits/security-pros/card_017.png b/assets/portraits/security-pros/card_017.png index 5d282fd8..9043a340 100644 Binary files a/assets/portraits/security-pros/card_017.png and b/assets/portraits/security-pros/card_017.png differ diff --git a/assets/portraits/security-pros/card_018.png b/assets/portraits/security-pros/card_018.png index dbc2054a..0bb698ae 100644 Binary files a/assets/portraits/security-pros/card_018.png and b/assets/portraits/security-pros/card_018.png differ diff --git a/assets/portraits/security-pros/card_019.png b/assets/portraits/security-pros/card_019.png index 4a150188..e789c3cd 100644 Binary files a/assets/portraits/security-pros/card_019.png and b/assets/portraits/security-pros/card_019.png differ diff --git a/assets/portraits/security-pros/card_020.png b/assets/portraits/security-pros/card_020.png index 8c946dec..1af7f25e 100644 Binary files a/assets/portraits/security-pros/card_020.png and b/assets/portraits/security-pros/card_020.png differ diff --git a/assets/portraits/security-pros/card_021.png b/assets/portraits/security-pros/card_021.png deleted file mode 100644 index 27355487..00000000 Binary files a/assets/portraits/security-pros/card_021.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_022.png b/assets/portraits/security-pros/card_022.png index 7b5ffa00..c49e22ca 100644 Binary files a/assets/portraits/security-pros/card_022.png and b/assets/portraits/security-pros/card_022.png differ diff --git a/assets/portraits/security-pros/card_023.png b/assets/portraits/security-pros/card_023.png index d8867a98..920b10d7 100644 Binary files a/assets/portraits/security-pros/card_023.png and b/assets/portraits/security-pros/card_023.png differ diff --git a/assets/portraits/security-pros/card_024.png b/assets/portraits/security-pros/card_024.png index c2edea63..8753f02b 100644 Binary files a/assets/portraits/security-pros/card_024.png and b/assets/portraits/security-pros/card_024.png differ diff --git a/assets/portraits/security-pros/card_025.png b/assets/portraits/security-pros/card_025.png index 5d858e15..bb3a9eec 100644 Binary files a/assets/portraits/security-pros/card_025.png and b/assets/portraits/security-pros/card_025.png differ diff --git a/assets/portraits/security-pros/card_026.png b/assets/portraits/security-pros/card_026.png deleted file mode 100644 index 02d38472..00000000 Binary files a/assets/portraits/security-pros/card_026.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_027.png b/assets/portraits/security-pros/card_027.png index 1ced3919..1d5a67f8 100644 Binary files a/assets/portraits/security-pros/card_027.png and b/assets/portraits/security-pros/card_027.png differ diff --git a/assets/portraits/security-pros/card_028.png b/assets/portraits/security-pros/card_028.png deleted file mode 100644 index 73c6a6fa..00000000 Binary files a/assets/portraits/security-pros/card_028.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_029.png b/assets/portraits/security-pros/card_029.png deleted file mode 100644 index 63011ecc..00000000 Binary files a/assets/portraits/security-pros/card_029.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_030.png b/assets/portraits/security-pros/card_030.png deleted file mode 100644 index e076d030..00000000 Binary files a/assets/portraits/security-pros/card_030.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_031.png b/assets/portraits/security-pros/card_031.png index b7989310..4864e7c9 100644 Binary files a/assets/portraits/security-pros/card_031.png and b/assets/portraits/security-pros/card_031.png differ diff --git a/assets/portraits/security-pros/card_032.png b/assets/portraits/security-pros/card_032.png deleted file mode 100644 index ed0648a5..00000000 Binary files a/assets/portraits/security-pros/card_032.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_033.png b/assets/portraits/security-pros/card_033.png deleted file mode 100644 index c28a487a..00000000 Binary files a/assets/portraits/security-pros/card_033.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_034.png b/assets/portraits/security-pros/card_034.png deleted file mode 100644 index 2a92e9c4..00000000 Binary files a/assets/portraits/security-pros/card_034.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_035.png b/assets/portraits/security-pros/card_035.png index eaf43ec8..09c15e90 100644 Binary files a/assets/portraits/security-pros/card_035.png and b/assets/portraits/security-pros/card_035.png differ diff --git a/assets/portraits/security-pros/card_036.png b/assets/portraits/security-pros/card_036.png index d0b980ef..73f4af32 100644 Binary files a/assets/portraits/security-pros/card_036.png and b/assets/portraits/security-pros/card_036.png differ diff --git a/assets/portraits/security-pros/card_037.png b/assets/portraits/security-pros/card_037.png index d93595f1..258ef483 100644 Binary files a/assets/portraits/security-pros/card_037.png and b/assets/portraits/security-pros/card_037.png differ diff --git a/assets/portraits/security-pros/card_038.png b/assets/portraits/security-pros/card_038.png index 32d741c7..8d966bec 100644 Binary files a/assets/portraits/security-pros/card_038.png and b/assets/portraits/security-pros/card_038.png differ diff --git a/assets/portraits/security-pros/card_039.png b/assets/portraits/security-pros/card_039.png index 99709be9..4dd76ef4 100644 Binary files a/assets/portraits/security-pros/card_039.png and b/assets/portraits/security-pros/card_039.png differ diff --git a/assets/portraits/security-pros/card_040.png b/assets/portraits/security-pros/card_040.png index 8395c298..b886319c 100644 Binary files a/assets/portraits/security-pros/card_040.png and b/assets/portraits/security-pros/card_040.png differ diff --git a/assets/portraits/security-pros/card_041.png b/assets/portraits/security-pros/card_041.png index f19528ad..60ae166e 100644 Binary files a/assets/portraits/security-pros/card_041.png and b/assets/portraits/security-pros/card_041.png differ diff --git a/assets/portraits/security-pros/card_042.png b/assets/portraits/security-pros/card_042.png deleted file mode 100644 index 47a8fa22..00000000 Binary files a/assets/portraits/security-pros/card_042.png and /dev/null differ diff --git a/assets/portraits/security-pros/card_043.png b/assets/portraits/security-pros/card_043.png index b4ddc87e..667f7808 100644 Binary files a/assets/portraits/security-pros/card_043.png and b/assets/portraits/security-pros/card_043.png differ diff --git a/assets/portraits/security-pros/card_044.png b/assets/portraits/security-pros/card_044.png index 4586780b..d0f7ecca 100644 Binary files a/assets/portraits/security-pros/card_044.png and b/assets/portraits/security-pros/card_044.png differ diff --git a/assets/portraits/security-pros/card_045.png b/assets/portraits/security-pros/card_045.png index 54611abd..5bc5ddb8 100644 Binary files a/assets/portraits/security-pros/card_045.png and b/assets/portraits/security-pros/card_045.png differ diff --git a/assets/portraits/security-pros/card_046.png b/assets/portraits/security-pros/card_046.png index b778eb1c..e81e6d10 100644 Binary files a/assets/portraits/security-pros/card_046.png and b/assets/portraits/security-pros/card_046.png differ diff --git a/assets/portraits/security-pros/card_047.png b/assets/portraits/security-pros/card_047.png index 093ccf88..a2b7df9e 100644 Binary files a/assets/portraits/security-pros/card_047.png and b/assets/portraits/security-pros/card_047.png differ diff --git a/assets/portraits/security-pros/card_048.png b/assets/portraits/security-pros/card_048.png index d8045249..63cfaef6 100644 Binary files a/assets/portraits/security-pros/card_048.png and b/assets/portraits/security-pros/card_048.png differ diff --git a/assets/portraits/security-pros/card_049.png b/assets/portraits/security-pros/card_049.png index bb4eb644..cfe561ad 100644 Binary files a/assets/portraits/security-pros/card_049.png and b/assets/portraits/security-pros/card_049.png differ diff --git a/assets/portraits/security-pros/card_050.png b/assets/portraits/security-pros/card_050.png deleted file mode 100644 index d8728032..00000000 Binary files a/assets/portraits/security-pros/card_050.png and /dev/null differ diff --git a/data/security-references.json b/data/security-references.json new file mode 100644 index 00000000..91007960 --- /dev/null +++ b/data/security-references.json @@ -0,0 +1,747 @@ +[ + { + "number": 1, + "slug": "whitfield-diffie", + "name": "Whitfield Diffie", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/0/0c/Whitfield_Diffie_Royal_Society.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Duncan.Hull", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Whitfield_Diffie_Royal_Society.jpg", + "file": "Whitfield Diffie Royal Society.jpg", + "qid": "Q462089", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptographer (born 1944)" + }, + { + "number": 2, + "slug": "martin-hellman", + "name": "Martin Hellman", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/d/d4/Martin-Hellman.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "User .:Ajvol:. on en.wikipedia", + "license": "CC BY-SA 3.0", + "licenseUrl": "http://creativecommons.org/licenses/by-sa/3.0/", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Martin-Hellman.jpg", + "file": "Martin-Hellman.jpg", + "qid": "Q476466", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptologist (born 1945)" + }, + { + "number": 3, + "slug": "ron-rivest", + "name": "Ron Rivest", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/0/0b/Rivest_cropped.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Sherman,_Rivest,_and_Chaum.jpg: carback1\nderivative work: Bonnifac (talk)", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Rivest_cropped.jpg", + "file": "Rivest cropped.jpg", + "commonsFile": "Rivest cropped.jpg", + "via": "override" + }, + "candidates": [] + }, + { + "number": 4, + "slug": "adi-shamir", + "name": "Adi Shamir", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/1e/Adi_Shamir_at_TU_Darmstadt_%282013%29.jpg/1280px-Adi_Shamir_at_TU_Darmstadt_%282013%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Erik Tews", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Adi_Shamir_at_TU_Darmstadt_(2013).jpg", + "file": "Adi Shamir at TU Darmstadt (2013).jpg", + "qid": "Q320624", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "Israeli cryptographer (born 1952)" + }, + { + "number": 5, + "slug": "leonard-adleman", + "name": "Leonard Adleman", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/af/Len-mankin-pic.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "len adlmen", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Len-mankin-pic.jpg", + "file": "Len-mankin-pic.jpg", + "qid": "Q918650", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist" + }, + { + "number": 6, + "slug": "phil-zimmermann", + "name": "Phil Zimmermann", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/8/85/Phil_zimmermann.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Phil Zimmermann, uploaded by Matt Crypto", + "license": "CC BY-SA 3.0", + "licenseUrl": "http://creativecommons.org/licenses/by-sa/3.0/", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Phil_zimmermann.jpg", + "file": "Phil zimmermann.jpg", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Phil zimmermann.jpg" + ] + }, + { + "number": 7, + "slug": "bruce-schneier", + "name": "Bruce Schneier", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/45/Bruce_Schneier_1.jpg/1280px-Bruce_Schneier_1.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "sfllaw", + "license": "CC BY-SA 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Bruce_Schneier_1.jpg", + "file": "Bruce Schneier 1.jpg", + "qid": "Q368328", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist (born 1963)" + }, + { + "number": 8, + "slug": "paul-kocher", + "name": "Paul Kocher", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/ac/Paul_kocher_deepcrack.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "https://www.oocities.org", + "license": "CC0", + "licenseUrl": "http://creativecommons.org/publicdomain/zero/1.0/deed.en", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Paul_kocher_deepcrack.jpg", + "file": "Paul kocher deepcrack.jpg", + "qid": "Q3371582", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptographer" + }, + { + "number": 9, + "slug": "dorothy-denning", + "name": "Dorothy E. Denning", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/a9/Dorothy-Denning-Feb2013-head.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Unknown photographer, US Navy", + "license": "Public domain", + "licenseUrl": "", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Dorothy-Denning-Feb2013-head.jpg", + "file": "Dorothy-Denning-Feb2013-head.jpg", + "qid": "Q11542", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American information security researcher" + }, + { + "number": 10, + "slug": "gene-spafford", + "name": "Gene Spafford", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/9/9d/Gene-spafford.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Chlor at English Wikipedia", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Gene-spafford.jpg", + "file": "Gene-spafford.jpg", + "qid": "Q5531458", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist" + }, + { + "number": 11, + "slug": "ross-anderson", + "name": "Ross Anderson", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/b/b3/Ross_Anderson_%28security_researcher%29.jpg/1280px-Ross_Anderson_%28security_researcher%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Markus Kuhn", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Ross_Anderson_(security_researcher).jpg", + "file": "Ross Anderson (security researcher).jpg", + "qid": "Q110901283", + "via": "commons-search" + }, + "candidates": [ + "Ross Anderson (security researcher).jpg", + "Ross Anderson (7862797770).jpg", + "Ross Anderson (7862798682).jpg", + "Ross Anderson 15 June 2018 IMG 4317.jpg", + "Ross Anderson (Men of affairs, 1914).png" + ] + }, + { + "number": 12, + "slug": "matt-blaze", + "name": "Matt Blaze", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/c/c6/Matt_Blaze_DEF_CON_20.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "DEFCONConference", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Matt_Blaze_DEF_CON_20.jpg", + "file": "Matt Blaze DEF CON 20.jpg", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Matt Blaze DEF CON 20.jpg" + ] + }, + { + "number": 13, + "slug": "radia-perlman", + "name": "Radia Perlman", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/a/af/Radia_Perlman_2009.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Scientist-100 at English Wikipedia", + "license": "Public domain", + "licenseUrl": "", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Radia_Perlman_2009.jpg", + "file": "Radia Perlman 2009.jpg", + "qid": "Q7558", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American software designer and network engineer (born 1951)" + }, + { + "number": 14, + "slug": "steven-bellovin", + "name": "Steven M. Bellovin", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/9/95/Steven_M_Bellovin_2016.jpg/1280px-Steven_M_Bellovin_2016.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "New America", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Steven_M_Bellovin_2016.jpg", + "file": "Steven M Bellovin 2016.jpg", + "qid": "Q1485905", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist" + }, + { + "number": 15, + "slug": "william-cheswick", + "name": "William Cheswick", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/f/fc/William_Cheswick_2024.jpg/1280px-William_Cheswick_2024.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Kevin Payravi", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:William_Cheswick_2024.jpg", + "file": "William Cheswick 2024.jpg", + "qid": "Q8006762", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American engineer" + }, + { + "number": 16, + "slug": "marcus-ranum", + "name": "Marcus J. Ranum", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/1/12/Mjr-portrait-picture-mid.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Marcus Ranum (per discussion on ticket)", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Mjr-portrait-picture-mid.jpg", + "file": "Mjr-portrait-picture-mid.jpg", + "qid": "Q3846688", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "researcher" + }, + { + "number": 17, + "slug": "wietse-venema", + "name": "Wietse Venema", + "ref": { + "url": "https://live.staticflickr.com/104/276434076_581ed1215d_b.jpg", + "credit": "Alexandre Dulaunoy", + "license": "CC BY-SA 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/2.0/", + "sourceUrl": "https://www.flickr.com/photos/31797858@N00/276434076", + "note": "hack.lu 2006. Supersedes the Wikidata P18 file, which shows a different man.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 18, + "slug": "dan-farmer", + "name": "Dan Farmer", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/9/99/Dan_Farmer_in_2007.JPG?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Ian Manka", + "license": "CC BY-SA 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Dan_Farmer_in_2007.JPG", + "file": "Dan Farmer in 2007.JPG", + "qid": "Q5213467", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer programmer, computer security researcher and businessman" + }, + { + "number": 19, + "slug": "gordon-lyon", + "name": "Gordon Lyon", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/f/fc/Fyodor_Hope6_Appelbaum_1100x1100.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Jacob Appelbaum", + "license": "CC BY-SA 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Fyodor_Hope6_Appelbaum_1100x1100.jpg", + "file": "Fyodor Hope6 Appelbaum 1100x1100.jpg", + "qid": "Q3091346", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American network security expert, open source programmer, writer, and a hacker" + }, + { + "number": 20, + "slug": "martin-roesch", + "name": "Martin Roesch", + "ref": { + "url": "https://media.kasperskycontenthub.com/wp-content/uploads/sites/103/2013/07/07043722/sourcefire.jpg", + "credit": "Sourcefire press photo", + "license": "supplied", + "sourceUrl": "https://threatpost.com/", + "note": "Sourcefire-era press photograph. Supersedes the Commons files, which are a different Martin.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 21, + "slug": "gerald-combs", + "name": "Gerald Combs", + "ref": null, + "candidates": [] + }, + { + "number": 22, + "slug": "renaud-deraison", + "name": "Renaud Deraison", + "ref": { + "url": "https://live.staticflickr.com/122/276307114_f29f0a38ec_b.jpg", + "credit": "Alexandre Dulaunoy", + "license": "CC BY-SA 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/2.0/", + "sourceUrl": "https://www.flickr.com/photos/31797858@N00/276307114", + "note": "hack.lu 2006 opening speech.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 23, + "slug": "paul-vixie", + "name": "Paul Vixie", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/d/d6/Paul_Vixie_-_2014.jpg/1280px-Paul_Vixie_-_2014.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "ImaginingtheInternet", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Paul_Vixie_-_2014.jpg", + "file": "Paul Vixie - 2014.jpg", + "qid": "Q92777", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American internet pioneer" + }, + { + "number": 24, + "slug": "niels-provos", + "name": "Niels Provos", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/d/dc/Niels_provos.jpg/1280px-Niels_provos.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "NonAlphabetic", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Niels_provos.jpg", + "file": "Niels provos.jpg", + "qid": "Q7031568", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "cryptographer" + }, + { + "number": 25, + "slug": "jeremiah-grossman", + "name": "Jeremiah Grossman", + "ref": { + "url": "https://cdn.prod.website-files.com/62deb8a5505ca121b1665bbe/63cf05d85cad9419bfa83096_Photo%20Oct%2003%202022%2C%206%2054%2000%20AM-p-800.jpg", + "credit": "Jeremiah Grossman", + "license": "supplied", + "sourceUrl": "https://www.jeremiahgrossman.com/", + "note": "Publicity photo from his own site, supplied by the site owner as reference.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 26, + "slug": "robert-hansen", + "name": "Robert Hansen", + "ref": null, + "candidates": [ + "Robert Hansen ex libris.jpg", + "Robert Hansen Jr 180425.jpg" + ] + }, + { + "number": 27, + "slug": "mark-curphey", + "name": "Mark Curphey", + "ref": { + "url": "https://user-images.githubusercontent.com/143776/193322138-009524de-bae1-4190-989d-2d729ad0beb8.png", + "credit": "Mark Curphey", + "license": "supplied", + "sourceUrl": "https://www.crash-override.com/", + "note": "Portrait published by his own company.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 28, + "slug": "jeff-williams", + "name": "Jeff Williams", + "ref": null, + "candidates": [], + "rejected": "\"Jeff Williams photo.jpg\" is one of several unrelated Jeff Williamses. Cannot confirm it is the Contrast Security founder." + }, + { + "number": 29, + "slug": "gary-mcgraw", + "name": "Gary McGraw", + "ref": null, + "candidates": [], + "rejected": "Only candidate is a group briefing shot with audience heads across the frame; the other Commons hits are unrelated US Navy photographs." + }, + { + "number": 30, + "slug": "michael-howard", + "name": "Michael Howard", + "ref": null, + "candidates": [], + "rejected": "Every Commons hit is a different Michael Howard — the British politician, or a young man with no connection to the Microsoft SDL author." + }, + { + "number": 31, + "slug": "adam-shostack", + "name": "Adam Shostack", + "ref": { + "url": "https://shostack.org/img/site/Adam-Shostack-headshot.png", + "credit": "Adam Shostack", + "license": "supplied", + "sourceUrl": "https://shostack.org/about/adam", + "note": "Headshot published on his own site.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 32, + "slug": "ivan-ristic", + "name": "Ivan Ristić", + "ref": null, + "candidates": [] + }, + { + "number": 33, + "slug": "mark-dowd", + "name": "Mark Dowd", + "ref": null, + "candidates": [] + }, + { + "number": 34, + "slug": "halvar-flake", + "name": "Thomas Dullien", + "ref": null, + "candidates": [] + }, + { + "number": 35, + "slug": "alex-sotirov", + "name": "Alex Sotirov", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/c/c5/Alex_Sotirov.jpg/1280px-Alex_Sotirov.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Alexander Klink", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Alex_Sotirov.jpg", + "file": "Alex Sotirov.jpg", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Alex Sotirov.jpg" + ] + }, + { + "number": 36, + "slug": "thomas-ptacek", + "name": "Thomas Ptacek", + "ref": { + "url": "https://live.staticflickr.com/2733/4302617844_52625091af_b.jpg", + "credit": "cfournie", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0/", + "sourceUrl": "https://www.flickr.com/photos/37932109@N05/4302617844", + "via": "override" + }, + "candidates": [] + }, + { + "number": 37, + "slug": "matthew-green", + "name": "Matthew D. Green", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/d/da/Matthew_Green.jpeg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Calhbca", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Matthew_Green.jpeg", + "file": "Matthew Green.jpeg", + "qid": "Q16729426", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptographer and researcher" + }, + { + "number": 38, + "slug": "j-alex-halderman", + "name": "J. Alex Halderman", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/b/b2/J._Alex_Halderman_-_2018.jpg/1280px-J._Alex_Halderman_-_2018.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "media.ccc.de", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:J._Alex_Halderman_-_2018.jpg", + "file": "J. Alex Halderman - 2018.jpg", + "qid": "Q130974774", + "via": "commons-search" + }, + "candidates": [ + "J. Alex Halderman - 2018.jpg" + ] + }, + { + "number": 39, + "slug": "nadia-heninger", + "name": "Nadia Heninger", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/5/5a/Nadia_Heninger_2013_at_Chaos_Communication_Congress_30C3_DJB_03.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "Ordercrazy", + "license": "CC0", + "licenseUrl": "http://creativecommons.org/publicdomain/zero/1.0/deed.en", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Nadia_Heninger_2013_at_Chaos_Communication_Congress_30C3_DJB_03.jpg", + "file": "Nadia Heninger 2013 at Chaos Communication Congress 30C3 DJB 03.jpg", + "qid": "Q56678255", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American cryptographer, computer security expert" + }, + { + "number": 40, + "slug": "troy-hunt", + "name": "Troy Hunt", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/1/11/Troy_Hunt.jpg/1280px-Troy_Hunt.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Troy Hunt", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Troy_Hunt.jpg", + "file": "Troy Hunt.jpg", + "qid": "Q23308064", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "Australian web security expert" + }, + { + "number": 41, + "slug": "window-snyder", + "name": "Window Snyder", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/42/Window_Snyder_Web_Summit_2017_cropped.jpg/1280px-Window_Snyder_Web_Summit_2017_cropped.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Web Summit", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Window_Snyder_Web_Summit_2017_cropped.jpg", + "file": "Window Snyder Web Summit 2017 cropped.jpg", + "qid": "Q8024355", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "Computer security expert" + }, + { + "number": 42, + "slug": "heather-adkins", + "name": "Heather Adkins", + "ref": null, + "candidates": [] + }, + { + "number": 43, + "slug": "parisa-tabriz", + "name": "Parisa Tabriz", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/6/67/Parisa_Tabriz_Blackhat%2717_profile.jpg/1280px-Parisa_Tabriz_Blackhat%2717_profile.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "mrisher", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Parisa_Tabriz_Blackhat%2717_profile.jpg", + "file": "Parisa Tabriz Blackhat'17 profile.jpg", + "qid": "Q17496835", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American/Iranian computer security expert" + }, + { + "number": 44, + "slug": "alex-stamos", + "name": "Alex Stamos", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/6/6a/Alex_Stamos_at_Web_Summit_2015_-_Dublin%2C_Ireland.jpg/1280px-Alex_Stamos_at_Web_Summit_2015_-_Dublin%2C_Ireland.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Web Summit", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Alex_Stamos_at_Web_Summit_2015_-_Dublin,_Ireland.jpg", + "file": "Alex Stamos at Web Summit 2015 - Dublin, Ireland.jpg", + "qid": "Q50822408", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist and academic, former chief security officer at Facebook" + }, + { + "number": 45, + "slug": "dan-geer", + "name": "Dan Geer", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/4/4d/Dan_Geer_-_Freedom_to_Connect_2015_%2816109600123%29.jpg/1280px-Dan_Geer_-_Freedom_to_Connect_2015_%2816109600123%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Doc Searls from Santa Barbara, USA", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Dan_Geer_-_Freedom_to_Connect_2015_(16109600123).jpg", + "file": "Dan Geer - Freedom to Connect 2015 (16109600123).jpg", + "qid": "Q5213551", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "American computer scientist" + }, + { + "number": 46, + "slug": "kevin-mandia", + "name": "Kevin Mandia", + "ref": { + "url": "https://ballisticventures.com/wp-content/uploads/2024/06/Kevin-Mandia.png", + "credit": "Ballistic Ventures", + "license": "supplied", + "sourceUrl": "https://www.ballisticventures.com/team/kevin-mandia", + "note": "Partner bio photo.", + "via": "override" + }, + "candidates": [] + }, + { + "number": 47, + "slug": "mikko-hypponen", + "name": "Mikko Hyppönen", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/c/ce/Mikko_Hypp%C3%B6nen_-_2021_%28cropped%29.jpg/1280px-Mikko_Hypp%C3%B6nen_-_2021_%28cropped%29.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "Ville Erkkilä", + "license": "CC BY-SA 4.0", + "licenseUrl": "https://creativecommons.org/licenses/by-sa/4.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Mikko_Hypp%C3%B6nen_-_2021_(cropped).jpg", + "file": "Mikko Hyppönen - 2021 (cropped).jpg", + "qid": "Q2489610", + "via": "wikidata-p18" + }, + "candidates": [], + "desc": "Finnish computer security expert" + }, + { + "number": 48, + "slug": "robert-m-lee", + "name": "Robert M. Lee", + "ref": { + "url": "https://upload.wikimedia.org/wikipedia/commons/9/92/Robert_M._Lee_at_World_Economic_Forum_Davos_2022.png?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail_unscaled", + "credit": "World Economic Forum", + "license": "CC BY 3.0", + "licenseUrl": "https://creativecommons.org/licenses/by/3.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Robert_M._Lee_at_World_Economic_Forum_Davos_2022.png", + "file": "Robert M. Lee at World Economic Forum Davos 2022.png", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Robert M. Lee at World Economic Forum Davos 2022.png" + ] + }, + { + "number": 49, + "slug": "lesley-carhart", + "name": "Lesley Carhart", + "ref": { + "url": "https://thumb.wikimedia.org/wikipedia/commons/thumb/7/70/Lesley_Carhart_-_What_Can_We_Learn_About_Cybersecurity_Trashfires.jpg/1280px-Lesley_Carhart_-_What_Can_We_Learn_About_Cybersecurity_Trashfires.jpg?utm_source=commons.wikimedia.org&utm_campaign=imageinfo&utm_content=thumbnail", + "credit": "TechCrunch", + "license": "CC BY 2.0", + "licenseUrl": "https://creativecommons.org/licenses/by/2.0", + "sourceUrl": "https://commons.wikimedia.org/wiki/File:Lesley_Carhart_-_What_Can_We_Learn_About_Cybersecurity_Trashfires.jpg", + "file": "Lesley Carhart - What Can We Learn About Cybersecurity Trashfires.jpg", + "qid": null, + "via": "commons-search" + }, + "candidates": [ + "Lesley Carhart - What Can We Learn About Cybersecurity Trashfires.jpg", + "Lesley Carhart - What Can We Learn About Cybersecurity Trashfires (cropped).jpg" + ] + }, + { + "number": 50, + "slug": "katie-nickels", + "name": "Katie Nickels", + "ref": null, + "candidates": [] + } +] diff --git a/public/cards/security/001-whitfield-diffie-back.png b/public/cards/security/001-whitfield-diffie-back.png index 915e9f9c..22db10ae 100644 Binary files a/public/cards/security/001-whitfield-diffie-back.png and b/public/cards/security/001-whitfield-diffie-back.png differ diff --git a/public/cards/security/001-whitfield-diffie-front.png b/public/cards/security/001-whitfield-diffie-front.png index a94cd2a6..456a3902 100644 Binary files a/public/cards/security/001-whitfield-diffie-front.png and b/public/cards/security/001-whitfield-diffie-front.png differ diff --git a/public/cards/security/002-martin-hellman-back.png b/public/cards/security/002-martin-hellman-back.png index 13e8958f..35c28d32 100644 Binary files a/public/cards/security/002-martin-hellman-back.png and b/public/cards/security/002-martin-hellman-back.png differ diff --git a/public/cards/security/002-martin-hellman-front.png b/public/cards/security/002-martin-hellman-front.png index 4c44c51e..94fecf84 100644 Binary files a/public/cards/security/002-martin-hellman-front.png and b/public/cards/security/002-martin-hellman-front.png differ diff --git a/public/cards/security/003-ron-rivest-back.png b/public/cards/security/003-ron-rivest-back.png index 065bb240..8d8dc62e 100644 Binary files a/public/cards/security/003-ron-rivest-back.png and b/public/cards/security/003-ron-rivest-back.png differ diff --git a/public/cards/security/003-ron-rivest-front.png b/public/cards/security/003-ron-rivest-front.png index 9e84ffdd..d366c2c9 100644 Binary files a/public/cards/security/003-ron-rivest-front.png and b/public/cards/security/003-ron-rivest-front.png differ diff --git a/public/cards/security/004-adi-shamir-back.png b/public/cards/security/004-adi-shamir-back.png index 5a55fd03..c316c6d1 100644 Binary files a/public/cards/security/004-adi-shamir-back.png and b/public/cards/security/004-adi-shamir-back.png differ diff --git a/public/cards/security/004-adi-shamir-front.png b/public/cards/security/004-adi-shamir-front.png index 19d8ce36..6fa0c767 100644 Binary files a/public/cards/security/004-adi-shamir-front.png and b/public/cards/security/004-adi-shamir-front.png differ diff --git a/public/cards/security/005-leonard-adleman-back.png b/public/cards/security/005-leonard-adleman-back.png index ee6ecded..2aa1e27e 100644 Binary files a/public/cards/security/005-leonard-adleman-back.png and b/public/cards/security/005-leonard-adleman-back.png differ diff --git a/public/cards/security/005-leonard-adleman-front.png b/public/cards/security/005-leonard-adleman-front.png index 1e72d2f8..5667914f 100644 Binary files a/public/cards/security/005-leonard-adleman-front.png and b/public/cards/security/005-leonard-adleman-front.png differ diff --git a/public/cards/security/006-phil-zimmermann-back.png b/public/cards/security/006-phil-zimmermann-back.png index 013dcb22..32cad92d 100644 Binary files a/public/cards/security/006-phil-zimmermann-back.png and b/public/cards/security/006-phil-zimmermann-back.png differ diff --git a/public/cards/security/006-phil-zimmermann-front.png b/public/cards/security/006-phil-zimmermann-front.png index 88e40f72..b30f44d0 100644 Binary files a/public/cards/security/006-phil-zimmermann-front.png and b/public/cards/security/006-phil-zimmermann-front.png differ diff --git a/public/cards/security/007-bruce-schneier-back.png b/public/cards/security/007-bruce-schneier-back.png index 0aa46261..665c466d 100644 Binary files a/public/cards/security/007-bruce-schneier-back.png and b/public/cards/security/007-bruce-schneier-back.png differ diff --git a/public/cards/security/007-bruce-schneier-front.png b/public/cards/security/007-bruce-schneier-front.png index 09ee26dd..8192b1d3 100644 Binary files a/public/cards/security/007-bruce-schneier-front.png and b/public/cards/security/007-bruce-schneier-front.png differ diff --git a/public/cards/security/008-paul-kocher-back.png b/public/cards/security/008-paul-kocher-back.png index f59ff81b..beee2443 100644 Binary files a/public/cards/security/008-paul-kocher-back.png and b/public/cards/security/008-paul-kocher-back.png differ diff --git a/public/cards/security/008-paul-kocher-front.png b/public/cards/security/008-paul-kocher-front.png index 1ad6909f..c2148af6 100644 Binary files a/public/cards/security/008-paul-kocher-front.png and b/public/cards/security/008-paul-kocher-front.png differ diff --git a/public/cards/security/009-dorothy-denning-back.png b/public/cards/security/009-dorothy-denning-back.png index 316ee333..a6aed799 100644 Binary files a/public/cards/security/009-dorothy-denning-back.png and b/public/cards/security/009-dorothy-denning-back.png differ diff --git a/public/cards/security/009-dorothy-denning-front.png b/public/cards/security/009-dorothy-denning-front.png index 7cc90d98..4528550d 100644 Binary files a/public/cards/security/009-dorothy-denning-front.png and b/public/cards/security/009-dorothy-denning-front.png differ diff --git a/public/cards/security/010-gene-spafford-back.png b/public/cards/security/010-gene-spafford-back.png index 0e7499e5..2ccf8289 100644 Binary files a/public/cards/security/010-gene-spafford-back.png and b/public/cards/security/010-gene-spafford-back.png differ diff --git a/public/cards/security/010-gene-spafford-front.png b/public/cards/security/010-gene-spafford-front.png index 53deabb0..6aa9e79a 100644 Binary files a/public/cards/security/010-gene-spafford-front.png and b/public/cards/security/010-gene-spafford-front.png differ diff --git a/public/cards/security/011-ross-anderson-back.png b/public/cards/security/011-ross-anderson-back.png index 5b828bdb..90a8bbe3 100644 Binary files a/public/cards/security/011-ross-anderson-back.png and b/public/cards/security/011-ross-anderson-back.png differ diff --git a/public/cards/security/011-ross-anderson-front.png b/public/cards/security/011-ross-anderson-front.png index 3e2d83ec..69d034aa 100644 Binary files a/public/cards/security/011-ross-anderson-front.png and b/public/cards/security/011-ross-anderson-front.png differ diff --git a/public/cards/security/012-matt-blaze-back.png b/public/cards/security/012-matt-blaze-back.png index f62ff8a7..8d2e733e 100644 Binary files a/public/cards/security/012-matt-blaze-back.png and b/public/cards/security/012-matt-blaze-back.png differ diff --git a/public/cards/security/012-matt-blaze-front.png b/public/cards/security/012-matt-blaze-front.png index 7a04bb56..513dbf40 100644 Binary files a/public/cards/security/012-matt-blaze-front.png and b/public/cards/security/012-matt-blaze-front.png differ diff --git a/public/cards/security/013-radia-perlman-back.png b/public/cards/security/013-radia-perlman-back.png index 1ec4b05c..e382a9af 100644 Binary files a/public/cards/security/013-radia-perlman-back.png and b/public/cards/security/013-radia-perlman-back.png differ diff --git a/public/cards/security/013-radia-perlman-front.png b/public/cards/security/013-radia-perlman-front.png index 52e33b49..7b7052c3 100644 Binary files a/public/cards/security/013-radia-perlman-front.png and b/public/cards/security/013-radia-perlman-front.png differ diff --git a/public/cards/security/014-steven-bellovin-back.png b/public/cards/security/014-steven-bellovin-back.png index 1b0a1bf6..76121f39 100644 Binary files a/public/cards/security/014-steven-bellovin-back.png and b/public/cards/security/014-steven-bellovin-back.png differ diff --git a/public/cards/security/014-steven-bellovin-front.png b/public/cards/security/014-steven-bellovin-front.png index a6c48d4d..ce3f9d89 100644 Binary files a/public/cards/security/014-steven-bellovin-front.png and b/public/cards/security/014-steven-bellovin-front.png differ diff --git a/public/cards/security/015-william-cheswick-back.png b/public/cards/security/015-william-cheswick-back.png index 1b2d5b61..c7851443 100644 Binary files a/public/cards/security/015-william-cheswick-back.png and b/public/cards/security/015-william-cheswick-back.png differ diff --git a/public/cards/security/015-william-cheswick-front.png b/public/cards/security/015-william-cheswick-front.png index f96a23e5..2880d76c 100644 Binary files a/public/cards/security/015-william-cheswick-front.png and b/public/cards/security/015-william-cheswick-front.png differ diff --git a/public/cards/security/016-marcus-ranum-back.png b/public/cards/security/016-marcus-ranum-back.png index ce73574e..3fc69234 100644 Binary files a/public/cards/security/016-marcus-ranum-back.png and b/public/cards/security/016-marcus-ranum-back.png differ diff --git a/public/cards/security/016-marcus-ranum-front.png b/public/cards/security/016-marcus-ranum-front.png index c8777327..6bb4d114 100644 Binary files a/public/cards/security/016-marcus-ranum-front.png and b/public/cards/security/016-marcus-ranum-front.png differ diff --git a/public/cards/security/017-wietse-venema-back.png b/public/cards/security/017-wietse-venema-back.png index e667238a..4eaabb47 100644 Binary files a/public/cards/security/017-wietse-venema-back.png and b/public/cards/security/017-wietse-venema-back.png differ diff --git a/public/cards/security/017-wietse-venema-front.png b/public/cards/security/017-wietse-venema-front.png index b40d4804..52efeafb 100644 Binary files a/public/cards/security/017-wietse-venema-front.png and b/public/cards/security/017-wietse-venema-front.png differ diff --git a/public/cards/security/018-dan-farmer-back.png b/public/cards/security/018-dan-farmer-back.png index 5cb3d653..a5bbdae3 100644 Binary files a/public/cards/security/018-dan-farmer-back.png and b/public/cards/security/018-dan-farmer-back.png differ diff --git a/public/cards/security/018-dan-farmer-front.png b/public/cards/security/018-dan-farmer-front.png index 473abf7a..bb4bbbb9 100644 Binary files a/public/cards/security/018-dan-farmer-front.png and b/public/cards/security/018-dan-farmer-front.png differ diff --git a/public/cards/security/019-gordon-lyon-back.png b/public/cards/security/019-gordon-lyon-back.png index cd2b3f9c..6624303b 100644 Binary files a/public/cards/security/019-gordon-lyon-back.png and b/public/cards/security/019-gordon-lyon-back.png differ diff --git a/public/cards/security/019-gordon-lyon-front.png b/public/cards/security/019-gordon-lyon-front.png index 9ec0efb0..84437d4c 100644 Binary files a/public/cards/security/019-gordon-lyon-front.png and b/public/cards/security/019-gordon-lyon-front.png differ diff --git a/public/cards/security/020-martin-roesch-back.png b/public/cards/security/020-martin-roesch-back.png index 02d4a65a..3b38d354 100644 Binary files a/public/cards/security/020-martin-roesch-back.png and b/public/cards/security/020-martin-roesch-back.png differ diff --git a/public/cards/security/020-martin-roesch-front.png b/public/cards/security/020-martin-roesch-front.png index 8c832f07..d1ec09bb 100644 Binary files a/public/cards/security/020-martin-roesch-front.png and b/public/cards/security/020-martin-roesch-front.png differ diff --git a/public/cards/security/021-gerald-combs-front.png b/public/cards/security/021-gerald-combs-front.png index e1a455ea..65f8bd8f 100644 Binary files a/public/cards/security/021-gerald-combs-front.png and b/public/cards/security/021-gerald-combs-front.png differ diff --git a/public/cards/security/022-renaud-deraison-back.png b/public/cards/security/022-renaud-deraison-back.png index 0217894f..bb38908d 100644 Binary files a/public/cards/security/022-renaud-deraison-back.png and b/public/cards/security/022-renaud-deraison-back.png differ diff --git a/public/cards/security/022-renaud-deraison-front.png b/public/cards/security/022-renaud-deraison-front.png index ce87913c..15424ec8 100644 Binary files a/public/cards/security/022-renaud-deraison-front.png and b/public/cards/security/022-renaud-deraison-front.png differ diff --git a/public/cards/security/023-paul-vixie-back.png b/public/cards/security/023-paul-vixie-back.png index e6c64562..f850fa4b 100644 Binary files a/public/cards/security/023-paul-vixie-back.png and b/public/cards/security/023-paul-vixie-back.png differ diff --git a/public/cards/security/023-paul-vixie-front.png b/public/cards/security/023-paul-vixie-front.png index 821a620f..17852305 100644 Binary files a/public/cards/security/023-paul-vixie-front.png and b/public/cards/security/023-paul-vixie-front.png differ diff --git a/public/cards/security/024-niels-provos-back.png b/public/cards/security/024-niels-provos-back.png index f89601bd..593d7460 100644 Binary files a/public/cards/security/024-niels-provos-back.png and b/public/cards/security/024-niels-provos-back.png differ diff --git a/public/cards/security/024-niels-provos-front.png b/public/cards/security/024-niels-provos-front.png index cf9a1aae..d713bc22 100644 Binary files a/public/cards/security/024-niels-provos-front.png and b/public/cards/security/024-niels-provos-front.png differ diff --git a/public/cards/security/025-jeremiah-grossman-back.png b/public/cards/security/025-jeremiah-grossman-back.png index d047bb6e..667dd89f 100644 Binary files a/public/cards/security/025-jeremiah-grossman-back.png and b/public/cards/security/025-jeremiah-grossman-back.png differ diff --git a/public/cards/security/025-jeremiah-grossman-front.png b/public/cards/security/025-jeremiah-grossman-front.png index ad6925ad..a80bca16 100644 Binary files a/public/cards/security/025-jeremiah-grossman-front.png and b/public/cards/security/025-jeremiah-grossman-front.png differ diff --git a/public/cards/security/026-robert-hansen-front.png b/public/cards/security/026-robert-hansen-front.png index cb5823cb..247ea7f7 100644 Binary files a/public/cards/security/026-robert-hansen-front.png and b/public/cards/security/026-robert-hansen-front.png differ diff --git a/public/cards/security/027-mark-curphey-back.png b/public/cards/security/027-mark-curphey-back.png index bb236cb3..21484504 100644 Binary files a/public/cards/security/027-mark-curphey-back.png and b/public/cards/security/027-mark-curphey-back.png differ diff --git a/public/cards/security/027-mark-curphey-front.png b/public/cards/security/027-mark-curphey-front.png index 11ec5d12..16ca9dc1 100644 Binary files a/public/cards/security/027-mark-curphey-front.png and b/public/cards/security/027-mark-curphey-front.png differ diff --git a/public/cards/security/028-jeff-williams-front.png b/public/cards/security/028-jeff-williams-front.png index d5c7f860..9cf3ff45 100644 Binary files a/public/cards/security/028-jeff-williams-front.png and b/public/cards/security/028-jeff-williams-front.png differ diff --git a/public/cards/security/029-gary-mcgraw-front.png b/public/cards/security/029-gary-mcgraw-front.png index 0605914d..e272c3ab 100644 Binary files a/public/cards/security/029-gary-mcgraw-front.png and b/public/cards/security/029-gary-mcgraw-front.png differ diff --git a/public/cards/security/030-michael-howard-front.png b/public/cards/security/030-michael-howard-front.png index 8ecbb8d5..b29227ef 100644 Binary files a/public/cards/security/030-michael-howard-front.png and b/public/cards/security/030-michael-howard-front.png differ diff --git a/public/cards/security/031-adam-shostack-back.png b/public/cards/security/031-adam-shostack-back.png index 2fba1004..a68f21a8 100644 Binary files a/public/cards/security/031-adam-shostack-back.png and b/public/cards/security/031-adam-shostack-back.png differ diff --git a/public/cards/security/031-adam-shostack-front.png b/public/cards/security/031-adam-shostack-front.png index 2120988d..c30a6740 100644 Binary files a/public/cards/security/031-adam-shostack-front.png and b/public/cards/security/031-adam-shostack-front.png differ diff --git a/public/cards/security/032-ivan-ristic-front.png b/public/cards/security/032-ivan-ristic-front.png index 8b9f45d4..42ebd24a 100644 Binary files a/public/cards/security/032-ivan-ristic-front.png and b/public/cards/security/032-ivan-ristic-front.png differ diff --git a/public/cards/security/033-mark-dowd-front.png b/public/cards/security/033-mark-dowd-front.png index 87002206..d57f13c3 100644 Binary files a/public/cards/security/033-mark-dowd-front.png and b/public/cards/security/033-mark-dowd-front.png differ diff --git a/public/cards/security/034-halvar-flake-front.png b/public/cards/security/034-halvar-flake-front.png index 86064ce3..8caa1fef 100644 Binary files a/public/cards/security/034-halvar-flake-front.png and b/public/cards/security/034-halvar-flake-front.png differ diff --git a/public/cards/security/035-alex-sotirov-back.png b/public/cards/security/035-alex-sotirov-back.png index fd1cb3f5..e6ca68a9 100644 Binary files a/public/cards/security/035-alex-sotirov-back.png and b/public/cards/security/035-alex-sotirov-back.png differ diff --git a/public/cards/security/035-alex-sotirov-front.png b/public/cards/security/035-alex-sotirov-front.png index 94dba9a3..b3300063 100644 Binary files a/public/cards/security/035-alex-sotirov-front.png and b/public/cards/security/035-alex-sotirov-front.png differ diff --git a/public/cards/security/036-thomas-ptacek-back.png b/public/cards/security/036-thomas-ptacek-back.png index 8112421e..887c6a63 100644 Binary files a/public/cards/security/036-thomas-ptacek-back.png and b/public/cards/security/036-thomas-ptacek-back.png differ diff --git a/public/cards/security/036-thomas-ptacek-front.png b/public/cards/security/036-thomas-ptacek-front.png index d1bed8f7..80d67b85 100644 Binary files a/public/cards/security/036-thomas-ptacek-front.png and b/public/cards/security/036-thomas-ptacek-front.png differ diff --git a/public/cards/security/037-matthew-green-back.png b/public/cards/security/037-matthew-green-back.png index 605ad9a5..6dd1e292 100644 Binary files a/public/cards/security/037-matthew-green-back.png and b/public/cards/security/037-matthew-green-back.png differ diff --git a/public/cards/security/037-matthew-green-front.png b/public/cards/security/037-matthew-green-front.png index a7a100c4..270c2f0c 100644 Binary files a/public/cards/security/037-matthew-green-front.png and b/public/cards/security/037-matthew-green-front.png differ diff --git a/public/cards/security/038-j-alex-halderman-back.png b/public/cards/security/038-j-alex-halderman-back.png index 68721d79..806661fd 100644 Binary files a/public/cards/security/038-j-alex-halderman-back.png and b/public/cards/security/038-j-alex-halderman-back.png differ diff --git a/public/cards/security/038-j-alex-halderman-front.png b/public/cards/security/038-j-alex-halderman-front.png index 9074d706..47f6f07d 100644 Binary files a/public/cards/security/038-j-alex-halderman-front.png and b/public/cards/security/038-j-alex-halderman-front.png differ diff --git a/public/cards/security/039-nadia-heninger-back.png b/public/cards/security/039-nadia-heninger-back.png index fb9eaa07..4cfd6c5a 100644 Binary files a/public/cards/security/039-nadia-heninger-back.png and b/public/cards/security/039-nadia-heninger-back.png differ diff --git a/public/cards/security/039-nadia-heninger-front.png b/public/cards/security/039-nadia-heninger-front.png index 8a47094a..7a9a407b 100644 Binary files a/public/cards/security/039-nadia-heninger-front.png and b/public/cards/security/039-nadia-heninger-front.png differ diff --git a/public/cards/security/040-troy-hunt-back.png b/public/cards/security/040-troy-hunt-back.png index d24a1914..d4b6cb54 100644 Binary files a/public/cards/security/040-troy-hunt-back.png and b/public/cards/security/040-troy-hunt-back.png differ diff --git a/public/cards/security/040-troy-hunt-front.png b/public/cards/security/040-troy-hunt-front.png index 5572fad0..fc1558c6 100644 Binary files a/public/cards/security/040-troy-hunt-front.png and b/public/cards/security/040-troy-hunt-front.png differ diff --git a/public/cards/security/041-window-snyder-back.png b/public/cards/security/041-window-snyder-back.png index 984b62d8..93cf0f41 100644 Binary files a/public/cards/security/041-window-snyder-back.png and b/public/cards/security/041-window-snyder-back.png differ diff --git a/public/cards/security/041-window-snyder-front.png b/public/cards/security/041-window-snyder-front.png index 9d4d275a..d0678bbf 100644 Binary files a/public/cards/security/041-window-snyder-front.png and b/public/cards/security/041-window-snyder-front.png differ diff --git a/public/cards/security/042-heather-adkins-front.png b/public/cards/security/042-heather-adkins-front.png index 1a6b5540..c5c04b27 100644 Binary files a/public/cards/security/042-heather-adkins-front.png and b/public/cards/security/042-heather-adkins-front.png differ diff --git a/public/cards/security/043-parisa-tabriz-back.png b/public/cards/security/043-parisa-tabriz-back.png index 1f8a070d..fb941b38 100644 Binary files a/public/cards/security/043-parisa-tabriz-back.png and b/public/cards/security/043-parisa-tabriz-back.png differ diff --git a/public/cards/security/043-parisa-tabriz-front.png b/public/cards/security/043-parisa-tabriz-front.png index 950dff9d..a139857a 100644 Binary files a/public/cards/security/043-parisa-tabriz-front.png and b/public/cards/security/043-parisa-tabriz-front.png differ diff --git a/public/cards/security/044-alex-stamos-back.png b/public/cards/security/044-alex-stamos-back.png index 7357be20..a53d3892 100644 Binary files a/public/cards/security/044-alex-stamos-back.png and b/public/cards/security/044-alex-stamos-back.png differ diff --git a/public/cards/security/044-alex-stamos-front.png b/public/cards/security/044-alex-stamos-front.png index 2b3c68e3..e2a00a4c 100644 Binary files a/public/cards/security/044-alex-stamos-front.png and b/public/cards/security/044-alex-stamos-front.png differ diff --git a/public/cards/security/045-dan-geer-back.png b/public/cards/security/045-dan-geer-back.png index 9afe5c4d..f691a647 100644 Binary files a/public/cards/security/045-dan-geer-back.png and b/public/cards/security/045-dan-geer-back.png differ diff --git a/public/cards/security/045-dan-geer-front.png b/public/cards/security/045-dan-geer-front.png index 98f7dd90..21499e6d 100644 Binary files a/public/cards/security/045-dan-geer-front.png and b/public/cards/security/045-dan-geer-front.png differ diff --git a/public/cards/security/046-kevin-mandia-back.png b/public/cards/security/046-kevin-mandia-back.png index 0d7eadf7..5c958546 100644 Binary files a/public/cards/security/046-kevin-mandia-back.png and b/public/cards/security/046-kevin-mandia-back.png differ diff --git a/public/cards/security/046-kevin-mandia-front.png b/public/cards/security/046-kevin-mandia-front.png index e4e68aeb..1fabb795 100644 Binary files a/public/cards/security/046-kevin-mandia-front.png and b/public/cards/security/046-kevin-mandia-front.png differ diff --git a/public/cards/security/047-mikko-hypponen-back.png b/public/cards/security/047-mikko-hypponen-back.png index 49aefc24..c3a6b915 100644 Binary files a/public/cards/security/047-mikko-hypponen-back.png and b/public/cards/security/047-mikko-hypponen-back.png differ diff --git a/public/cards/security/047-mikko-hypponen-front.png b/public/cards/security/047-mikko-hypponen-front.png index d2aa740c..4ef33547 100644 Binary files a/public/cards/security/047-mikko-hypponen-front.png and b/public/cards/security/047-mikko-hypponen-front.png differ diff --git a/public/cards/security/048-robert-m-lee-back.png b/public/cards/security/048-robert-m-lee-back.png index cb39e91f..5dc332b5 100644 Binary files a/public/cards/security/048-robert-m-lee-back.png and b/public/cards/security/048-robert-m-lee-back.png differ diff --git a/public/cards/security/048-robert-m-lee-front.png b/public/cards/security/048-robert-m-lee-front.png index 71b9c743..a02063cc 100644 Binary files a/public/cards/security/048-robert-m-lee-front.png and b/public/cards/security/048-robert-m-lee-front.png differ diff --git a/public/cards/security/049-lesley-carhart-back.png b/public/cards/security/049-lesley-carhart-back.png index 39ae3330..ec156eb1 100644 Binary files a/public/cards/security/049-lesley-carhart-back.png and b/public/cards/security/049-lesley-carhart-back.png differ diff --git a/public/cards/security/049-lesley-carhart-front.png b/public/cards/security/049-lesley-carhart-front.png index 62a29c60..6d91a98f 100644 Binary files a/public/cards/security/049-lesley-carhart-front.png and b/public/cards/security/049-lesley-carhart-front.png differ diff --git a/public/cards/security/050-katie-nickels-front.png b/public/cards/security/050-katie-nickels-front.png index 118e8db8..7e9b953b 100644 Binary files a/public/cards/security/050-katie-nickels-front.png and b/public/cards/security/050-katie-nickels-front.png differ diff --git a/scripts/security-pros.mjs b/scripts/security-pros.mjs index 50bdfde6..59d2972f 100644 --- a/scripts/security-pros.mjs +++ b/scripts/security-pros.mjs @@ -25,6 +25,17 @@ therefore opt-in only and never part of `all`. Running it means accepting that every affected face must be checked by eye before publish. + PORTRAITS ARE CONDITIONED ON A REAL PHOTOGRAPH + ---------------------------------------------- + The first pass generated faces from text prompts alone, so every portrait was an + invented stranger. Generation now takes a verified reference photo as input (see + scripts/security-refs.mjs), and a card with no verified photo gets NO FACE — the + front renders a plate saying so. We do not invent a likeness of a real person. + + The reference photo's photographer is credited: most are CC BY / CC BY-SA, and a + conditioned portrait is a derivative work. data/security-references.json carries + the provenance and the card page prints it. + The roster is src/data/security.ts, imported directly (Node strips the types), so there is no second copy of the data to keep in sync. @@ -72,6 +83,9 @@ process.emitWarning = (warning, ...rest) => { return emitWarning(warning, ...rest); }; +const REFS_DIR = path.join(ROOT, 'assets', 'references', 'security-pros'); +const REF_MANIFEST = path.join(ROOT, 'data', 'security-references.json'); + const DIRS = { approvedArt: path.join(ROOT, 'assets', 'portraits', 'security-pros'), workingArt: path.join(ROOT, 'assets', 'portraits-art', 'security-pros'), @@ -207,6 +221,40 @@ async function editImage(pngBuffer, prompt, { size = '1024x1536' } = {}) { // -------------------------------------------------------------- prompts ---- +function referenceFor(h) { + const p = path.join(REFS_DIR, `${cardId(h.number)}.jpg`); + return fssync.existsSync(p) ? p : null; +} + +let refManifest = null; +function refInfo(slug) { + if (!refManifest) { + refManifest = fssync.existsSync(REF_MANIFEST) + ? JSON.parse(fssync.readFileSync(REF_MANIFEST, 'utf8')) + : []; + } + return refManifest.find((r) => r.slug === slug)?.ref || null; +} + +/** + * Image-to-image instruction. The reference photograph is the subject; the model's + * job is to restyle it, not to reimagine who it is. Says so several ways, because + * the failure we are guarding against is precisely a plausible different face. + */ +function likenessPrompt(h) { + const motif = (h.domains || []).slice(0, 2).join(' and ') || 'computer security'; + return [ + 'Repaint this photograph as a realistic semi-painterly portrait for a premium collectible trading card.', + 'CRITICAL: keep the SAME PERSON. Preserve the exact facial structure, likeness, age, hairstyle, facial hair,', + 'glasses, skin tone and expression of the person in the photograph. Do not beautify, do not de-age,', + 'do not substitute a different face. The result must be recognisable as the same individual.', + 'Head and shoulders, facing the viewer, warm cinematic studio lighting with a cool rim light,', + 'detailed painted realism, dignified and serious.', + `Replace the background with a dark background carrying subtle faint motifs of ${motif}.`, + 'No text, no letters, no logos, no border, no card frame, no watermark.', + ].join(' '); +} + function portraitPrompt(h) { const motif = (h.domains || []).slice(0, 2).join(' and ') || 'computer security'; const era = h.era ? ` Period-accurate to ${h.era}.` : ''; @@ -331,9 +379,20 @@ async function pool(items, label, task) { async function portraits(roster) { await ensureDirs(); console.log(`portraits: ${provider()} / ${provider() === 'openai' ? OPENAI_MODEL() : GEMINI_MODEL()}`); - return pool(roster, 'portraits', async (h) => { + const withRef = roster.filter((h) => referenceFor(h)); + const without = roster.filter((h) => !referenceFor(h)); + if (without.length) { + console.log( + `portraits: ${without.length} have no verified reference photo and will get no face ` + + `(${without.map((h) => h.number).join(', ')}). Run scripts/security-refs.mjs to look again.` + ); + } + return pool(withRef, 'portraits', async (h) => { if (portraitFor(h)) return 'skip'; - const buf = await withRetry(cardId(h.number), () => generateImage(portraitPrompt(h))); + // Image-to-image off the real photograph. Never generateImage() — a text-only + // prompt is what produced the invented faces this pipeline exists to prevent. + const ref = await fs.readFile(referenceFor(h)); + const buf = await withRetry(cardId(h.number), () => editImage(ref, likenessPrompt(h), { size: '1024x1024' })); await fs.writeFile(path.join(DIRS.workingArt, `${cardId(h.number)}.png`), buf); }); } @@ -367,10 +426,11 @@ async function render(roster) { const id = cardId(h.number); try { const art = portraitFor(h); - if (!art) throw new Error('no portrait art — run `portraits` first'); - const uri = `data:image/png;base64,${(await fs.readFile(art)).toString('base64')}`; - await shoot(buildFront(h, uri), id, 'front'); - await shoot(buildBack(h), id, 'back'); + // No art is a legitimate outcome: nobody with no verified reference photo + // gets an invented face. buildFront renders a "no likeness" plate instead. + const uri = art ? `data:image/png;base64,${(await fs.readFile(art)).toString('base64')}` : null; + await shoot(buildFront(h, uri, refInfo(h.slug)), id, 'front'); + await shoot(buildBack(h, refInfo(h.slug)), id, 'back'); ok++; console.log(`✓ ${String(h.number).padStart(2, '0')} ${h.name} (${h.rarity})`); } catch (err) { diff --git a/scripts/security-refs.mjs b/scripts/security-refs.mjs new file mode 100644 index 00000000..63aa105f --- /dev/null +++ b/scripts/security-refs.mjs @@ -0,0 +1,388 @@ +#!/usr/bin/env node +/* + Security Professionals (Series Three) — reference photo resolution. + + WHY THIS EXISTS + --------------- + The first art pass generated portraits from text prompts alone, so every face + was an invented stranger rather than the real person. Portraits are now + conditioned on an actual photograph, and this stage is what finds one. + + IDENTITY IS NOT ASSUMED. A Wikidata search for a name returns humans, not the + right human: "Michael Howard" resolves to the British Conservative leader, + "Jeff Williams" to a Canadian rugby player, "Ivan Ristic" to an art historian. + Shipping those would put a stranger's face on the card, which is worse than the + generic one it replaces. So a candidate is only accepted when it clears + `looksRight()` below, and everything else is reported for a human to judge. + + Resolution order, highest authority first: + 1. OVERRIDES hand-verified, including photos supplied directly + 2. Wikidata P18 free file already wired to a verified item + 3. Commons search free file that exists but is not wired to the item + 4. nothing the card gets no face; it does NOT get an invented one + + Provenance for every accepted photo is written to data/security-references.json + and is what the card page credits. A CC BY / CC BY-SA photo obliges us to name + the photographer, and a derivative portrait inherits that obligation. + + Run: + node scripts/security-refs.mjs resolve # find candidates, write the manifest + node scripts/security-refs.mjs fetch # download accepted references + node scripts/security-refs.mjs report # who is still missing a face +*/ +import fs from 'node:fs/promises'; +import fssync from 'node:fs'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const UA = 'opensourcelegends.com card pipeline (anthony@profullstack.com)'; + +const emitWarning = process.emitWarning.bind(process); +process.emitWarning = (warning, ...rest) => { + const code = + rest.find((r) => typeof r === 'string' && r.startsWith('MODULE_')) ?? + rest.find((r) => r && typeof r === 'object')?.code; + if (code === 'MODULE_TYPELESS_PACKAGE_JSON') return; + return emitWarning(warning, ...rest); +}; + +const REFS_DIR = path.join(ROOT, 'assets', 'references', 'security-pros'); +const MANIFEST = path.join(ROOT, 'data', 'security-references.json'); +const cardId = (n) => `card_${String(n).padStart(3, '0')}`; + +/** + * Hand-verified references. An entry here overrides every lookup. + * + * `license` must describe the actual terms. "supplied" means the site owner + * provided the file directly and is asserting the right to use it: that is not + * a free licence, so the card credits the source rather than claiming CC terms + * the deck cannot back. + */ +const OVERRIDES = { + // Wikidata's P18 for Rivest is a two-person conference photo; a portrait pass + // conditioned on it could easily lock onto the wrong face. This is the solo crop. + 'ron-rivest': { commonsFile: 'Rivest cropped.jpg' }, + + // CC-licensed conference photography, found via Openverse where Commons had + // nothing. Alexandre Dulaunoy shot hack.lu 2006 and released the set CC BY-SA. + 'wietse-venema': { + url: 'https://live.staticflickr.com/104/276434076_581ed1215d_b.jpg', + credit: 'Alexandre Dulaunoy', + license: 'CC BY-SA 2.0', + licenseUrl: 'https://creativecommons.org/licenses/by-sa/2.0/', + sourceUrl: 'https://www.flickr.com/photos/31797858@N00/276434076', + note: 'hack.lu 2006. Supersedes the Wikidata P18 file, which shows a different man.', + }, + 'renaud-deraison': { + url: 'https://live.staticflickr.com/122/276307114_f29f0a38ec_b.jpg', + credit: 'Alexandre Dulaunoy', + license: 'CC BY-SA 2.0', + licenseUrl: 'https://creativecommons.org/licenses/by-sa/2.0/', + sourceUrl: 'https://www.flickr.com/photos/31797858@N00/276307114', + note: 'hack.lu 2006 opening speech.', + }, + 'thomas-ptacek': { + url: 'https://live.staticflickr.com/2733/4302617844_52625091af_b.jpg', + credit: 'cfournie', + license: 'CC BY 2.0', + licenseUrl: 'https://creativecommons.org/licenses/by/2.0/', + sourceUrl: 'https://www.flickr.com/photos/37932109@N05/4302617844', + }, + + // Official bio / press photographs, used as portrait references on the site + // owner's authorisation. These are not freely licensed, so the manifest records + // where each came from rather than asserting CC terms the deck cannot back. + 'adam-shostack': { + url: 'https://shostack.org/img/site/Adam-Shostack-headshot.png', + credit: 'Adam Shostack', + license: 'supplied', + sourceUrl: 'https://shostack.org/about/adam', + note: 'Headshot published on his own site.', + }, + 'kevin-mandia': { + url: 'https://ballisticventures.com/wp-content/uploads/2024/06/Kevin-Mandia.png', + credit: 'Ballistic Ventures', + license: 'supplied', + sourceUrl: 'https://www.ballisticventures.com/team/kevin-mandia', + note: 'Partner bio photo.', + }, + 'mark-curphey': { + url: 'https://user-images.githubusercontent.com/143776/193322138-009524de-bae1-4190-989d-2d729ad0beb8.png', + credit: 'Mark Curphey', + license: 'supplied', + sourceUrl: 'https://www.crash-override.com/', + note: 'Portrait published by his own company.', + }, + 'martin-roesch': { + url: 'https://media.kasperskycontenthub.com/wp-content/uploads/sites/103/2013/07/07043722/sourcefire.jpg', + credit: 'Sourcefire press photo', + license: 'supplied', + sourceUrl: 'https://threatpost.com/', + note: 'Sourcefire-era press photograph. Supersedes the Commons files, which are a different Martin.', + }, + + 'jeremiah-grossman': { + url: 'https://cdn.prod.website-files.com/62deb8a5505ca121b1665bbe/63cf05d85cad9419bfa83096_Photo%20Oct%2003%202022%2C%206%2054%2000%20AM-p-800.jpg', + credit: 'Jeremiah Grossman', + license: 'supplied', + sourceUrl: 'https://www.jeremiahgrossman.com/', + note: 'Publicity photo from his own site, supplied by the site owner as reference.', + }, +}; + +/** Wikidata items confirmed to be the wrong human — never accept these. */ +const REJECT_QIDS = new Set([ + 'Q325381', // Michael Howard, British politician + 'Q38051181', // Robert Hansen, Danish crime novelist 1883-1957 +]); + +/** + * Auto-resolution found something, a human looked at it, and it did not hold up. + * These get no face. Recorded with the reason so nobody re-adds them by rerunning + * the resolver and trusting the output. + */ +const REJECTED = { + 'jeff-williams': + '"Jeff Williams photo.jpg" is one of several unrelated Jeff Williamses. Cannot confirm it is the Contrast Security founder.', + 'gary-mcgraw': + 'Only candidate is a group briefing shot with audience heads across the frame; the other Commons hits are unrelated US Navy photographs.', + 'michael-howard': + 'Every Commons hit is a different Michael Howard — the British politician, or a young man with no connection to the Microsoft SDL author.', +}; + +const j = async (url) => { + const r = await fetch(url, { headers: { 'User-Agent': UA } }); + if (!r.ok) throw new Error(`${r.status} for ${url}`); + return r.json(); +}; + +async function loadRoster() { + // src/data/security.ts statically imports the manifest, so it has to exist + // before the roster can be loaded — including on the very first resolve, and + // after someone deletes it to force a clean run. + if (!fssync.existsSync(MANIFEST)) { + await fs.mkdir(path.dirname(MANIFEST), { recursive: true }); + await fs.writeFile(MANIFEST, '[]\n'); + } + const mod = await import(path.join(ROOT, 'src', 'data', 'security.ts')); + return mod.pros; +} + +/** + * Does this Wikidata item plausibly describe the person on the card? + * Deliberately conservative: a miss costs a manual lookup, a false accept ships + * a stranger's face. + */ +const FIELD = + /secur|crypt|comput|software|engineer|scientist|hacker|program|internet|research|professor|technolog|inform|network|business|writer/i; + +function looksRight(entity, pro) { + if (!entity) return false; + const claims = entity.claims || {}; + if (!(claims.P31 || []).some((c) => c.mainsnak?.datavalue?.value?.id === 'Q5')) return false; + + const label = entity.labels?.en?.value || ''; + const desc = entity.descriptions?.en?.value || ''; + const aliases = (entity.aliases?.en || []).map((a) => a.value); + + // Surname must actually appear — "Mark Dowd" matching "Mark Dowdall" is a miss. + const surname = pro.name.split(/\s+/).pop().toLowerCase(); + const names = [label, ...aliases].map((s) => s.toLowerCase()); + if (!names.some((n) => n.split(/\s+/).includes(surname))) return false; + + // A dead-before-the-internet birth date rules the person out. + const born = claims.P569?.[0]?.mainsnak?.datavalue?.value?.time; + if (born) { + const year = Number(String(born).slice(1, 5)); + if (year && year < 1920) return false; + } + + return FIELD.test(desc) || FIELD.test(label); +} + + +const norm = (s) => + String(s) + .toLowerCase() + .replace(/[._-]+/g, ' ') + .replace(/[^a-z0-9 ]+/g, '') + .replace(/\s+/g, ' ') + .trim(); + +/** + * True only when the filename *begins* with the person's full name, so + * "Alex Sotirov.jpg" and "J. Alex Halderman - 2018.jpg" pass while + * "Martin Wagenleiter (Roesch) 861x1200.jpg" and "HT-Jeffrey-Williams.jpg" do not. + */ +function filenameOpensWithName(filename, name) { + const f = norm(filename.replace(/\.[a-z0-9]+$/i, '')); + return f.startsWith(norm(name)); +} + +/** Commons file -> direct URL plus the licence and author we are obliged to credit. */ +async function commonsMeta(filename) { + const d = await j( + `https://commons.wikimedia.org/w/api.php?action=query&format=json&prop=imageinfo&iiprop=url|extmetadata&iiurlwidth=1024&titles=${encodeURIComponent('File:' + filename)}` + ); + const page = Object.values(d.query?.pages || {})[0]; + const info = page?.imageinfo?.[0]; + if (!info) return null; + const meta = info.extmetadata || {}; + const strip = (v) => String(v?.value ?? '').replace(/<[^>]*>/g, '').trim(); + // A 19th-century photographer credit means the search found a historical + // namesake, not the person on the card. + const dateRaw = strip(meta.DateTimeOriginal) || strip(meta.DateTime); + const year = Number((dateRaw.match(/\b(1[6-9]\d{2}|20\d{2})\b/) || [])[1]); + if (year && year < 1950) return null; + + return { + url: info.thumburl || info.url, + credit: strip(meta.Artist) || 'Unknown', + license: strip(meta.LicenseShortName) || 'unknown', + licenseUrl: strip(meta.LicenseUrl) || '', + sourceUrl: info.descriptionurl, + file: filename, + }; +} + +async function resolve() { + const roster = await loadRoster(); + const manifest = []; + + for (const pro of roster) { + const row = { number: pro.number, slug: pro.slug, name: pro.name, ref: null, candidates: [] }; + + if (REJECTED[pro.slug]) { + row.rejected = REJECTED[pro.slug]; + manifest.push(row); + console.log(`${String(pro.number).padStart(2)} ${pro.name.padEnd(22)} REJECTED ${row.rejected.slice(0, 58)}`); + continue; + } + + if (OVERRIDES[pro.slug]) { + const o = OVERRIDES[pro.slug]; + row.ref = o.commonsFile + ? { ...(await commonsMeta(o.commonsFile)), ...o, via: 'override' } + : { ...o, via: 'override' }; + manifest.push(row); + console.log(`${String(pro.number).padStart(2)} ${pro.name.padEnd(22)} OVERRIDE ${row.ref.license}`); + continue; + } + + let entity = null; + let qid = null; + try { + const s = await j( + `https://www.wikidata.org/w/api.php?action=wbsearchentities&search=${encodeURIComponent(pro.name)}&language=en&format=json&limit=6&type=item` + ); + for (const hit of s.search || []) { + if (REJECT_QIDS.has(hit.id)) continue; + const e = await j(`https://www.wikidata.org/wiki/Special:EntityData/${hit.id}.json`); + const ent = e.entities?.[hit.id]; + if (looksRight(ent, pro)) { + entity = ent; + qid = hit.id; + break; + } + } + } catch (err) { + row.error = String(err.message).slice(0, 80); + } + + const p18 = entity?.claims?.P18?.[0]?.mainsnak?.datavalue?.value; + if (p18) { + const meta = await commonsMeta(p18); + if (meta) { + row.ref = { ...meta, qid, via: 'wikidata-p18' }; + row.desc = entity.descriptions?.en?.value || ''; + } + } + + // No P18: a free file may still exist on Commons, unlinked. Containing the + // name somewhere is far too weak a test — "Martin Wagenleiter (Roesch)" and + // "Merseyside PTE 40th anniversary" both matched that way, and a search for + // "Robert M. Lee" happily returns a daguerreotype of the Confederate general. + // So the filename must OPEN with the person's full name. + if (!row.ref) { + try { + const s = await j( + `https://commons.wikimedia.org/w/api.php?action=query&format=json&generator=search&gsrnamespace=6&gsrlimit=10&gsrsearch=${encodeURIComponent(pro.name)}` + ); + for (const p of Object.values(s.query?.pages || {})) { + const f = p.title.replace(/^File:/, ''); + if (!/\.(jpe?g|png)$/i.test(f)) continue; + if (!filenameOpensWithName(f, pro.name)) continue; + row.candidates.push(f); + } + if (row.candidates.length) { + const meta = await commonsMeta(row.candidates[0]); + if (meta) row.ref = { ...meta, qid, via: 'commons-search' }; + } + } catch {} + } + + manifest.push(row); + const tag = row.ref ? row.ref.via.toUpperCase().padEnd(10) : 'NONE '; + console.log( + `${String(pro.number).padStart(2)} ${pro.name.padEnd(22)} ${tag} ${row.ref ? `${row.ref.license} | ${row.ref.credit.slice(0, 34)}` : (row.desc || '')}` + ); + } + + await fs.mkdir(path.dirname(MANIFEST), { recursive: true }); + await fs.writeFile(MANIFEST, JSON.stringify(manifest, null, 2) + '\n'); + const got = manifest.filter((m) => m.ref).length; + console.log(`\nresolve: ${got}/${manifest.length} have a reference photo -> ${path.relative(ROOT, MANIFEST)}`); + const none = manifest.filter((m) => !m.ref); + console.log(`no photo (${none.length}) — these get no face, never an invented one:`); + for (const m of none) console.log(` ${String(m.number).padStart(2)} ${m.name}${m.rejected ? ' — ' + m.rejected.slice(0, 70) : ''}`); +} + +async function fetchRefs() { + const manifest = JSON.parse(await fs.readFile(MANIFEST, 'utf8')); + await fs.mkdir(REFS_DIR, { recursive: true }); + let ok = 0; + const failed = []; + for (const row of manifest.filter((m) => m.ref)) { + const dest = path.join(REFS_DIR, `${cardId(row.number)}.jpg`); + if (fssync.existsSync(dest)) { + ok++; + continue; + } + try { + const r = await fetch(row.ref.url, { headers: { 'User-Agent': UA } }); + if (!r.ok) throw new Error(String(r.status)); + await fs.writeFile(dest, Buffer.from(await r.arrayBuffer())); + ok++; + console.log(`✓ ${String(row.number).padStart(2)} ${row.name}`); + } catch (err) { + failed.push(`${row.number} ${row.name}: ${err.message}`); + } + } + console.log(`fetch: ${ok} references in ${path.relative(ROOT, REFS_DIR)}, ${failed.length} failed`); + for (const f of failed) console.log(' ✗ ' + f); +} + +async function report() { + const manifest = JSON.parse(await fs.readFile(MANIFEST, 'utf8')); + const byLicense = {}; + for (const m of manifest.filter((x) => x.ref)) { + byLicense[m.ref.license] = (byLicense[m.ref.license] || 0) + 1; + } + console.log('references by licence:', byLicense); + const missing = manifest.filter((m) => !m.ref); + console.log(`\n${missing.length} with no reference photo — these get no face, not an invented one:`); + for (const m of missing) console.log(` ${String(m.number).padStart(2)} ${m.name}`); +} + +const cmd = process.argv[2] || 'resolve'; +const fns = { resolve, fetch: fetchRefs, report }; +if (!fns[cmd]) { + console.error(`unknown command: ${cmd}\n\n resolve | fetch | report`); + process.exit(1); +} +fns[cmd]().catch((err) => { + console.error('ERROR:', err.message); + process.exit(1); +}); diff --git a/scripts/security-template.mjs b/scripts/security-template.mjs index 1ebc489c..df8ef757 100644 --- a/scripts/security-template.mjs +++ b/scripts/security-template.mjs @@ -85,7 +85,7 @@ const shell = (accent) => `${FONTS} .crest { display:flex; align-items:center; gap:7px; } .crest .t { font-family:'Oswald'; color:#fff; font-weight:700; font-size:11px; letter-spacing:.6px; line-height:1.05; }`; -export function buildFront(pro, portraitDataUri) { +export function buildFront(pro, portraitDataUri, ref) { const accent = accentOf(pro); const kw = traits(pro).join('  ·  '); const alias = pro.handle @@ -98,6 +98,13 @@ export function buildFront(pro, portraitDataUri) { invented text. The caption band is now sized to what it actually holds. */ .portrait { position:absolute; left:14px; right:14px; top:14px; height:72%; border-radius:16px; overflow:hidden; box-shadow:inset 0 0 0 2px ${accent}55; } .portrait img { width:100%; height:100%; object-fit:cover; object-position:center 18%; } + /* A card with no verified reference photo says so, rather than carrying an + invented face. This is a deliberate, visible gap. */ + .nolikeness { width:100%; height:100%; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:16px; + background:radial-gradient(90% 70% at 50% 35%, #171d26 0%, #0c1016 70%); } + .nolikeness .wm { opacity:.12; position:absolute; } + .nolikeness .initials { font-family:'Oswald'; font-weight:700; font-size:96px; letter-spacing:6px; color:${accent}; opacity:.85; z-index:1; } + .nolikeness .nlnote { font-family:'JetBrains Mono',monospace; font-size:15px; line-height:1.5; color:#8b93a5; text-align:center; letter-spacing:.6px; z-index:1; } .portrait::after { content:''; position:absolute; left:0; right:0; bottom:0; height:34%; background:linear-gradient(180deg,transparent,rgba(11,15,17,.94)); } .num { position:absolute; top:18px; left:20px; z-index:4; font-family:'Oswald'; font-weight:700; font-size:40px; line-height:1; padding:2px 14px 4px; background:${accent}; color:#0a0810; border-radius:0 0 12px 0; clip-path:polygon(0 0,100% 0,84% 100%,0 100%); } .crestbox { position:absolute; top:20px; right:20px; z-index:4; } @@ -110,7 +117,13 @@ export function buildFront(pro, portraitDataUri) { .kw { font-family:'Oswald'; flex:1; text-align:center; color:#cfd2dc; font-weight:600; font-size:15px; letter-spacing:1.2px; }
-
+
${ + portraitDataUri + ? `` + : `${emblem(accent, 150)}${esc( + pro.name.split(/\s+/).map((w) => w[0]).join('') + )}no freely-licensed photograph
of this person was found
` + }
${pad2(pro.number)}
${emblem(accent, 24)}SECURITY
PROFESSIONALS
${esc(pro.name)}
${alias}
${esc(pro.title)}
@@ -118,7 +131,7 @@ export function buildFront(pro, portraitDataUri) {
`; } -export function buildBack(pro) { +export function buildBack(pro, ref) { const accent = accentOf(pro); const rarity = String(pro.rarity).toUpperCase(); const domains = (pro.domains || []).slice(0, 5).map((d) => `
  • ${esc(d)}
  • `).join(''); @@ -167,6 +180,9 @@ export function buildBack(pro) { .foot { border-top:1px solid ${accent}55; padding-top:11px; color:#9a9ead; font-size:11px; letter-spacing:.3px; } .foot .r { display:flex; justify-content:space-between; align-items:center; } .foot b { color:#d7d9e2; } + /* CC BY and CC BY-SA reference photos oblige us to name the photographer, and + a conditioned portrait is a derivative work. */ + .credit { color:#7f8698; font-size:10px; letter-spacing:.2px; }
    @@ -189,6 +205,13 @@ export function buildBack(pro) {
    ERA ${esc(pro.era)}NATIONALITY ${esc(pro.nationality)}
    KNOWN FOR ${esc(pro.knownFor)}${lockGlyph(accent, 18)}
    + ${ + ref + ? `
    PORTRAIT AFTER A PHOTOGRAPH BY ${esc( + ref.credit + )}${ref.license && ref.license !== 'supplied' ? ` · ${esc(ref.license)}` : ''}
    ` + : '' + }
    `; } diff --git a/src/app/security-professionals/[slug]/page.tsx b/src/app/security-professionals/[slug]/page.tsx index f6ef4dd4..5457ae07 100644 --- a/src/app/security-professionals/[slug]/page.tsx +++ b/src/app/security-professionals/[slug]/page.tsx @@ -1,13 +1,15 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; import CardDetail from '@/components/CardDetail'; -import { pros, getPro, rarityLabel, statusLabel } from '@/data/security'; +import { pros, getPro, rarityLabel, statusLabel, getPortraitCredit } from '@/data/security'; import { site } from '@/data/site'; type Params = { params: Promise<{ slug: string }> }; +// Only illustrated cards get a page — an entry with no face has nothing to show, +// and listing it here would prerender 404s. export function generateStaticParams() { - return pros.map((p) => ({ slug: p.slug })); + return pros.filter((p) => p.front).map((p) => ({ slug: p.slug })); } export async function generateMetadata({ params }: Params): Promise { @@ -37,8 +39,8 @@ export default async function ProPage({ params }: Params) { const prev = i > 0 ? pros[i - 1] : undefined; const next = i < pros.length - 1 ? pros[i + 1] : undefined; - // Art is published for the whole set, but keep the page honest if a face is - // ever missing rather than rendering a broken image. + // A card page exists only once its art does. While the set is unillustrated this + // is every slug, which is intended — better a 404 than a page with no card on it. if (!p.front || !p.back) notFound(); const jsonLd = { @@ -92,6 +94,7 @@ export default async function ProPage({ params }: Params) { { label: 'Influence', value: p.influence }, ]} sources={p.sources} + portraitCredit={getPortraitCredit(p.slug)} front={p.front} back={p.back} prev={prev?.front ? { slug: prev.slug, name: prev.name, front: prev.front } : undefined} diff --git a/src/app/security-professionals/page.tsx b/src/app/security-professionals/page.tsx index 88744985..24db57b9 100644 --- a/src/app/security-professionals/page.tsx +++ b/src/app/security-professionals/page.tsx @@ -42,6 +42,11 @@ const CRITERIA = [ head: 'Nobody appears twice', body: 'Series Two documents the people who broke things. This one documents the people whose job was to hold. A name in Hacking Legends cannot also be here.', }, + { + tag: 'Real faces', + head: 'No invented likenesses', + body: 'A portrait is painted from an identified photograph of that person, credited to the photographer. Where no properly licensed photo exists, the card carries no face. An earlier version of this set generated faces from text alone; those were fabrications and they were withdrawn.', + }, ]; export default function SecurityProfessionalsPage() { @@ -65,15 +70,11 @@ export default function SecurityProfessionalsPage() { line rather than crossing it.

    - {illustratedCount >= totalPlanned - ? `All ${totalPlanned} cards are illustrated. The set was built in public and the - roster stays open to argument — every stat line and scouting report is a pull - request away from changing.` - : `This set is being built in public. Copy is drafted, stats are in flux, and ${ - illustratedCount === 0 - ? 'not a single portrait has been illustrated yet' - : `${illustratedCount} of ${totalPlanned} cards are illustrated so far` - }. What you see below is the working roster.`} + Every portrait here is painted from a real, identified photograph of the person, + and the photographer is credited on the card. {totalPlanned - illustratedCount} of + the {totalPlanned} have no freely-licensed photograph we could find, so those cards + carry no face rather than an invented one. If you can point us at a properly + licensed photo of someone, that is the single most useful thing you can contribute.

    @@ -106,7 +107,7 @@ export default function SecurityProfessionalsPage() { {lockedCount}/{totalPlanned}
    - Artwork rendered + Portraits from a verified photo

    What ships next

      - {illustratedCount >= totalPlanned ? ( - <> -
    1. Take nominations and re-cut any slot the argument goes against.
    2. -
    3. Re-render any card whose copy or stat line changes in review.
    4. -
    5. Print run and packs, alongside the open-licensed downloads.
    6. - - ) : ( - <> -
    7. Take nominations on the {totalPlanned} names and settle the disputed slots.
    8. -
    9. Lock the stat lines and freeze the list.
    10. -
    11. Illustrate fronts and backs, same treatment as Series One and Two.
    12. -
    13. Card pages, print run and packs, alongside the open-licensed downloads.
    14. - - )} +
    15. + Find licensed photographs for the {totalPlanned - illustratedCount} cards with + no face. Pull requests welcome, and so are pointers. +
    16. +
    17. Take nominations and re-cut any slot the argument goes against.
    18. +
    19. Re-render any card whose copy or stat line changes in review.
    20. +
    21. Print run and packs, alongside the open-licensed downloads.
    diff --git a/src/components/CardDetail.tsx b/src/components/CardDetail.tsx index e6c13f82..872ed731 100644 --- a/src/components/CardDetail.tsx +++ b/src/components/CardDetail.tsx @@ -9,6 +9,18 @@ const CARD_VERSION = 'g1'; export type DetailStat = { label: string; value: number }; export type DetailSource = { label: string; url: string }; +/** + * The photograph a portrait was painted from. Most are CC BY or CC BY-SA, which + * oblige us to name the photographer — and a conditioned portrait is a derivative + * work, so the obligation travels with the card. + */ +export type DetailPortraitCredit = { + credit: string; + license: string; + licenseUrl?: string; + sourceUrl?: string; +}; + export type DetailNeighbour = { slug: string; name: string; front: string }; export type CardDetailProps = { @@ -38,6 +50,7 @@ export type CardDetailProps = { /** Secondary stat bars. Series Two prints four; Series One has none. */ stats?: DetailStat[]; sources?: DetailSource[]; + portraitCredit?: DetailPortraitCredit; statusLabel?: string; front: string; back: string; @@ -76,6 +89,7 @@ export default function CardDetail(props: CardDetailProps) { noteLabel = 'Collector’s note', stats, sources, + portraitCredit, statusLabel, front, back, @@ -229,6 +243,37 @@ export default function CardDetail(props: CardDetailProps) {
    )} + {portraitCredit && ( +
    +

    Portrait

    +

    + Painted from a photograph of {name} by{' '} + {portraitCredit.sourceUrl ? ( + + {portraitCredit.credit} + + ) : ( + portraitCredit.credit + )} + {portraitCredit.license === 'supplied' ? ( + ', supplied by the subject.' + ) : ( + <> + , licensed{' '} + {portraitCredit.licenseUrl ? ( + + {portraitCredit.license} + + ) : ( + portraitCredit.license + )} + . + + )} +

    +
    + )} + {sources && sources.length > 0 && (

    Sources

    diff --git a/src/data/security.ts b/src/data/security.ts index 0911938d..371f256f 100644 --- a/src/data/security.ts +++ b/src/data/security.ts @@ -1,6 +1,13 @@ -// Security Professionals — Series Three. IN PROGRESS: copy is written, art is not started. -// Hand-curated (not auto-generated). `front`/`back` will be written by the publish step -// once portraits exist — do not hand-edit those two fields. +// Security Professionals — Series Three. Copy is final; ART IS WITHDRAWN. +// Hand-curated (not auto-generated). `front`/`back` are written by the publish step — +// do not hand-edit those two fields. +// +// 2026-09-08: the first art pass was generated from text prompts alone, so every portrait +// was an invented face rather than a likeness of the real person. For a set that presents +// itself as documented history that is a fabrication, so all 50 faces were withdrawn and +// the source portraits deleted. The replacement pass conditions generation on a real, +// freely-licensed reference photo and credits the photographer; anyone with no such photo +// gets no face at all rather than an invented one. // // The brief for this set is deliberately narrower than Series Two. Hacking Legends // documents the people who broke things; this one documents the people whose job was to @@ -1357,3 +1364,27 @@ export const featuredPros = pros.filter((p) => p.status === 'locked').slice(0, 6 export function getPro(slug: string) { return pros.find((p) => p.slug === slug); } + +export type PortraitCredit = { + credit: string; + license: string; + licenseUrl?: string; + sourceUrl?: string; +}; + +type RefRow = { slug: string; ref?: (PortraitCredit & { url?: string }) | null }; + +/** + * Photographer credits for the portraits, written by scripts/security-refs.mjs. + * Every portrait in this set is painted from a real, identified photograph — most + * under CC BY or CC BY-SA, which require attribution, and a conditioned portrait + * is a derivative work. A card with no entry here has no face at all. + */ +import refs from '../../data/security-references.json' with { type: 'json' }; + +export function getPortraitCredit(slug: string): PortraitCredit | undefined { + const row = (refs as RefRow[]).find((r) => r.slug === slug); + if (!row?.ref) return undefined; + const { credit, license, licenseUrl, sourceUrl } = row.ref; + return { credit, license, licenseUrl, sourceUrl }; +}