mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-26 22:51:47 +02:00
* 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
73 lines
3.9 KiB
TypeScript
73 lines
3.9 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
||
import { redactFindingSpans, scan } from "../lib/redact-engine";
|
||
import { redact, sanitizeForJson } from "../lib/cso/process";
|
||
|
||
const secret = ["8Fk2pQ9vXz4wL7mN", "3rT6yB1cD5eG0hJq"].join("");
|
||
const marker = "<REDACTED-env.kv>";
|
||
|
||
describe("redaction binds each finding to its original span", () => {
|
||
test("never masks a later unflagged assignment instead of the flagged value", () => {
|
||
const input = `DB_PASSWORD=${secret}\nOTHER_API_KEY="your-api-key-here"`;
|
||
expect(scan(input).findings.map((f) => f.id)).toEqual(["env.kv"]);
|
||
expect(redactFindingSpans(input)).toBe(`DB_PASSWORD=${marker}\nOTHER_API_KEY="your-api-key-here"`);
|
||
});
|
||
|
||
test("literal example values are suppressed before redaction", () => {
|
||
const input = `DB_PASSWORD=synthetic-example-secret\nOTHER_API_KEY="your-api-key-here"`;
|
||
expect(scan(input).findings).toEqual([]);
|
||
expect(redactFindingSpans(input)).toBe(input);
|
||
});
|
||
|
||
for (const [name, prefix, suffix] of [
|
||
["first line", "", ""],
|
||
["later line", "unrelated line\n", "\nend"],
|
||
["indented", "header\n ", ""],
|
||
["CRLF", "header\r\n", "\r\nend"],
|
||
]) {
|
||
test(`${name}: masks the captured value and keeps surrounding text`, () => {
|
||
const input = `${prefix}DB_PASSWORD=${secret}${suffix}`;
|
||
expect(scan(input).findings.map((f) => f.id)).toEqual(["env.kv"]);
|
||
expect(redactFindingSpans(input)).toBe(`${prefix}DB_PASSWORD=${marker}${suffix}`);
|
||
});
|
||
}
|
||
|
||
test("repeated identical values map independently, preserving unflagged neighbors", () => {
|
||
const input = `DB_PASSWORD=${secret}\nOTHER_API_KEY="your-api-key-here"\nAPI_KEY=${secret}`;
|
||
expect(scan(input).findings.map((f) => f.id)).toEqual(["env.kv", "env.kv"]);
|
||
expect(redactFindingSpans(input)).toBe(`DB_PASSWORD=${marker}\nOTHER_API_KEY="your-api-key-here"\nAPI_KEY=${marker}`);
|
||
});
|
||
|
||
test("many findings retain exact order without per-finding raw rescans", () => {
|
||
const input = Array.from({ length: 300 }, (_, i) => `API_KEY=${secret}${i.toString(36)}`).join("\n");
|
||
const output = redactFindingSpans(input);
|
||
expect(scan(input).findings).toHaveLength(300);
|
||
expect(output).toBe(Array.from({ length: 300 }, () => `API_KEY=${marker}`).join("\n"));
|
||
});
|
||
|
||
test("normalization maps fullwidth Unicode, entity text, and zero-width bytes back to original span", () => {
|
||
const encoded = `${secret.slice(0, 9)}&${secret.slice(9)}`;
|
||
const entity = `${secret.slice(0, 9)}&${secret.slice(9)}`;
|
||
const invisible = `${secret.slice(0, 9)}\u200b${secret.slice(9)}`;
|
||
for (const value of [encoded, entity, invisible]) {
|
||
const input = `DB_PASSWORD=${value}\nOTHER_API_KEY="your-api-key-here"`;
|
||
expect(scan(input).findings.map((f) => f.id)).toEqual(["env.kv"]);
|
||
expect(redactFindingSpans(input)).toBe(`DB_PASSWORD=${marker}\nOTHER_API_KEY="your-api-key-here"`);
|
||
}
|
||
});
|
||
|
||
test("overlapping JWT and Bearer findings coalesce; marker-only and unlocated oversize finding still withhold", () => {
|
||
const part = "Ab3dE6fGh8Ij9Kl0Mn1O";
|
||
const jwt = `eyJ${part}.eyJ${part}.${part}`;
|
||
expect(scan(`Authorization: Bearer ${jwt}`).findings.map((f) => f.id)).toEqual(["auth.bearer", "jwt"]);
|
||
expect(redactFindingSpans(`Authorization: Bearer ${jwt}`)).toMatch(/^Authorization: Bearer <REDACTED-[a-z.+]+>$/);
|
||
expect(redactFindingSpans("-----BEGIN " + "PRIVATE KEY-----\nbody")).toBeNull();
|
||
expect(redactFindingSpans(`DB_PASSWORD=${secret}`, { maxBytes: 10 })).toBeNull();
|
||
});
|
||
|
||
test("CSO process and JSON output use the exact span, without dropping safe context", () => {
|
||
const input = `DB_PASSWORD=${secret}\nOTHER_API_KEY="your-api-key-here"`;
|
||
expect(redact(input)).toBe(`DB_PASSWORD=${marker}\nOTHER_API_KEY="your-api-key-here"`);
|
||
expect(sanitizeForJson({ output: input })).toEqual({ output: `DB_PASSWORD=${marker}\nOTHER_API_KEY="your-api-key-here"` });
|
||
});
|
||
});
|