mirror of
https://github.com/garrytan/gstack.git
synced 2026-08-23 14:32:33 +02:00
Merge remote-tracking branch 'origin/main' into garrytan/chrome-extension-ctrl
# Conflicts: # .agents/skills/gstack-browse/SKILL.md # .agents/skills/gstack-design-review/SKILL.md # .agents/skills/gstack-qa/SKILL.md # .agents/skills/gstack-setup-browser-cookies/SKILL.md # .agents/skills/gstack/SKILL.md # README.md # browse/src/cli.ts
This commit is contained in:
@@ -386,10 +386,42 @@ describe('Cookies and storage', () => {
|
||||
});
|
||||
|
||||
test('storage set and get works', async () => {
|
||||
await handleReadCommand('storage', ['set', 'testKey', 'testValue'], bm);
|
||||
await handleReadCommand('storage', ['set', 'testData', 'testValue'], bm);
|
||||
const result = await handleReadCommand('storage', [], bm);
|
||||
const storage = JSON.parse(result);
|
||||
expect(storage.localStorage.testKey).toBe('testValue');
|
||||
expect(storage.localStorage.testData).toBe('testValue');
|
||||
});
|
||||
|
||||
test('storage read redacts sensitive keys', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
|
||||
await handleReadCommand('storage', ['set', 'auth_token', 'my-secret-token'], bm);
|
||||
await handleReadCommand('storage', ['set', 'api_key', 'key-12345'], bm);
|
||||
await handleReadCommand('storage', ['set', 'displayName', 'normalValue'], bm);
|
||||
const result = await handleReadCommand('storage', [], bm);
|
||||
const storage = JSON.parse(result);
|
||||
expect(storage.localStorage.auth_token).toMatch(/REDACTED/);
|
||||
expect(storage.localStorage.api_key).toMatch(/REDACTED/);
|
||||
expect(storage.localStorage.displayName).toBe('normalValue');
|
||||
});
|
||||
|
||||
test('storage read redacts sensitive values by prefix', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
|
||||
// JWT value under innocuous key name
|
||||
await handleReadCommand('storage', ['set', 'userData', 'eyJhbGciOiJIUzI1NiJ9.payload.sig'], bm);
|
||||
// GitHub PAT under innocuous key name
|
||||
await handleReadCommand('storage', ['set', 'repoAccess', 'ghp_abc123def456'], bm);
|
||||
const result = await handleReadCommand('storage', [], bm);
|
||||
const storage = JSON.parse(result);
|
||||
expect(storage.localStorage.userData).toMatch(/REDACTED/);
|
||||
expect(storage.localStorage.repoAccess).toMatch(/REDACTED/);
|
||||
});
|
||||
|
||||
test('storage redaction includes value length', async () => {
|
||||
await handleWriteCommand('goto', [baseUrl + '/basic.html'], bm);
|
||||
await handleReadCommand('storage', ['set', 'session_token', 'abc123'], bm);
|
||||
const result = await handleReadCommand('storage', [], bm);
|
||||
const storage = JSON.parse(result);
|
||||
expect(storage.localStorage.session_token).toBe('[REDACTED — 6 chars]');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -2,67 +2,71 @@ import { describe, it, expect } from 'bun:test';
|
||||
import { validateNavigationUrl } from '../src/url-validation';
|
||||
|
||||
describe('validateNavigationUrl', () => {
|
||||
it('allows http URLs', () => {
|
||||
expect(() => validateNavigationUrl('http://example.com')).not.toThrow();
|
||||
it('allows http URLs', async () => {
|
||||
await expect(validateNavigationUrl('http://example.com')).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows https URLs', () => {
|
||||
expect(() => validateNavigationUrl('https://example.com/path?q=1')).not.toThrow();
|
||||
it('allows https URLs', async () => {
|
||||
await expect(validateNavigationUrl('https://example.com/path?q=1')).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows localhost', () => {
|
||||
expect(() => validateNavigationUrl('http://localhost:3000')).not.toThrow();
|
||||
it('allows localhost', async () => {
|
||||
await expect(validateNavigationUrl('http://localhost:3000')).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows 127.0.0.1', () => {
|
||||
expect(() => validateNavigationUrl('http://127.0.0.1:8080')).not.toThrow();
|
||||
it('allows 127.0.0.1', async () => {
|
||||
await expect(validateNavigationUrl('http://127.0.0.1:8080')).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('allows private IPs', () => {
|
||||
expect(() => validateNavigationUrl('http://192.168.1.1')).not.toThrow();
|
||||
it('allows private IPs', async () => {
|
||||
await expect(validateNavigationUrl('http://192.168.1.1')).resolves.toBeUndefined();
|
||||
});
|
||||
|
||||
it('blocks file:// scheme', () => {
|
||||
expect(() => validateNavigationUrl('file:///etc/passwd')).toThrow(/scheme.*not allowed/i);
|
||||
it('blocks file:// scheme', async () => {
|
||||
await expect(validateNavigationUrl('file:///etc/passwd')).rejects.toThrow(/scheme.*not allowed/i);
|
||||
});
|
||||
|
||||
it('blocks javascript: scheme', () => {
|
||||
expect(() => validateNavigationUrl('javascript:alert(1)')).toThrow(/scheme.*not allowed/i);
|
||||
it('blocks javascript: scheme', async () => {
|
||||
await expect(validateNavigationUrl('javascript:alert(1)')).rejects.toThrow(/scheme.*not allowed/i);
|
||||
});
|
||||
|
||||
it('blocks data: scheme', () => {
|
||||
expect(() => validateNavigationUrl('data:text/html,<h1>hi</h1>')).toThrow(/scheme.*not allowed/i);
|
||||
it('blocks data: scheme', async () => {
|
||||
await expect(validateNavigationUrl('data:text/html,<h1>hi</h1>')).rejects.toThrow(/scheme.*not allowed/i);
|
||||
});
|
||||
|
||||
it('blocks AWS/GCP metadata endpoint', () => {
|
||||
expect(() => validateNavigationUrl('http://169.254.169.254/latest/meta-data/')).toThrow(/cloud metadata/i);
|
||||
it('blocks AWS/GCP metadata endpoint', async () => {
|
||||
await expect(validateNavigationUrl('http://169.254.169.254/latest/meta-data/')).rejects.toThrow(/cloud metadata/i);
|
||||
});
|
||||
|
||||
it('blocks GCP metadata hostname', () => {
|
||||
expect(() => validateNavigationUrl('http://metadata.google.internal/computeMetadata/v1/')).toThrow(/cloud metadata/i);
|
||||
it('blocks GCP metadata hostname', async () => {
|
||||
await expect(validateNavigationUrl('http://metadata.google.internal/computeMetadata/v1/')).rejects.toThrow(/cloud metadata/i);
|
||||
});
|
||||
|
||||
it('blocks metadata hostname with trailing dot', () => {
|
||||
expect(() => validateNavigationUrl('http://metadata.google.internal./computeMetadata/v1/')).toThrow(/cloud metadata/i);
|
||||
it('blocks Azure metadata hostname', async () => {
|
||||
await expect(validateNavigationUrl('http://metadata.azure.internal/metadata/instance')).rejects.toThrow(/cloud metadata/i);
|
||||
});
|
||||
|
||||
it('blocks metadata IP in hex form', () => {
|
||||
expect(() => validateNavigationUrl('http://0xA9FEA9FE/')).toThrow(/cloud metadata/i);
|
||||
it('blocks metadata hostname with trailing dot', async () => {
|
||||
await expect(validateNavigationUrl('http://metadata.google.internal./computeMetadata/v1/')).rejects.toThrow(/cloud metadata/i);
|
||||
});
|
||||
|
||||
it('blocks metadata IP in decimal form', () => {
|
||||
expect(() => validateNavigationUrl('http://2852039166/')).toThrow(/cloud metadata/i);
|
||||
it('blocks metadata IP in hex form', async () => {
|
||||
await expect(validateNavigationUrl('http://0xA9FEA9FE/')).rejects.toThrow(/cloud metadata/i);
|
||||
});
|
||||
|
||||
it('blocks metadata IP in octal form', () => {
|
||||
expect(() => validateNavigationUrl('http://0251.0376.0251.0376/')).toThrow(/cloud metadata/i);
|
||||
it('blocks metadata IP in decimal form', async () => {
|
||||
await expect(validateNavigationUrl('http://2852039166/')).rejects.toThrow(/cloud metadata/i);
|
||||
});
|
||||
|
||||
it('blocks IPv6 metadata with brackets', () => {
|
||||
expect(() => validateNavigationUrl('http://[fd00::]/')).toThrow(/cloud metadata/i);
|
||||
it('blocks metadata IP in octal form', async () => {
|
||||
await expect(validateNavigationUrl('http://0251.0376.0251.0376/')).rejects.toThrow(/cloud metadata/i);
|
||||
});
|
||||
|
||||
it('throws on malformed URLs', () => {
|
||||
expect(() => validateNavigationUrl('not-a-url')).toThrow(/Invalid URL/i);
|
||||
it('blocks IPv6 metadata with brackets', async () => {
|
||||
await expect(validateNavigationUrl('http://[fd00::]/')).rejects.toThrow(/cloud metadata/i);
|
||||
});
|
||||
|
||||
it('throws on malformed URLs', async () => {
|
||||
await expect(validateNavigationUrl('not-a-url')).rejects.toThrow(/Invalid URL/i);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user