* add security hardening features - Add authentication vault (`auth save/login/list/show/delete`) so credentials are stored locally and never exposed to the LLM (fixes Snyk W007) - Add `--content-boundaries` flag to wrap page-sourced output in structural markers, helping LLMs distinguish tool output from untrusted page content (fixes Snyk W011) - Add `--allowed-domains` flag to restrict browser navigation to trusted domains - Add `--action-policy` for static allow/deny gating of action categories, with opt-in `--confirm-actions`/`--confirm-interactive` for orchestrator or human-in-the-loop confirmation - Add `--max-output` flag to truncate large page outputs, preventing context flooding - New docs page at /security, updated README, SKILL.md, CLI help text, and templates * fixes * fixes * fixes * fixes * fixes * fixes * fixes * docs
68 lines
2.3 KiB
TypeScript
68 lines
2.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { requestConfirmation, getAndRemovePending } from './confirmation.js';
|
|
|
|
describe('confirmation', () => {
|
|
beforeEach(() => {
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
});
|
|
|
|
describe('requestConfirmation', () => {
|
|
it('should return a confirmation ID', () => {
|
|
const result = requestConfirmation('evaluate', 'eval', 'Evaluate JS', { script: 'test' });
|
|
expect(result.confirmationId).toBeTruthy();
|
|
expect(result.confirmationId).toMatch(/^c_[0-9a-f]{16}$/);
|
|
});
|
|
|
|
it('should generate unique IDs', () => {
|
|
const r1 = requestConfirmation('evaluate', 'eval', 'desc', {});
|
|
const r2 = requestConfirmation('click', 'click', 'desc', {});
|
|
expect(r1.confirmationId).not.toBe(r2.confirmationId);
|
|
});
|
|
});
|
|
|
|
describe('getAndRemovePending', () => {
|
|
it('should retrieve and remove a pending confirmation', () => {
|
|
const { confirmationId } = requestConfirmation('evaluate', 'eval', 'desc', {
|
|
action: 'evaluate',
|
|
script: 'test',
|
|
});
|
|
|
|
const entry = getAndRemovePending(confirmationId);
|
|
expect(entry).not.toBeNull();
|
|
expect(entry!.action).toBe('evaluate');
|
|
expect(entry!.command).toEqual({ action: 'evaluate', script: 'test' });
|
|
});
|
|
|
|
it('should return null on second retrieval (already removed)', () => {
|
|
const { confirmationId } = requestConfirmation('evaluate', 'eval', 'desc', {});
|
|
getAndRemovePending(confirmationId);
|
|
expect(getAndRemovePending(confirmationId)).toBeNull();
|
|
});
|
|
|
|
it('should return null for non-existent ID', () => {
|
|
expect(getAndRemovePending('c_nonexistent')).toBeNull();
|
|
});
|
|
|
|
it('should auto-deny after 60 seconds', () => {
|
|
const { confirmationId } = requestConfirmation('evaluate', 'eval', 'desc', {});
|
|
|
|
vi.advanceTimersByTime(60_000);
|
|
|
|
expect(getAndRemovePending(confirmationId)).toBeNull();
|
|
});
|
|
|
|
it('should still be retrievable before 60 second timeout', () => {
|
|
const { confirmationId } = requestConfirmation('evaluate', 'eval', 'desc', {});
|
|
|
|
vi.advanceTimersByTime(59_999);
|
|
|
|
const entry = getAndRemovePending(confirmationId);
|
|
expect(entry).not.toBeNull();
|
|
});
|
|
});
|
|
});
|