mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-18 19:02:18 +02:00
test: pin the security-property regression guards from pre-landing review
The pre-landing review found the fixes were correct but three regression guards were missing — each pins a property whose silent revert would keep behavior identical while reopening the hole: - validateAuth: a static tripwire asserting crypto.timingSafeEqual + the got.length===want.length gate + the null-header guard (a revert to `===` keeps accept/reject green but restores the timing side-channel). - redact: a table-driven loop over the exported URL_PASSWORD_PLACEHOLDER_WORDS so a typo or dropped entry can't silently start blocking a doc placeholder; plus a substring-can't-rescue-a-real-secret assertion. - config: assert the self-contained .gitignore is written even when git already ignores .gstack/, proving the write precedes the isIgnoredByGit early return. - bun-polyfill: cover the 128+signal exit branch (POSIX only). URL_PASSWORD_PLACEHOLDER_WORDS is exported so the table test can't drift. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
341d7be27c
commit
5d74ed7231
@@ -64,6 +64,22 @@ describe('Server auth security', () => {
|
||||
expect(scopeBlock).toContain('Domain not allowed');
|
||||
});
|
||||
|
||||
// Test 1d: validateAuth compares the bearer token in CONSTANT TIME with a
|
||||
// length gate. A revert to `header === \`Bearer ${authToken}\`` keeps
|
||||
// accept/reject behavior identical (functional tests still pass) but silently
|
||||
// reintroduces the byte-by-byte timing side-channel; dropping the length gate
|
||||
// makes timingSafeEqual throw RangeError (500 instead of 401) on a wrong-length
|
||||
// token. Pin both properties, mirroring the token-registry sibling guard.
|
||||
test('validateAuth uses constant-time comparison with a length gate', () => {
|
||||
const authBlock = sliceBetween(SERVER_SRC, 'function validateAuth(req: Request): boolean {', '// Factory-scoped shutdown');
|
||||
expect(authBlock).toContain('crypto.timingSafeEqual');
|
||||
expect(authBlock).toContain('got.length === want.length');
|
||||
// The null-header guard must remain (Buffer.from(null) would otherwise throw).
|
||||
expect(authBlock).toContain('header === null');
|
||||
// The raw === comparison of the header against the bearer string must be gone.
|
||||
expect(authBlock).not.toContain('header === `Bearer ${authToken}`');
|
||||
});
|
||||
|
||||
// Test 2: /refs endpoint requires auth via validateAuth
|
||||
test('/refs endpoint requires authentication', () => {
|
||||
const refsBlock = sliceBetween(SERVER_SRC, "url.pathname === '/refs'", "url.pathname === '/activity/stream'");
|
||||
|
||||
Reference in New Issue
Block a user