mirror of
https://github.com/garrytan/gstack.git
synced 2026-07-15 20:17:24 +02:00
fix: CSO security fixes — token leak, domain bypass, input validation
1. Remove root token from /health endpoint entirely (CSO #1 CRITICAL). Origin header is spoofable. Extension reads from ~/.gstack/.auth.json. 2. Add domain check for newtab URL (CSO #5). Previously only goto was checked, allowing domain-restricted agents to bypass via newtab. 3. Validate scope values, rateLimit, expiresSeconds in createToken() (CSO #4). Rejects invalid scopes and negative values. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -139,8 +139,11 @@ describe('token-registry', () => {
|
||||
expect(validateToken('gsk_sess_unknown')).toBeNull();
|
||||
});
|
||||
|
||||
it('rejects expired token', () => {
|
||||
const created = createToken({ clientId: 'expiring', expiresSeconds: -1 });
|
||||
it('rejects expired token', async () => {
|
||||
// expiresSeconds: 0 creates a token that expires at creation time
|
||||
const created = createToken({ clientId: 'expiring', expiresSeconds: 0 });
|
||||
// Wait 1ms so the expiry is definitively in the past
|
||||
await new Promise(r => setTimeout(r, 2));
|
||||
expect(validateToken(created.token)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -345,4 +348,52 @@ describe('token-registry', () => {
|
||||
expect(SCOPE_ADMIN.has('screenshot')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── CSO Fix #4: Input validation ──────────────────────────────
|
||||
describe('Input validation (CSO finding #4)', () => {
|
||||
it('rejects invalid scope values', () => {
|
||||
expect(() => createToken({
|
||||
clientId: 'test-invalid-scope',
|
||||
scopes: ['read', 'bogus' as any],
|
||||
})).toThrow('Invalid scope: bogus');
|
||||
});
|
||||
|
||||
it('rejects negative rateLimit', () => {
|
||||
expect(() => createToken({
|
||||
clientId: 'test-neg-rate',
|
||||
rateLimit: -1,
|
||||
})).toThrow('rateLimit must be >= 0');
|
||||
});
|
||||
|
||||
it('rejects negative expiresSeconds', () => {
|
||||
expect(() => createToken({
|
||||
clientId: 'test-neg-expire',
|
||||
expiresSeconds: -100,
|
||||
})).toThrow('expiresSeconds must be >= 0 or null');
|
||||
});
|
||||
|
||||
it('accepts null expiresSeconds (indefinite)', () => {
|
||||
const token = createToken({
|
||||
clientId: 'test-indefinite',
|
||||
expiresSeconds: null,
|
||||
});
|
||||
expect(token.expiresAt).toBeNull();
|
||||
});
|
||||
|
||||
it('accepts zero rateLimit (unlimited)', () => {
|
||||
const token = createToken({
|
||||
clientId: 'test-unlimited-rate',
|
||||
rateLimit: 0,
|
||||
});
|
||||
expect(token.rateLimit).toBe(0);
|
||||
});
|
||||
|
||||
it('accepts valid scopes', () => {
|
||||
const token = createToken({
|
||||
clientId: 'test-valid-scopes',
|
||||
scopes: ['read', 'write', 'admin', 'meta'],
|
||||
});
|
||||
expect(token.scopes).toEqual(['read', 'write', 'admin', 'meta']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user