fix(web): Steuerzeichen in safe-next.ts als Unicode-Escapes statt Rohbytes

FORBIDDEN_CHARS_RE in sanitizeNextPath bestand aus drei rohen Steuerbytes
(NUL, US, DEL) statt Escapes - jeder Editor, Formatierer oder Minifier in
der Kette kann solche Bytes stillschweigend verschlucken. Umgeschrieben auf
Unicode-Escapes fuer den Bereich U+0000 bis U+001F und U+007F. Zeichenmenge
ueber alle 65536 Codepunkte aus U+0000 bis U+FFFF als unveraendert
nachgewiesen (54 abgewiesene Codepunkte, Bitmap-SHA-256
3d58108b87e4641e506602cc701a66d11ec19cf20551937fada1826f50ecbe64 vor und
nach dem Umbau identisch). Kommentar korrigiert: die vorherige Behauptung,
Escapes seien "am Edge" noetig, war falsch - zwischen den beiden
Escape-Schreibweisen gibt es zur Laufzeit keinen Unterschied.

Quick-Vorgang 260921-i8x, Task 1/3.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
This commit is contained in:
2026-09-21 13:26:25 +02:00
parent 287799a6fc
commit f85c91be01
3 changed files with 584 additions and 0 deletions
+54
View File
@@ -1,6 +1,30 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { describe, expect, it } from 'vitest';
import { buildNextParam, sanitizeNextPath } from './safe-next';
// Die 54 Codepunkte, die `sanitizeNextPath` bei der Planung von quick-260921-i8x
// ueber alle 65536 Codepunkte aus U+0000..U+FFFF gemessen abweist (SHA-256 der
// Bitmap: 3d58108b87e4641e506602cc701a66d11ec19cf20551937fada1826f50ecbe64).
// Hart hinterlegt, nicht aus der Regel abgeleitet (D-01): eine aus derselben
// Regel erzeugte Erwartung wuerde jede Aenderung der Regel mitmachen und
// bewiese damit nichts.
const EXPECTED_REJECTED_CODEPOINTS: number[] = [
...Array.from({ length: 0x20 }, (_, i) => i), // 0x00-0x1f
0x20,
0x5c,
0x7f,
0xa0,
0x1680,
...Array.from({ length: 0x200a - 0x2000 + 1 }, (_, i) => 0x2000 + i), // 0x2000-0x200a
0x2028,
0x2029,
0x202f,
0x205f,
0x3000,
0xfeff,
];
/**
* safe-next.test — Ruecksprung nach Anmeldung (quick-260917-gyd).
*
@@ -70,4 +94,34 @@ describe('sanitizeNextPath', () => {
expect(sanitizeNextPath('/login/')).toBe('/');
expect(sanitizeNextPath('/loginhistory')).toBe('/loginhistory');
});
it('Test A: die abgewiesene Zeichenmenge ist und bleibt genau diese 54 Codepunkte (T-i8x-01)', () => {
const rejected: number[] = [];
for (let cp = 0; cp <= 0xffff; cp++) {
const input = `/a${String.fromCharCode(cp)}b`;
if (sanitizeNextPath(input) === '/') {
rejected.push(cp);
}
}
expect(rejected).toEqual(EXPECTED_REJECTED_CODEPOINTS);
expect(rejected).toHaveLength(54);
});
it('Test B: die Datei enthaelt kein rohes Steuerzeichen', () => {
const filePath = join(import.meta.dirname, 'safe-next.ts');
const bytes = readFileSync(filePath);
let count = 0;
for (const byte of bytes) {
if ((byte < 0x20 && byte !== 0x0a) || byte === 0x7f) {
count++;
}
}
expect(count).toBe(0);
});
it('Test C: der Zwei-Zeichen-Praefixtest bleibt scharf', () => {
expect(sanitizeNextPath('//host')).toBe('/');
expect(sanitizeNextPath('/\\host')).toBe('/');
expect(sanitizeNextPath(`/${String.fromCharCode(0x00)}`)).toBe('/');
});
});
Binary file not shown.