diff --git a/README.md b/README.md index bb15901..6bd9698 100644 --- a/README.md +++ b/README.md @@ -626,6 +626,12 @@ Control sensitive operations with pattern matching: hooks: confirm: # Requires user approval via Telegram - shell.* + - process.start + - process.kill + - browser.* + - message.send + - cron.create + - cron.delete - file.write - file.patch log: # Logs but doesn't block @@ -640,7 +646,7 @@ For unrestricted deployments, pair hooks with agent-level sensitive gating: ```yaml agents: # deny_without_elevation | confirm_without_elevation - sensitive_mode: deny_without_elevation + sensitive_mode: confirm_without_elevation immutable_denylist: - tool: shell.exec args_pattern: "git push origin main" diff --git a/config/default.yaml b/config/default.yaml index 84f5860..9da085a 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -246,6 +246,12 @@ models: hooks: confirm: - shell.* + - process.start + - process.kill + - browser.* + - message.send + - cron.create + - cron.delete - file.write - file.patch log: @@ -260,6 +266,11 @@ hooks: # Those permissions are enforced at runtime when requests are routed into a skill context. # - See: docs/security/SAFE_PERSONAL_AGENT.md +agents: + # In full-access mode, sensitive operations are gated by HookEngine confirmation + # (instead of requiring temporary /elevate windows). + sensitive_mode: confirm_without_elevation + # ── Prompt Assembly ─────────────────────────────────────────────────── # Tune how much context Flynn loads into the system prompt. # diff --git a/docs/plans/state.json b/docs/plans/state.json index 67700d4..b633c8d 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -5390,6 +5390,24 @@ "docs/plans/state.json" ], "test_status": "pnpm test:run src/cli/companion.test.ts src/cli/index.test.ts + pnpm typecheck passing" + }, + "full-access-hooks-guard-defaults": { + "status": "completed", + "date": "2026-02-18", + "updated": "2026-02-18", + "summary": "Aligned default behavior to full-access-with-hooks: setup now defaults to `tools.profile: full`, sensitive host operations default to `agents.sensitive_mode: confirm_without_elevation`, and default hook confirmations were expanded to cover high-impact actions (shell/process/browser/message send/cron mutations/file writes). This removes mandatory `/elevate` for standard operation while keeping explicit confirmation gates.", + "files_modified": [ + "src/config/schema.ts", + "src/config/schema.test.ts", + "src/cli/setup/config.ts", + "src/cli/setup/config.test.ts", + "src/cli/setup/security.ts", + "src/cli/setup/sections.test.ts", + "config/default.yaml", + "README.md", + "docs/plans/state.json" + ], + "test_status": "pnpm test:run src/config/schema.test.ts src/cli/setup/config.test.ts src/cli/setup/sections.test.ts + pnpm typecheck passing" } }, "overall_progress": { diff --git a/src/cli/setup/config.test.ts b/src/cli/setup/config.test.ts index aa8ec48..3d78676 100644 --- a/src/cli/setup/config.test.ts +++ b/src/cli/setup/config.test.ts @@ -10,6 +10,11 @@ describe('ConfigBuilder', () => { expect(obj.models.default.provider).toBe('anthropic'); expect(obj.models.default.api_key).toBe('sk-ant-test'); expect(obj.server.port).toBe(3777); + expect(obj.tools?.profile).toBe('full'); + expect(obj.agents?.sensitive_mode).toBe('confirm_without_elevation'); + expect((obj.hooks?.confirm as string[]) ?? []).toEqual( + expect.arrayContaining(['shell.*', 'browser.*', 'message.send']), + ); }); it('adds telegram channel', () => { diff --git a/src/cli/setup/config.ts b/src/cli/setup/config.ts index af5b93f..096953b 100644 --- a/src/cli/setup/config.ts +++ b/src/cli/setup/config.ts @@ -33,6 +33,9 @@ export interface SetupConfig { sandbox?: { enabled?: boolean }; pairing?: { enabled?: boolean }; tools?: { profile?: string }; + agents?: { + sensitive_mode?: 'deny_without_elevation' | 'confirm_without_elevation'; + }; agent_configs?: Record; + agents.sensitive_mode = mode; + this.config.agents = agents as SetupConfig['agents']; + } + setResearchAgentEnabled(options: ResearchAgentOptions): void { const agentConfigs = (this.config.agent_configs ?? {}) as Record>; const existing = (agentConfigs.research ?? {}) as Record; diff --git a/src/cli/setup/sections.test.ts b/src/cli/setup/sections.test.ts index 70e5197..140a41c 100644 --- a/src/cli/setup/sections.test.ts +++ b/src/cli/setup/sections.test.ts @@ -60,6 +60,8 @@ describe('setupSecurity', () => { const config = builder.build(); expect(config.sandbox!.enabled).toBe(true); expect(config.pairing!.enabled).toBe(true); + expect(config.tools?.profile).toBe('full'); + expect(config.agents?.sensitive_mode).toBe('confirm_without_elevation'); expect(config.agent_configs?.research?.model_tier).toBe('complex'); expect(config.agent_configs?.research?.tool_profile).toBe('messaging'); }); diff --git a/src/cli/setup/security.ts b/src/cli/setup/security.ts index b70eeef..a655052 100644 --- a/src/cli/setup/security.ts +++ b/src/cli/setup/security.ts @@ -2,10 +2,10 @@ import type { Prompter } from './prompts.js'; import type { ConfigBuilder } from './config.js'; const TOOL_PROFILES = [ - { label: 'messaging (recommended)', value: 'messaging' }, - { label: 'minimal (status only)', value: 'minimal' }, + { label: 'full (recommended)', value: 'full' }, { label: 'coding (fs + runtime)', value: 'coding' }, - { label: 'full (unrestricted)', value: 'full' }, + { label: 'messaging (read + services, no shell/writes)', value: 'messaging' }, + { label: 'minimal (status only)', value: 'minimal' }, ]; const RESEARCH_AGENT_TIERS = [ @@ -35,12 +35,13 @@ export async function setupSecurity(p: Prompter, builder: ConfigBuilder): Promis p.println(); p.println(' Tool profiles control which tools the agent can use:'); - p.println(' full — all tools available (file, shell, web, memory, messaging)'); + p.println(' full — all tools available (file, shell, web, memory, messaging) (default)'); p.println(' coding — file system + shell + sessions + memory'); p.println(' messaging — read-only + web/memory + connected services (no file writes/shell)'); p.println(' minimal — status checks only (read-only, safest)'); const profile = await p.choose('Tool policy profile:', TOOL_PROFILES); builder.setToolProfile(profile); + builder.setSensitiveMode('confirm_without_elevation'); p.println(); p.println(' Research agent adds a dedicated specialist for deep web research.'); diff --git a/src/config/schema.test.ts b/src/config/schema.test.ts index bcb44b3..05b6320 100644 --- a/src/config/schema.test.ts +++ b/src/config/schema.test.ts @@ -1544,7 +1544,7 @@ describe('configSchema — agents truthfulness/autonomy', () => { const result = configSchema.parse(minimalConfig); expect(result.agents.truthfulness_mode).toBe('standard'); expect(result.agents.autonomy_level).toBe('standard'); - expect(result.agents.sensitive_mode).toBe('deny_without_elevation'); + expect(result.agents.sensitive_mode).toBe('confirm_without_elevation'); expect(result.agents.immutable_denylist).toEqual( expect.arrayContaining([ expect.objectContaining({ tool: 'shell.exec', args_pattern: 'git push origin main' }), diff --git a/src/config/schema.ts b/src/config/schema.ts index 1d1ea80..9e96df1 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -499,7 +499,7 @@ const agentsSchema = z.object({ /** Autonomy level for tool execution: conservative | standard | autonomous. */ autonomy_level: autonomyLevelSchema.default('standard'), /** Sensitive host-action behavior for high-impact tools. */ - sensitive_mode: sensitiveModeSchema.default('deny_without_elevation'), + sensitive_mode: sensitiveModeSchema.default('confirm_without_elevation'), /** Immutable denylist enforced even during elevated mode. */ immutable_denylist: z.array(immutableDenyRuleSchema).default([ {