Scrub: the founder list leaves the repository in every encoding; the never-push founder check decodes base64, hex and .b64 blobs too (7 October 2026, 21:4x UK)
tools/ci/founder-strings.b64 was the founder check's pattern list base64-encoded: a grep could not read it, any reader of the public host could (git.igneum.network was public from 21:26 UK; read off at 21:36). The list now lives only in a private file (~/.config/igneum/founder-strings on the Mac, /srv/discord-hooks/founder-strings on build-1 for the Discord guard; $IGNEUM_FOUNDER_STRINGS overrides) and site/forbidden-strings.txt carries no encoded copy. Readers: founder-strings-check.sh (skips with a line where the file is absent; the Mac's hook is the guard), site/scrub.mjs and launch-gates-check.mjs (the private file's patterns added where it exists), discord-hooks.mjs (three locations; the test writes a fixture list and loads the module after it), fresh-repo.sh (the private file; drops tools/ci/founder-strings.b64 from every commit; rewrites the base64 of every list regex out of every blob and scans for it). The check's second pass (main's addition): every base64 literal of 24 characters or more, every hex literal of 24 or more and every *.b64 file is decoded and scanned, so no encoding hides a term again; the self-test plants each fixture sample in plain text, in a .b64 file, as a base64 literal and as a hex literal, each caught and named, and a tree without the list skips with its line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
07e21189dd
commit
ff0d0dde61
7 changed files with 121 additions and 76 deletions
|
|
@ -13,16 +13,9 @@ intake[_-]?key
|
||||||
Tailscale
|
Tailscale
|
||||||
tailscale
|
tailscale
|
||||||
ts\.net
|
ts\.net
|
||||||
# the founder's name, logins and the earlier businesses, base64-encoded so the list is not itself a hit (a `b64:` line is decoded
|
# the founder's name, logins and the earlier businesses are NOT in this file in any encoding (a base64 line is a disclosure to any reader, found
|
||||||
# and compiled case-insensitive by site/scrub.mjs and tools/ci/launch-gates-check.mjs; the same patterns live in tools/ci/founder-strings.b64)
|
# 7 October 2026, 21:3x UK, on the public host): they live in the private list ~/.config/igneum/founder-strings, read by site/scrub.mjs,
|
||||||
b64:[encoded-pattern-removed]
|
# tools/ci/launch-gates-check.mjs and tools/ci/founder-strings-check.sh where it exists; the Mac's pre-push hook is the guard on every push.
|
||||||
b64:[encoded-pattern-removed]
|
|
||||||
b64:[encoded-pattern-removed]
|
|
||||||
b64:[encoded-pattern-removed]
|
|
||||||
b64:[encoded-pattern-removed]
|
|
||||||
b64:[encoded-pattern-removed]
|
|
||||||
b64:[encoded-pattern-removed]
|
|
||||||
b64:[encoded-pattern-removed]
|
|
||||||
Hetzner
|
Hetzner
|
||||||
igneum-seed
|
igneum-seed
|
||||||
/root/
|
/root/
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
// so the build runs the same on this Mac, on Vercel and on the CI runner. The build fails if any pattern in
|
// so the build runs the same on this Mac, on Vercel and on the CI runner. The build fails if any pattern in
|
||||||
// site/forbidden-strings.txt survives, so a private name, host or address can never reach the page.
|
// site/forbidden-strings.txt survives, so a private name, host or address can never reach the page.
|
||||||
import { readFileSync } from 'node:fs';
|
import { readFileSync } from 'node:fs';
|
||||||
|
import { homedir } from 'node:os';
|
||||||
import { join, dirname } from 'node:path';
|
import { join, dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
const here = dirname(fileURLToPath(import.meta.url));
|
const here = dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
@ -59,13 +60,21 @@ const RULES = [
|
||||||
[/\(local time, UTC\+1\)/g, '(UTC)'],
|
[/\(local time, UTC\+1\)/g, '(UTC)'],
|
||||||
[/\bB[S]T\b/g, 'UTC'],
|
[/\bB[S]T\b/g, 'UTC'],
|
||||||
];
|
];
|
||||||
|
// the private founder list (~/.config/igneum/founder-strings or $IGNEUM_FOUNDER_STRINGS; perl regex, a tab, a sample per row; # comments);
|
||||||
|
// absent here: no founder patterns from this reader (the Mac's pre-push hook carries the file and is the guard)
|
||||||
|
function founderPatternsFromFile() {
|
||||||
|
try {
|
||||||
|
const f = process.env.IGNEUM_FOUNDER_STRINGS || join(homedir(), '.config', 'igneum', 'founder-strings');
|
||||||
|
return readFileSync(f, 'utf8').split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')).map((l) => new RegExp(l.split('\t')[0], 'i'));
|
||||||
|
} catch { return []; }
|
||||||
|
}
|
||||||
export function scrubBench(text) {
|
export function scrubBench(text) {
|
||||||
let out = text;
|
let out = text;
|
||||||
for (const [re, rep] of RULES) out = out.replace(re, rep);
|
for (const [re, rep] of RULES) out = out.replace(re, rep);
|
||||||
const pats = readFileSync(join(here, 'forbidden-strings.txt'), 'utf8').split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'))
|
const pats = readFileSync(join(here, 'forbidden-strings.txt'), 'utf8').split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'))
|
||||||
.map(l => (l.startsWith('b64:') ? new RegExp(Buffer.from(l.slice(4), 'base64').toString('utf8'), 'i') : new RegExp(l))); // a b64: line is an encoded, case-insensitive pattern
|
.map(l => new RegExp(l)).concat(founderPatternsFromFile()); // plus the private founder list where it exists
|
||||||
const hits = [];
|
const hits = [];
|
||||||
out.split('\n').forEach((line, i) => { for (const p of pats) if (p.test(line)) { hits.push(`${i + 1}: ${p.source.startsWith('(?<!') || p.flags.includes('i') ? '(an encoded founder pattern)' : p.source}`); break; } });
|
out.split('\n').forEach((line, i) => { for (const p of pats) if (p.test(line)) { hits.push(`${i + 1}: ${p.flags.includes('i') ? '(a founder pattern from the private list)' : p.source}`); break; } });
|
||||||
if (hits.length) throw new Error(`scrub: forbidden strings remain on the bench page:\n${hits.slice(0, 20).join('\n')}`);
|
if (hits.length) throw new Error(`scrub: forbidden strings remain on the bench page:\n${hits.slice(0, 20).join('\n')}`);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,37 +1,49 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# No founder name, personal login, earlier business or personal address in any tracked text file (the pre-public scrub,
|
# No founder name, personal login, earlier business or personal address in any tracked text file, in plain text OR in an encoding
|
||||||
# 7 October 2026, main's item (4): forbidden strings over tracked files on every merge, known-failed first). The identity
|
# (the pre-public scrub, 7 October 2026; a never-push class since 20:5x UK: every push, every branch). The identity check
|
||||||
# check (tools/ci/identity-check.sh) reads the public EXPORT list; this one reads EVERY tracked file, because the repository
|
# (tools/ci/identity-check.sh) reads the public EXPORT list; this one reads EVERY tracked file, because the repository itself
|
||||||
# itself goes public at the testnet (docs/fud-fixes.md section 5).
|
# is public (git.igneum.network).
|
||||||
#
|
#
|
||||||
# The patterns are not written in this tree in plain text: a plaintext list would be the hit it looks for. They live
|
# The patterns are NOT in the repository in any form. They live in a private file, ~/.config/igneum/founder-strings
|
||||||
# base64-encoded in tools/ci/founder-strings.b64 (one decoded line per pattern: perl regex, a tab, a sample the self-test plants;
|
# ($IGNEUM_FOUNDER_STRINGS overrides; one row per pattern: perl regex, a tab, a sample the self-test plants; # comments), on the
|
||||||
# case-insensitive; # comments ignored)
|
# Mac that pushes. A base64 copy in the tree (tools/ci/founder-strings.b64, 19:5x to 21:3x UK) was a disclosure to any reader of
|
||||||
# and are decoded into a private temporary file at run time. The login's pre-rename spelling is in the list too (main's ruling,
|
# the public host and is gone from every commit. Where the file is absent (a hosted CI runner, a box) the check prints a skip
|
||||||
# 7 October 2026: the public tree names igneum-labs only); the fresh-repository step rewrites it in the history (tools/repo/fresh-repo.sh).
|
# line and passes; the Mac's pre-push hook, which has the file, is the guard.
|
||||||
#
|
#
|
||||||
# tools/ci/founder-strings-check.sh # exit 1 with file:line for every hit over the tracked text files
|
# Two passes over every tracked text file: the plain text, then every encoded blob decoded and scanned (base64 literals of 24
|
||||||
# tools/ci/founder-strings-check.sh --self-test # a fixture tree with one hit per pattern class fails and names the file; a clean
|
# characters or more, every *.b64 file whole, hex literals of 24 characters or more), so an encoding never hides a term again.
|
||||||
# # fixture passes; the encoded list decodes to at least five patterns
|
#
|
||||||
|
# tools/ci/founder-strings-check.sh # exit 1 with file:line for every hit (decoded hits say so); exit 0 with a skip line without the list
|
||||||
|
# tools/ci/founder-strings-check.sh --self-test # with a FIXTURE list of made-up names (never the real file): a clean tree passes; each
|
||||||
|
# # sample planted in plain text, in a .b64 file, as a base64 literal and as a hex literal is
|
||||||
|
# # caught and names its file; a tree without the list skips with the line
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
LIST="$HERE/founder-strings.b64"
|
LIST="${IGNEUM_FOUNDER_STRINGS:-$HOME/.config/igneum/founder-strings}"
|
||||||
REPO="${FOUNDER_CHECK_REPO:-$(cd "$HERE/../.." && pwd)}"
|
REPO="${FOUNDER_CHECK_REPO:-$(cd "$HERE/../.." && pwd)}"
|
||||||
|
|
||||||
decode() { # [samples]: the regexes (or, with "samples", the sample per regex), one per line, into a 0600 file whose name is printed
|
rows() { grep -vE '^\s*(#|$)' "$LIST"; } # the live rows
|
||||||
local f col=1; [ "${1:-}" = samples ] && col=2
|
scan() { # <repo> <list>: plain pass, then the decoded pass; prints "file:line:text" or "file:line:(decoded <kind>) text"; exit 1 on any hit
|
||||||
f="$(mktemp)"; chmod 600 "$f"
|
local repo="$1" list="$2" pats hits
|
||||||
base64 -d < "$LIST" | grep -vE '^\s*(#|$)' | cut -f"$col" > "$f"
|
pats="$(mktemp)"; chmod 600 "$pats"; grep -vE '^\s*(#|$)' "$list" | cut -f1 > "$pats"
|
||||||
echo "$f"
|
hits="$(cd "$repo" && git ls-files -z -- . ':!*.png' ':!*.jpg' ':!*.jpeg' ':!*.gif' ':!*.ico' ':!*.woff' ':!*.woff2' ':!*.ttf' ':!*.pdf' ':!*.zip' ':!*.bin' \
|
||||||
}
|
|
||||||
scan() { # <repo>: every tracked text file against the decoded list; prints file:line:text, exit 1 on any hit (perl: the Mac's grep has no -P)
|
|
||||||
local repo="$1" pats hits
|
|
||||||
pats="$(decode)"
|
|
||||||
hits="$(cd "$repo" && git ls-files -z -- . ':!*.png' ':!*.jpg' ':!*.jpeg' ':!*.gif' ':!*.ico' ':!*.woff' ':!*.woff2' ':!*.ttf' ':!*.pdf' ':!*.zip' ':!*.bin' ':!*.b64' \
|
|
||||||
| xargs -0 perl -e '
|
| xargs -0 perl -e '
|
||||||
|
use MIME::Base64 qw(decode_base64);
|
||||||
my $pf = shift @ARGV; open(my $ph, "<", $pf) or die; my @pats = map { chomp; qr/$_/i } grep { /\S/ } <$ph>; close $ph;
|
my $pf = shift @ARGV; open(my $ph, "<", $pf) or die; my @pats = map { chomp; qr/$_/i } grep { /\S/ } <$ph>; close $ph;
|
||||||
for my $f (@ARGV) { next unless -f $f && -T $f; open(my $h, "<", $f) or next; my $n = 0;
|
sub hit { my ($t) = @_; for my $p (@pats) { return 1 if $t =~ $p } 0 }
|
||||||
while (my $l = <$h>) { $n++; for my $p (@pats) { if ($l =~ $p) { chomp $l; print "$f:$n:" . substr($l, 0, 160) . "\n"; last } } } close $h; }
|
for my $f (@ARGV) {
|
||||||
|
next unless -f $f && -T $f; open(my $h, "<", $f) or next; my $n = 0; my $whole = "";
|
||||||
|
while (my $l = <$h>) {
|
||||||
|
$n++; $whole .= $l;
|
||||||
|
if (hit($l)) { chomp $l; print "$f:$n:" . substr($l, 0, 160) . "\n"; next }
|
||||||
|
# the encoded pass on this line: base64 literals and hex literals of 24 characters or more
|
||||||
|
while ($l =~ /([A-Za-z0-9+\/]{24,}={0,2})/g) { my $d = decode_base64($1); next unless length $d; if (hit($d)) { print "$f:$n:(decoded base64) " . substr($d =~ s/[^\x20-\x7e]/./gr, 0, 120) . "\n"; last } }
|
||||||
|
while ($l =~ /\b([0-9a-fA-F]{24,})\b/g) { my $x = $1; next if length($x) % 2; my $d = pack("H*", $x); if (hit($d)) { print "$f:$n:(decoded hex) " . substr($d =~ s/[^\x20-\x7e]/./gr, 0, 120) . "\n"; last } }
|
||||||
|
}
|
||||||
|
close $h;
|
||||||
|
# a .b64 file as one blob
|
||||||
|
if ($f =~ /\.b64$/) { (my $b = $whole) =~ s/\s+//g; my $d = decode_base64($b); if (length $d && hit($d)) { print "$f:1:(decoded .b64 file) " . substr($d =~ s/[^\x20-\x7e]/./gr, 0, 120) . "\n" } }
|
||||||
|
}
|
||||||
' "$pats" 2>/dev/null || true)"
|
' "$pats" 2>/dev/null || true)"
|
||||||
rm -f "$pats"
|
rm -f "$pats"
|
||||||
if [ -n "$hits" ]; then printf '%s\n' "$hits" | sed 's/^/founder-strings: /' >&2; return 1; fi
|
if [ -n "$hits" ]; then printf '%s\n' "$hits" | sed 's/^/founder-strings: /' >&2; return 1; fi
|
||||||
|
|
@ -39,28 +51,34 @@ scan() { # <repo>: every tracked text file against the decoded list; prints fi
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "${1:-}" = "--self-test" ]; then
|
if [ "${1:-}" = "--self-test" ]; then
|
||||||
n="$(base64 -d < "$LIST" | grep -vcE '^\s*(#|$)')"
|
top="$(mktemp -d)"; trap 'rm -rf "$top"' EXIT; fx="$top/repo"; mkdir -p "$fx"
|
||||||
[ "$n" -ge 5 ] || { echo "self-test failed: the encoded list decodes to $n pattern(s), expected at least 5"; exit 1; }
|
fixture="$top/list"; printf '# fixture\n\\bfoundername\\b\tfoundername\n\\bsurnamex\\b\tSurnamex\n\\bbiznamez\\b\tbiznamez\n' > "$fixture"
|
||||||
fx="$(mktemp -d)"; trap 'rm -rf "$fx"' EXIT
|
( cd "$fx" && git init -q -b master . ); mkdir -p "$fx/docs" "$fx/tools/ci" "$fx/site"
|
||||||
( cd "$fx" && git init -q -b master . )
|
|
||||||
mkdir -p "$fx/docs"
|
|
||||||
# a clean tree: the standing login, the project, a neutral owner word
|
|
||||||
printf 'Commit as igneum-labs. The founder decided on 5 October 2026. Igneum Labs LTD, DIFC.\n' > "$fx/docs/clean.md"
|
printf 'Commit as igneum-labs. The founder decided on 5 October 2026. Igneum Labs LTD, DIFC.\n' > "$fx/docs/clean.md"
|
||||||
|
printf 'aGVsbG8gd29ybGQsIG5vdGhpbmcgaGVyZQ==\n' > "$fx/tools/ci/clean.b64" # "hello world, nothing here"
|
||||||
( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m c )
|
( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m c )
|
||||||
FOUNDER_CHECK_REPO="$fx" bash "$0" >/dev/null 2>&1 || { echo "self-test failed: a clean tree was reported"; exit 1; }
|
fails=0
|
||||||
# one hit per pattern class, each from the encoded list's own sample column, so this script never spells them; every hit
|
scan "$fx" "$fixture" >/dev/null 2>&1 || { echo "self-test failed: a clean tree (with a harmless .b64) was reported"; fails=1; }
|
||||||
# must name its file
|
i=0
|
||||||
samples="$(decode samples)"; i=0; fails=0
|
while IFS=$'\t' read -r re sample; do
|
||||||
while IFS= read -r word; do
|
i=$((i + 1)); [ -n "$sample" ] || continue
|
||||||
i=$((i + 1)); [ -n "$word" ] || continue
|
for kind in plain b64file base64 hex; do
|
||||||
printf 'a line that names %s in passing\n' "$word" > "$fx/docs/hit-$i.md"
|
case "$kind" in
|
||||||
( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "h$i" )
|
plain) f="docs/hit-$i.md"; printf 'a line that names %s in passing\n' "$sample" > "$fx/$f" ;;
|
||||||
if out="$(FOUNDER_CHECK_REPO="$fx" bash "$0" 2>&1)"; then echo "self-test failed: pattern $i was not caught"; fails=1
|
b64file) f="tools/ci/hit-$i.b64"; printf 'a line that names %s in passing\n' "$sample" | base64 > "$fx/$f" ;;
|
||||||
else case "$out" in *"docs/hit-$i.md"*) ;; *) echo "self-test failed: the hit for pattern $i did not name its file: $out"; fails=1 ;; esac; fi
|
base64) f="site/hit-$i.mjs"; printf 'const X = "%s";\n' "$(printf 'a line that names %s in passing' "$sample" | base64 | tr -d '\n')" > "$fx/$f" ;;
|
||||||
rm -f "$fx/docs/hit-$i.md"; ( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "r$i" )
|
hex) f="site/hit-$i-hex.mjs"; printf 'const H = "%s";\n' "$(printf 'a line that names %s in passing' "$sample" | xxd -p | tr -d '\n')" > "$fx/$f" ;;
|
||||||
done < "$samples"; rm -f "$samples"
|
esac
|
||||||
# a binary file carrying a pattern is not read (images are not text)
|
( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "h" )
|
||||||
[ "$fails" = 0 ] && echo "self-test passed: a clean tree passes; every pattern class in the encoded list is caught in a fixture file and named; the list decodes to $n patterns"
|
if out="$(scan "$fx" "$fixture" 2>&1)"; then echo "self-test failed: pattern $i was not caught as $kind"; fails=1
|
||||||
|
else case "$out" in *"$f"*) ;; *) echo "self-test failed: the $kind hit for pattern $i did not name $f: $out"; fails=1 ;; esac; fi
|
||||||
|
rm -f "$fx/$f"; ( cd "$fx" && git add -A && git -c user.name=t -c user.email=t@t commit -q -m "r" )
|
||||||
|
done
|
||||||
|
done < <(grep -vE '^\s*(#|$)' "$fixture")
|
||||||
|
# without a list: the skip line, exit 0
|
||||||
|
out="$(IGNEUM_FOUNDER_STRINGS="$fx/no-such-list" FOUNDER_CHECK_REPO="$fx" bash "$0" 2>&1)" && case "$out" in *"skipped, no private list"*) ;; *) echo "self-test failed: no skip line without the list: $out"; fails=1 ;; esac || { echo "self-test failed: a tree without the list did not pass with a skip line"; fails=1; }
|
||||||
|
[ "$fails" = 0 ] && echo "self-test passed: a clean tree passes; every fixture pattern is caught in plain text, in a .b64 file, as a base64 literal and as a hex literal, each naming its file; without the private list the check skips with its line"
|
||||||
exit $fails
|
exit $fails
|
||||||
fi
|
fi
|
||||||
scan "$REPO" && echo "founder-strings: no founder name, personal login, earlier business or personal address in any tracked text file"
|
if [ ! -s "$LIST" ]; then echo "founder-strings: skipped, no private list at $LIST (the Mac's pre-push hook carries the list and is the guard; a runner or box has none)"; exit 0; fi
|
||||||
|
scan "$REPO" "$LIST" && echo "founder-strings: no founder name, personal login, earlier business or personal address in any tracked text file, plain or encoded ($(rows | wc -l | tr -d ' ') patterns from the private list)"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@
|
||||||
// node tools/ci/launch-gates-check.mjs --self-test # a gate row without a check fails; a handoff with the founder's name fails
|
// node tools/ci/launch-gates-check.mjs --self-test # a gate row without a check fails; a handoff with the founder's name fails
|
||||||
import { existsSync, readFileSync, mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
import { existsSync, readFileSync, mkdtempSync, writeFileSync, mkdirSync, rmSync } from 'node:fs';
|
||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import path from 'node:path';
|
import path, { join } from 'node:path';
|
||||||
|
import { homedir } from 'node:os';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
@ -21,11 +22,18 @@ const GO = 'docs/plans/testnet-go.md';
|
||||||
const PACK = 'docs/plans/launch-pack.md';
|
const PACK = 'docs/plans/launch-pack.md';
|
||||||
const INCOME = 'docs/analysis/income-tiers.md';
|
const INCOME = 'docs/analysis/income-tiers.md';
|
||||||
|
|
||||||
|
// the private founder list (~/.config/igneum/founder-strings or $IGNEUM_FOUNDER_STRINGS; perl regex, a tab, a sample per row; # comments);
|
||||||
|
// absent here: no founder patterns from this reader (the Mac's pre-push hook carries the file and is the guard)
|
||||||
|
function founderPatternsFromFile() {
|
||||||
|
try {
|
||||||
|
const f = process.env.IGNEUM_FOUNDER_STRINGS || join(homedir(), '.config', 'igneum', 'founder-strings');
|
||||||
|
return readFileSync(f, 'utf8').split('\n').map((l) => l.trim()).filter((l) => l && !l.startsWith('#')).map((l) => new RegExp(l.split('\t')[0], 'i'));
|
||||||
|
} catch { return []; }
|
||||||
|
}
|
||||||
function patterns(root) {
|
function patterns(root) {
|
||||||
const read = f => { try { return readFileSync(path.join(root, f), 'utf8'); } catch { return ''; } };
|
const read = f => { try { return readFileSync(path.join(root, f), 'utf8'); } catch { return ''; } };
|
||||||
const lines = [...read('site/forbidden-strings.txt').split('\n'), ...read('tools/ci/forbidden-strings.txt').split('\n')];
|
const lines = [...read('site/forbidden-strings.txt').split('\n'), ...read('tools/ci/forbidden-strings.txt').split('\n')];
|
||||||
return lines.map(l => l.trim()).filter(l => l && !l.startsWith('#'))
|
return lines.map(l => l.trim()).filter(l => l && !l.startsWith('#')).map(l => new RegExp(l)).concat(root === process.cwd() || root === ROOT ? founderPatternsFromFile() : []); // plus the private founder list for the real tree
|
||||||
.map(l => (l.startsWith('b64:') ? new RegExp(Buffer.from(l.slice(4), 'base64').toString('utf8'), 'i') : new RegExp(l))); // b64: = an encoded founder pattern
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function gateRows(text) {
|
export function gateRows(text) {
|
||||||
|
|
@ -100,7 +108,7 @@ function selfTest() {
|
||||||
const fx = mkdtempSync(path.join(tmpdir(), 'launch-gates-'));
|
const fx = mkdtempSync(path.join(tmpdir(), 'launch-gates-'));
|
||||||
try {
|
try {
|
||||||
for (const d of ['docs/plans', 'docs/analysis', 'site', 'tools/ci', 'tools/launch']) mkdirSync(path.join(fx, d), { recursive: true });
|
for (const d of ['docs/plans', 'docs/analysis', 'site', 'tools/ci', 'tools/launch']) mkdirSync(path.join(fx, d), { recursive: true });
|
||||||
writeFileSync(path.join(fx, 'site/forbidden-strings.txt'), 'b64:XGJmb3VuZGVybmFtZVxi\n'); // an encoded, case-insensitive pattern for a made-up name
|
writeFileSync(path.join(fx, 'site/forbidden-strings.txt'), '(?i)\\bfoundername\\b\n'.replace('(?i)', '')); // a made-up name as a plain pattern (the private list is not read for a fixture root)
|
||||||
writeFileSync(path.join(fx, 'tools/ci/forbidden-strings.txt'), '/Users/\n');
|
writeFileSync(path.join(fx, 'tools/ci/forbidden-strings.txt'), '/Users/\n');
|
||||||
writeFileSync(path.join(fx, 'tools/launch/x.mjs'), '');
|
writeFileSync(path.join(fx, 'tools/launch/x.mjs'), '');
|
||||||
const sentences = Array.from({ length: 8 }, (_, i) => `${i + 1}. sentence (8.3 sentence ${i + 1})`).join('\n');
|
const sentences = Array.from({ length: 8 }, (_, i) => `${i + 1}. sentence (8.3 sentence ${i + 1})`).join('\n');
|
||||||
|
|
@ -117,8 +125,8 @@ function selfTest() {
|
||||||
r = run(fx); if (!r.problems.some(p => /does not exist/.test(p))) throw new Error('self-test: a check naming a missing script passed');
|
r = run(fx); if (!r.problems.some(p => /does not exist/.test(p))) throw new Error('self-test: a check naming a missing script passed');
|
||||||
writeFileSync(path.join(fx, GO), good);
|
writeFileSync(path.join(fx, GO), good);
|
||||||
// the founder's name in the handoff, an em dash, a missing sentence
|
// the founder's name in the handoff, an em dash, a missing sentence
|
||||||
writeFileSync(path.join(fx, PACK), goodPack.replace('1. sentence', '1. Foundername says'));
|
writeFileSync(path.join(fx, PACK), goodPack.replace('1. sentence', '1. foundername says'));
|
||||||
r = run(fx); if (!r.problems.some(p => /forbidden pattern .*foundername/i.test(p))) throw new Error('self-test: the founder\'s name in the handoff passed (the encoded pattern did not match case-insensitively)');
|
r = run(fx); if (!r.problems.some(p => /forbidden pattern .*foundername/i.test(p))) throw new Error('self-test: the founder\'s name in the handoff passed');
|
||||||
writeFileSync(path.join(fx, PACK), goodPack.replace('2. sentence', '2. a \u2014 dash'));
|
writeFileSync(path.join(fx, PACK), goodPack.replace('2. sentence', '2. a \u2014 dash'));
|
||||||
r = run(fx); if (!r.problems.some(p => /em dash/.test(p))) throw new Error('self-test: an em dash in the handoff passed');
|
r = run(fx); if (!r.problems.some(p => /em dash/.test(p))) throw new Error('self-test: an em dash in the handoff passed');
|
||||||
writeFileSync(path.join(fx, PACK), goodPack.replace('(8.3 sentence 5)', ''));
|
writeFileSync(path.join(fx, PACK), goodPack.replace('(8.3 sentence 5)', ''));
|
||||||
|
|
|
||||||
|
|
@ -49,12 +49,18 @@ const STATE_FILE_LIVE = process.env.IGNEUM_DISCORD_STATE || path.join(os.homedir
|
||||||
// ---------- guards ----------
|
// ---------- guards ----------
|
||||||
// Forbidden in any post: the founder's name and logins, rented-box and build hosts, machine ids, internal paths, IP addresses,
|
// Forbidden in any post: the founder's name and logins, rented-box and build hosts, machine ids, internal paths, IP addresses,
|
||||||
// a standalone 32-hex token (a sha256 is 64 hex and passes), any dl.igneum.network path outside /public/, any mention.
|
// a standalone 32-hex token (a sha256 is 64 hex and passes), any dl.igneum.network path outside /public/, any mention.
|
||||||
// The founder's name, logins and the earlier businesses come from tools/ci/founder-strings.b64 (base64, so no tracked file
|
// The founder's name, logins and the earlier businesses come from the PRIVATE list (never a tracked file in any encoding: a base64
|
||||||
// spells them; the first three decoded patterns are the founder, the rest the earlier businesses). fs is read once at load.
|
// copy in the tree was a disclosure on the public host, 7 October 2026, 21:3x UK): $IGNEUM_FOUNDER_STRINGS, else
|
||||||
const FOUNDER_LIST = path.join(HERE, '..', 'ci', 'founder-strings.b64');
|
// ~/.config/igneum/founder-strings (the Mac), else /srv/discord-hooks/founder-strings (build-1, beside the webhook env). One row per
|
||||||
export function founderPatterns(file = FOUNDER_LIST) {
|
// pattern: perl regex, a tab, a sample. Absent everywhere: no founder rows (the host that posts carries the file).
|
||||||
const rows = Buffer.from(fs.readFileSync(file, 'utf8'), 'base64').toString('utf8').split('\n').filter((l) => l.trim() && !l.startsWith('#')).map((l) => l.split('\t'));
|
export function founderListPath(env = process.env) {
|
||||||
return rows.map(([re, sample], i) => ({ re: new RegExp(re, 'i'), sample, why: i < 3 ? "the founder's name or login" : i < 8 ? 'an earlier business' : "the founder's address" }));
|
for (const f of [env.IGNEUM_FOUNDER_STRINGS, path.join(os.homedir(), '.config', 'igneum', 'founder-strings'), '/srv/discord-hooks/founder-strings']) if (f && fs.existsSync(f)) return f;
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
export function founderPatterns(file = founderListPath()) {
|
||||||
|
if (!file) return [];
|
||||||
|
const rows = fs.readFileSync(file, 'utf8').split('\n').filter((l) => l.trim() && !l.startsWith('#')).map((l) => l.split('\t'));
|
||||||
|
return rows.map(([re, sample], i) => ({ re: new RegExp(re, 'i'), sample, why: i < 3 ? "the founder's name or login" : i < 8 ? 'an earlier business' : i === 8 ? "the founder's address" : 'the login before its rename' }));
|
||||||
}
|
}
|
||||||
export const FORBIDDEN = [
|
export const FORBIDDEN = [
|
||||||
...founderPatterns().map(({ re, why }) => ({ re, why })),
|
...founderPatterns().map(({ re, why }) => ({ re, why })),
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,14 @@ import fs from 'node:fs';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import {
|
// the founder guard reads a private list; the test writes a FIXTURE list of made-up names and points the module at it before loading
|
||||||
|
import { mkdtempSync as _mkd, writeFileSync as _wf } from 'node:fs'; import { tmpdir as _tmp } from 'node:os'; import { join as _join } from 'node:path';
|
||||||
|
{ const d = _mkd(_join(_tmp(), 'founder-fixture-')); _wf(_join(d, 'list'), '\\bfoundername\\b\tFoundername\n\\bsurnamex\\b\tSurnamex\n\\bloginx\\b\tloginx\n\\bbiz1\\b\tbiz1\n\\bbiz2\\b\tbiz2\n\\bbiz3\\b\tbiz3\n\\bbiz4\\b\tbiz4\n\\bbiz5\\b\tbiz5\n\\bmail@x\\.y\\b\tmail@x.y\n\\boldlogin\\b\toldlogin\n'); process.env.IGNEUM_FOUNDER_STRINGS = _join(d, 'list'); }
|
||||||
|
const {
|
||||||
shapePulse, shapeDigest, shapeWeekly, shapeRelease, shapeIncidentOpen, shapeIncidentResolve, changedLinesFromPlan, plainLine,
|
shapePulse, shapeDigest, shapeWeekly, shapeRelease, shapeIncidentOpen, shapeIncidentResolve, changedLinesFromPlan, plainLine,
|
||||||
guardText, guardPayload, embed, embedLength, LIMITS, snapshot, devnet2Line, versionShare, watchPass, WATCH, WATCH_CONDITIONS,
|
guardText, guardPayload, embed, embedLength, LIMITS, snapshot, devnet2Line, versionShare, watchPass, WATCH, WATCH_CONDITIONS,
|
||||||
Poster, postWebhook, dueNow, ukStamp, londonParts, fmtHash, fmtDur, fmtDelta, fmtChangePct, renderPreview, ICON, EMBER,
|
Poster, postWebhook, dueNow, ukStamp, londonParts, fmtHash, fmtDur, fmtDelta, fmtChangePct, renderPreview, ICON, EMBER,
|
||||||
shapeFeed, feedMilestones, shapeMilestone, hashOriginLine, runFeed, FEED_MILESTONES, founderPatterns } from './discord-hooks.mjs';
|
shapeFeed, feedMilestones, shapeMilestone, hashOriginLine, runFeed, FEED_MILESTONES, founderPatterns } = await import('./discord-hooks.mjs');
|
||||||
|
|
||||||
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const fx = name => JSON.parse(fs.readFileSync(path.join(HERE, 'fixtures', 'discord', `${name}.json`), 'utf8'));
|
const fx = name => JSON.parse(fs.readFileSync(path.join(HERE, 'fixtures', 'discord', `${name}.json`), 'utf8'));
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ set -euo pipefail
|
||||||
export TZ=UTC
|
export TZ=UTC
|
||||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||||
ROOT="$(cd "$HERE/../.." && pwd)"
|
ROOT="$(cd "$HERE/../.." && pwd)"
|
||||||
STANDING_LOGIN="${IGNEUM_STANDING_LOGIN:-$(base64 -d < "$(cd "$(dirname "$0")/../.." && pwd)/tools/ci/founder-strings.b64" | grep -vE '^#' | sed -n '10p' | cut -f2)}" # the login's pre-rename spelling, from the encoded list (no tracked file spells it)
|
FOUNDER_LIST="${IGNEUM_FOUNDER_STRINGS:-$HOME/.config/igneum/founder-strings}" # the PRIVATE list (perl regex, tab, sample per row); never a tracked file in any encoding
|
||||||
|
[ -s "$FOUNDER_LIST" ] || { echo "fresh-repo: no private founder list at $FOUNDER_LIST (the Mac holds it; the rewrite needs its rows)" >&2; exit 1; }
|
||||||
|
STANDING_LOGIN="${IGNEUM_STANDING_LOGIN:-$(grep -vE '^#' "$FOUNDER_LIST" | sed -n '10p' | cut -f2)}" # row 10: the login's pre-rename spelling
|
||||||
ORG="igneum-network"
|
ORG="igneum-network"
|
||||||
SOURCE="" WORK="" NEW_REPO="$ORG/igneum-core" NEW_LOGIN="" PUBLIC_CLAUDE="" CLEAN=0
|
SOURCE="" WORK="" NEW_REPO="$ORG/igneum-core" NEW_LOGIN="" PUBLIC_CLAUDE="" CLEAN=0
|
||||||
while [ $# -gt 0 ]; do
|
while [ $# -gt 0 ]; do
|
||||||
|
|
@ -90,14 +92,14 @@ PERSONAL_EMAILS="$(printf '%s\n' "$PERSONAL_PAIRS" | awk -F'|' 'NF==2{print $2}'
|
||||||
PERSONAL_NAMES="$(printf '%s\n' "$PERSONAL_PAIRS" | awk -F'|' 'NF==2 && $1 ~ / /{print $1}' | grep -v -E "^($STANDING_LOGIN|$TARGET_LOGIN)$" | sort -u || true)" # a name has a space; a login is not a name
|
PERSONAL_NAMES="$(printf '%s\n' "$PERSONAL_PAIRS" | awk -F'|' 'NF==2 && $1 ~ / /{print $1}' | grep -v -E "^($STANDING_LOGIN|$TARGET_LOGIN)$" | sort -u || true)" # a name has a space; a login is not a name
|
||||||
# the founder's first name, surname and second login from the encoded list (rows 1 to 3, sample column), so the rules never depend on
|
# the founder's first name, surname and second login from the encoded list (rows 1 to 3, sample column), so the rules never depend on
|
||||||
# which commits a given mirror carries (7 Oct 2026: build-1's mirror holds none of the 40 personal-identity commits)
|
# which commits a given mirror carries (7 Oct 2026: build-1's mirror holds none of the 40 personal-identity commits)
|
||||||
LIST_ROWS="$(base64 -d < "$ROOT/tools/ci/founder-strings.b64" | grep -vE '^#')"
|
LIST_ROWS="$(grep -vE '^#' "$FOUNDER_LIST")"
|
||||||
LIST_FIRST="$(printf '%s\n' "$LIST_ROWS" | sed -n '1p' | cut -f2 | awk '{print $1}')"; LIST_LAST="$(printf '%s\n' "$LIST_ROWS" | sed -n '2p' | cut -f2)"; LIST_SECOND="$(printf '%s\n' "$LIST_ROWS" | sed -n '3p' | cut -f2)"
|
LIST_FIRST="$(printf '%s\n' "$LIST_ROWS" | sed -n '1p' | cut -f2 | awk '{print $1}')"; LIST_LAST="$(printf '%s\n' "$LIST_ROWS" | sed -n '2p' | cut -f2)"; LIST_SECOND="$(printf '%s\n' "$LIST_ROWS" | sed -n '3p' | cut -f2)"
|
||||||
FIRST_NAMES="$( { printf '%s\n' "$PERSONAL_NAMES" | awk 'NF>=2{print $1}'; printf '%s\n' "$LIST_FIRST"; } | grep . | sort -u)"
|
FIRST_NAMES="$( { printf '%s\n' "$PERSONAL_NAMES" | awk 'NF>=2{print $1}'; printf '%s\n' "$LIST_FIRST"; } | grep . | sort -u)"
|
||||||
LAST_NAMES="$( { printf '%s\n' "$PERSONAL_NAMES" | awk 'NF>=2{print $NF}'; printf '%s\n' "$LIST_LAST"; } | grep . | sort -u)"
|
LAST_NAMES="$( { printf '%s\n' "$PERSONAL_NAMES" | awk 'NF>=2{print $NF}'; printf '%s\n' "$LIST_LAST"; } | grep . | sort -u)"
|
||||||
SECOND_LOGINS="$( { printf '%s\n' "$PERSONAL_EMAILS" | sed -nE 's/^[0-9]+\+([A-Za-z0-9-]+)@users\.noreply\.github\.com$/\1/p'; printf '%s\n' "$LIST_SECOND"; } | grep . | grep -v -E "^($STANDING_LOGIN|$TARGET_LOGIN)$" | sort -u || true)"
|
SECOND_LOGINS="$( { printf '%s\n' "$PERSONAL_EMAILS" | sed -nE 's/^[0-9]+\+([A-Za-z0-9-]+)@users\.noreply\.github\.com$/\1/p'; printf '%s\n' "$LIST_SECOND"; } | grep . | grep -v -E "^($STANDING_LOGIN|$TARGET_LOGIN)$" | sort -u || true)"
|
||||||
# the other businesses named in the plan (brand names, not people), from the encoded list tools/ci/founder-strings.b64 (rows 4 to 8;
|
# the other businesses named in the plan (brand names, not people), from the encoded list tools/ci/founder-strings.b64 (rows 4 to 8;
|
||||||
# no tracked file spells them: the founder-strings check reads every tracked file)
|
# no tracked file spells them: the founder-strings check reads every tracked file)
|
||||||
OTHER_BUSINESSES="$(base64 -d < "$ROOT/tools/ci/founder-strings.b64" | grep -vE '^#' | sed -n '4,8p' | cut -f1 | sed -E 's/\\b//g' | paste -sd'|' -)"
|
OTHER_BUSINESSES="$(grep -vE '^#' "$FOUNDER_LIST" | sed -n '4,8p' | cut -f1 | sed -E 's/\\b//g' | paste -sd'|' -)"
|
||||||
[ -n "$OTHER_BUSINESSES" ] || { echo "fresh-repo: the encoded founder list decoded to no business names" >&2; exit 1; }
|
[ -n "$OTHER_BUSINESSES" ] || { echo "fresh-repo: the encoded founder list decoded to no business names" >&2; exit 1; }
|
||||||
# the secrets: whichever of the four files exist, plus every dated copy the rotation left behind
|
# the secrets: whichever of the four files exist, plus every dated copy the rotation left behind
|
||||||
# (log-intake-key.old-<date>, dl-token.old-<date>: rotation phase 2 section 5 step 2 renames the .next files to the
|
# (log-intake-key.old-<date>, dl-token.old-<date>: rotation phase 2 section 5 step 2 renames the .next files to the
|
||||||
|
|
@ -157,6 +159,12 @@ while IFS= read -r login; do
|
||||||
printf '\\b%s\\b\n' "$login" >> "$IDENT"
|
printf '\\b%s\\b\n' "$login" >> "$IDENT"
|
||||||
done <<< "$SECOND_LOGINS"
|
done <<< "$SECOND_LOGINS"
|
||||||
printf 'regex:(?i)\\b(%s)\\b==>[other-business]\n' "$OTHER_BUSINESSES" >> "$REPLACE"
|
printf 'regex:(?i)\\b(%s)\\b==>[other-business]\n' "$OTHER_BUSINESSES" >> "$REPLACE"
|
||||||
|
# the encoded forms: the base64 of every list regex (site/forbidden-strings.txt carried them as b64: lines until 21:3x UK on 7 October 2026)
|
||||||
|
while IFS= read -r re; do
|
||||||
|
[ -n "$re" ] || continue; b="$(printf '%s' "$re" | base64 | tr -d '\n')"
|
||||||
|
printf 'literal:%s==>[encoded-pattern-removed]\n' "$b" >> "$REPLACE"
|
||||||
|
printf '%s\n' "$b" | sed 's/[][\\.*^$?+(){}|/]/\\&/g' >> "$IDENT"
|
||||||
|
done < <(printf '%s\n' "$LIST_ROWS" | cut -f1)
|
||||||
printf '\\b(%s)\\b\n' "$OTHER_BUSINESSES" >> "$IDENT"
|
printf '\\b(%s)\\b\n' "$OTHER_BUSINESSES" >> "$IDENT"
|
||||||
say "rules: $(grep -c . "$REPLACE") replace lines, $(grep -c . "$MAILMAP") mailmap lines, $(grep -c . "$IDENT") identity patterns, $(grep -c . "$SECRETS") secret patterns (files under $RULES, removed at exit)"
|
say "rules: $(grep -c . "$REPLACE") replace lines, $(grep -c . "$MAILMAP") mailmap lines, $(grep -c . "$IDENT") identity patterns, $(grep -c . "$SECRETS") secret patterns (files under $RULES, removed at exit)"
|
||||||
|
|
||||||
|
|
@ -169,7 +177,7 @@ scan_blobs() {
|
||||||
| perl -ne 'BEGIN { open(P, "<", shift) or die; @p = map { chomp; qr/$_/i } grep { /\S/ } <P>; $n = 0 } for my $p (@p) { if ($_ =~ $p) { $n++; last } } END { print "$n\n" }' "$1"
|
| perl -ne 'BEGIN { open(P, "<", shift) or die; @p = map { chomp; qr/$_/i } grep { /\S/ } <P>; $n = 0 } for my $p (@p) { if ($_ =~ $p) { $n++; last } } END { print "$n\n" }' "$1"
|
||||||
}
|
}
|
||||||
scan_meta() { git log --all --format='%an%n%ae%n%cn%n%ce%n%s%n%b' | perl -ne 'BEGIN { open(P, "<", shift) or die; @p = map { chomp; qr/$_/i } grep { /\S/ } <P>; $n = 0 } for my $p (@p) { if ($_ =~ $p) { $n++; last } } END { print "$n\n" }' "$1"; }
|
scan_meta() { git log --all --format='%an%n%ae%n%cn%n%ce%n%s%n%b' | perl -ne 'BEGIN { open(P, "<", shift) or die; @p = map { chomp; qr/$_/i } grep { /\S/ } <P>; $n = 0 } for my $p (@p) { if ($_ =~ $p) { $n++; last } } END { print "$n\n" }' "$1"; }
|
||||||
DROPPED=(docs/review) # the ledger, its fixes file and the ledger page are published with the repository (decision of 5 October 2026, docs/fud-fixes.md section 5); the review folder stays internal
|
DROPPED=(docs/review tools/ci/founder-strings.b64) # the encoded founder list (19:5x to 21:3x UK, 7 October 2026) leaves every commit # the ledger, its fixes file and the ledger page are published with the repository (decision of 5 October 2026, docs/fud-fixes.md section 5); the review folder stays internal
|
||||||
counts() { # <label>
|
counts() { # <label>
|
||||||
local label="$1"
|
local label="$1"
|
||||||
say ""
|
say ""
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue