Files
gstack/test/redact-unicode-offsets.test.ts
T
Garry Tan b9706f3635 v1.88.1.0 fix: harden credential boundaries and owned state (#2942)
* fix(settings): preserve symlinked settings targets

Resolve the selected target for locking, mutation, backup, and rollback; refuse target changes and preserve private modes. Addresses #2830.

* fix(redact): bind masking to original detected spans

Inspired by #2929's anchored-span diagnosis; independently implemented using normalization offsets. Addresses #2930 and the relocation portion of #2912 without changing detection sensitivity.

* fix(evals): exclude operator credentials from prefix admission

Adapts the credential-suffix screen proposed in #2636, with real launched-child regression coverage and deliberate provider-auth exceptions.

* fix(artifacts): retain custom allowlist rules on reinitialization

Preserve the exact user-owned suffix and publish only a successfully assembled replacement. Independently implements the repair reported in #2907.

* test(cso): verify exact masked reads and unmaskable payload refusal

* fix(cso): preserve exact filesystem identities through lease recovery

Preserve 64-bit device/inode identity and nanosecond race checks. Add native NTFS lifecycle coverage for #2927; retain ambiguous legacy-state refusal without claiming Windows PID-reuse recovery is resolved.

* fix(redact): bind pre-push scans to destination and preserve seam context

Uses #2935 (bd07318) as source evidence for push-target range and slice-overlap defects. Independently implemented; no cherry-pick or release metadata adoption.

* test(ci): gate native agent ownership and settings links on macOS

* fix(browse): bind agent lifetimes and cleanup to owned generations

Uses #2931 by Chris Hutton / Claude Fable 5.1 as attributed design input; independently implemented without broad sweeps or copied code. Keep uncertain children and locks rather than deleting foreign state.

* test(ci): include concurrent shutdown controls in the native macOS gate

* v1.88.1.0 fix: harden credential boundaries and owned state

* fix(redact): preserve target provenance and scan boundary semantics

* test(artifacts): read managed rules from atomic allowlist assembly

* fix: preserve native exit observations and fixture prerequisites

* fix: preserve UTF-16 offsets through redaction normalization
2026-09-23 08:54:53 -04:00

68 lines
3.2 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import * as path from "node:path";
import { applyRedactions, normalizeWithMap, redactFindingSpans, scan } from "../lib/redact-engine";
import { redact, sanitizeForJson } from "../lib/cso/process";
const secret = ["8Fk2pQ9vXz4wL7mN", "3rT6yB1cD5eG0hJq"].join("");
describe("normalized redaction offsets use UTF-16 units", () => {
test("every BMP input maps every emitted UTF-16 unit and the end sentinel", () => {
const mismatches: number[] = [];
for (let code = 0; code <= 0xffff; code++) {
const { normalized, map } = normalizeWithMap(String.fromCharCode(code));
if (map.length !== normalized.length + 1
|| map[normalized.length] !== 1
|| map.slice(0, -1).some((offset) => offset !== 0)) {
mismatches.push(code);
}
}
expect(mismatches).toEqual([]);
});
for (const count of [0, 1, 64]) {
test(`${count} supplementary NFKC expansions preserve the exact credential span`, () => {
const prefix = "\uFA6C".repeat(count) + "\n";
const suffix = "\n" + "z".repeat(256);
const input = `${prefix}DB_PASSWORD=${secret}${suffix}`;
const expected = `${prefix}DB_PASSWORD=<REDACTED-env.kv>${suffix}`;
expect(redactFindingSpans(input)).toBe(expected);
expect(scan(input).findings.map(({ id, line, col }) => ({ id, line, col })))
.toEqual([{ id: "env.kv", line: 2, col: 13 }]);
expect(redact(input)).toBe(expected);
expect(sanitizeForJson({ output: input })).toEqual({ output: expected });
});
}
test("supplementary source text, entities, and zero-width input retain their original bytes", () => {
const prefix = "\u{242EE} &amp; \u200b\n";
const value = `${secret.slice(0, 9)}\u200b${secret.slice(9)}`;
const input = `${prefix}DB_PASSWORD=${value}\r\nend`;
const { normalized, map } = normalizeWithMap(input);
expect(map).toHaveLength(normalized.length + 1);
expect(map[normalized.length]).toBe(input.length);
expect(redactFindingSpans(input)).toBe(`${prefix}DB_PASSWORD=<REDACTED-env.kv>\r\nend`);
});
for (const suffix of ["", " after"]) {
test(`both masking APIs preserve exact email boundaries with suffix ${JSON.stringify(suffix)}`, () => {
const prefix = "\uFA6C contact: ";
const input = prefix + "reviewer@audit.invalid" + suffix;
expect(redactFindingSpans(input)).toBe(prefix + "<REDACTED-pii.email>" + suffix);
const result = applyRedactions(input, ["pii.email"]);
expect(result.body).toBe(prefix + "<REDACTED-EMAIL>" + suffix);
expect(result.skipped).toEqual([]);
});
}
test("the actual auto-redact CLI masks a detected email at EOF after supplementary expansion", () => {
const prefix = "\uFA6C contact: ";
const input = prefix + "reviewer@audit.invalid";
const result = Bun.spawnSync([
process.execPath, path.resolve(import.meta.dir, "../bin/gstack-redact"), "--auto-redact", "pii.email",
], { stdin: Buffer.from(input), timeout: 30_000 });
expect(result.exitCode).toBe(0);
expect(result.stdout.toString()).toBe(prefix + "<REDACTED-EMAIL>");
expect(result.stderr.toString()).not.toContain("could not be auto-redacted");
});
});