From b574d170d131343695a411f67c08966438bdb1a5 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Sun, 15 Feb 2026 16:59:18 -0800 Subject: [PATCH] feat(security): wire /elevate to session config --- src/daemon/routing.ts | 138 +++++++++++++++++++++++++++++++--- src/gateway/handlers/agent.ts | 119 +++++++++++++++++++++++++++++ 2 files changed, 247 insertions(+), 10 deletions(-) diff --git a/src/daemon/routing.ts b/src/daemon/routing.ts index d97f02d..195ea2f 100644 --- a/src/daemon/routing.ts +++ b/src/daemon/routing.ts @@ -19,6 +19,8 @@ import type { ComponentRegistry } from '../intents/index.js'; import type { RoutingPolicy } from '../routing/index.js'; import { createClientFromConfig } from './models.js'; import type { SkillRegistry } from '../skills/index.js'; +import { auditLogger } from '../audit/index.js'; +import { randomUUID } from 'crypto'; function buildProviderConfigMap(config: Config): Partial> { const providerConfigs: Partial> = {}; @@ -197,7 +199,7 @@ export function createMessageRouter(deps: { effectiveToolRegistry = effectiveToolRegistry.clone(); effectiveToolRegistry.register(createMediaSendTool(collector)); - const orchestrator = new AgentOrchestrator({ + const orchestrator = new AgentOrchestrator({ modelRouter: deps.modelRouter, systemPrompt: effectiveSystemPrompt, session, @@ -218,15 +220,19 @@ export function createMessageRouter(deps: { memoryStore: deps.memoryStore, memoryInjectionStrategy: deps.config.memory?.injection_strategy, memoryMaxInjectionTokens: deps.config.memory?.max_injection_tokens, - toolPolicyContext: { - agent: effectiveTier, - provider: effectiveProvider, - autonomyLevel: deps.config.agents.autonomy_level ?? 'standard', - skillName: activeSkillName, - skillPermissions: activeSkill?.manifest.permissions, - allowedSecretScopes: activeSkill?.manifest.permissions?.secrets, - executionEnvironment, - }, + toolPolicyContext: { + agent: effectiveTier, + provider: effectiveProvider, + sessionId: session.id, + channel, + sender: senderId, + tier: effectiveTier, + autonomyLevel: deps.config.agents.autonomy_level ?? 'standard', + skillName: activeSkillName, + skillPermissions: activeSkill?.manifest.permissions, + allowedSecretScopes: activeSkill?.manifest.permissions?.secrets, + executionEnvironment, + }, attachmentCollector: collector, }); entry = { orchestrator, collector }; @@ -443,6 +449,118 @@ export function createMessageRouter(deps: { session.deleteConfig('modelTier'); return ''; }, + + getElevation: () => { + const untilRaw = session.getConfig('elevation.until_ms'); + const reason = session.getConfig('elevation.reason') ?? ''; + const id = session.getConfig('elevation.id') ?? ''; + if (!untilRaw || !id) { + return 'Elevated mode: off'; + } + const untilMs = Number.parseInt(untilRaw, 10); + if (!Number.isFinite(untilMs)) { + return 'Elevated mode: off'; + } + const now = Date.now(); + if (untilMs <= now) { + session.deleteConfig('elevation.until_ms'); + session.deleteConfig('elevation.reason'); + session.deleteConfig('elevation.id'); + auditLogger?.securityElevationExpired({ + session_id: session.id, + channel: msg.channel, + sender: msg.senderId, + elevation_id: id, + until_ms: untilMs, + reason: reason || undefined, + }); + return 'Elevated mode: off (expired)'; + } + const remainingMs = untilMs - now; + const remainingSec = Math.ceil(remainingMs / 1000); + return `Elevated mode: on (${remainingSec}s remaining)${reason ? ` — ${reason}` : ''}`; + }, + + setElevation: (input: string) => { + const raw = input.trim(); + const parts = raw.split(/\s+/); + const hasYes = parts.includes('--yes') || parts.includes('--confirm'); + const filtered = parts.filter(p => p !== '--yes' && p !== '--confirm'); + + if (filtered.length === 0) { + return 'Usage: /elevate --yes | /elevate off --yes'; + } + + if (filtered[0] === 'off') { + if (!hasYes) { + return 'Refusing to disable elevation without explicit confirmation. Use: /elevate off --yes'; + } + const existingId = session.getConfig('elevation.id') ?? randomUUID(); + const existingUntil = session.getConfig('elevation.until_ms'); + const existingReason = session.getConfig('elevation.reason') ?? ''; + session.deleteConfig('elevation.until_ms'); + session.deleteConfig('elevation.reason'); + session.deleteConfig('elevation.id'); + auditLogger?.securityElevationDisabled({ + session_id: session.id, + channel: msg.channel, + sender: msg.senderId, + elevation_id: existingId, + until_ms: existingUntil ? Number.parseInt(existingUntil, 10) : undefined, + reason: existingReason || undefined, + }); + return 'Elevated mode: off'; + } + + if (!hasYes) { + return 'Refusing to enable elevation without explicit confirmation. Use: /elevate --yes'; + } + + const dur = filtered[0]; + const reason = filtered.slice(1).join(' ').trim(); + const ttlMs = (() => { + const m = dur.match(/^(\d+)([smhd])$/i); + if (!m) { + return null; + } + const n = Number.parseInt(m[1], 10); + if (!Number.isFinite(n) || n <= 0) { + return null; + } + const unit = m[2].toLowerCase(); + if (unit === 's') {return n * 1000;} + if (unit === 'm') {return n * 60_000;} + if (unit === 'h') {return n * 3_600_000;} + if (unit === 'd') {return n * 86_400_000;} + return null; + })(); + + if (!ttlMs) { + return 'Invalid duration. Use one of: 30s, 10m, 1h, 1d'; + } + + const untilMs = Date.now() + ttlMs; + const id = randomUUID(); + session.setConfig('elevation.until_ms', String(untilMs)); + session.setConfig('elevation.id', id); + if (reason) { + session.setConfig('elevation.reason', reason); + } else { + session.deleteConfig('elevation.reason'); + } + + auditLogger?.securityElevationEnabled({ + session_id: session.id, + channel: msg.channel, + sender: msg.senderId, + elevation_id: id, + until_ms: untilMs, + ttl_ms: ttlMs, + reason: reason || undefined, + }); + + return `Elevated mode: on until ${new Date(untilMs).toISOString()}`; + }, }, }); diff --git a/src/gateway/handlers/agent.ts b/src/gateway/handlers/agent.ts index 4dbe15c..9a21da8 100644 --- a/src/gateway/handlers/agent.ts +++ b/src/gateway/handlers/agent.ts @@ -8,6 +8,8 @@ import type { Attachment } from '../../channels/types.js'; import type { SessionManager } from '../../session/manager.js'; import type { ModelTier } from '../../models/router.js'; import type { CommandRegistry } from '../../commands/index.js'; +import { auditLogger } from '../../audit/index.js'; +import { randomUUID } from 'crypto'; export interface AgentHandlerDeps { sessionBridge: SessionBridge; @@ -129,6 +131,123 @@ export function createAgentHandlers(deps: AgentHandlerDeps) { } return 'Session reset.'; }, + + getElevation: () => { + if (!sessionId || !deps.sessionManager) { + return 'Elevated mode: off'; + } + const untilRaw = deps.sessionManager.getSessionConfig('ws', sessionId, 'elevation.until_ms'); + const reason = deps.sessionManager.getSessionConfig('ws', sessionId, 'elevation.reason') ?? ''; + const id = deps.sessionManager.getSessionConfig('ws', sessionId, 'elevation.id') ?? ''; + if (!untilRaw || !id) { + return 'Elevated mode: off'; + } + const untilMs = Number.parseInt(untilRaw, 10); + if (!Number.isFinite(untilMs)) { + return 'Elevated mode: off'; + } + const now = Date.now(); + if (untilMs <= now) { + deps.sessionManager.deleteSessionConfig('ws', sessionId, 'elevation.until_ms'); + deps.sessionManager.deleteSessionConfig('ws', sessionId, 'elevation.reason'); + deps.sessionManager.deleteSessionConfig('ws', sessionId, 'elevation.id'); + auditLogger?.securityElevationExpired({ + session_id: `ws:${sessionId}`, + channel: 'ws', + sender: connectionId, + elevation_id: id, + until_ms: untilMs, + reason: reason || undefined, + }); + return 'Elevated mode: off (expired)'; + } + const remainingMs = untilMs - now; + const remainingSec = Math.ceil(remainingMs / 1000); + return `Elevated mode: on (${remainingSec}s remaining)${reason ? ` — ${reason}` : ''}`; + }, + + setElevation: (input: string) => { + if (!sessionId || !deps.sessionManager) { + return 'Elevate command is not available in this session.'; + } + const raw = input.trim(); + const parts = raw.split(/\s+/); + const hasYes = parts.includes('--yes') || parts.includes('--confirm'); + const filtered = parts.filter(p => p !== '--yes' && p !== '--confirm'); + + if (filtered.length === 0) { + return 'Usage: /elevate --yes | /elevate off --yes'; + } + + if (filtered[0] === 'off') { + if (!hasYes) { + return 'Refusing to disable elevation without explicit confirmation. Use: /elevate off --yes'; + } + const existingId = deps.sessionManager.getSessionConfig('ws', sessionId, 'elevation.id') ?? randomUUID(); + const existingUntil = deps.sessionManager.getSessionConfig('ws', sessionId, 'elevation.until_ms'); + const existingReason = deps.sessionManager.getSessionConfig('ws', sessionId, 'elevation.reason') ?? ''; + deps.sessionManager.deleteSessionConfig('ws', sessionId, 'elevation.until_ms'); + deps.sessionManager.deleteSessionConfig('ws', sessionId, 'elevation.reason'); + deps.sessionManager.deleteSessionConfig('ws', sessionId, 'elevation.id'); + auditLogger?.securityElevationDisabled({ + session_id: `ws:${sessionId}`, + channel: 'ws', + sender: connectionId, + elevation_id: existingId, + until_ms: existingUntil ? Number.parseInt(existingUntil, 10) : undefined, + reason: existingReason || undefined, + }); + return 'Elevated mode: off'; + } + + if (!hasYes) { + return 'Refusing to enable elevation without explicit confirmation. Use: /elevate --yes'; + } + + const dur = filtered[0]; + const reason = filtered.slice(1).join(' ').trim(); + const ttlMs = (() => { + const m = dur.match(/^(\d+)([smhd])$/i); + if (!m) { + return null; + } + const n = Number.parseInt(m[1], 10); + if (!Number.isFinite(n) || n <= 0) { + return null; + } + const unit = m[2].toLowerCase(); + if (unit === 's') {return n * 1000;} + if (unit === 'm') {return n * 60_000;} + if (unit === 'h') {return n * 3_600_000;} + if (unit === 'd') {return n * 86_400_000;} + return null; + })(); + if (!ttlMs) { + return 'Invalid duration. Use one of: 30s, 10m, 1h, 1d'; + } + + const untilMs = Date.now() + ttlMs; + const id = randomUUID(); + deps.sessionManager.setSessionConfig('ws', sessionId, 'elevation.until_ms', String(untilMs)); + deps.sessionManager.setSessionConfig('ws', sessionId, 'elevation.id', id); + if (reason) { + deps.sessionManager.setSessionConfig('ws', sessionId, 'elevation.reason', reason); + } else { + deps.sessionManager.deleteSessionConfig('ws', sessionId, 'elevation.reason'); + } + + auditLogger?.securityElevationEnabled({ + session_id: `ws:${sessionId}`, + channel: 'ws', + sender: connectionId, + elevation_id: id, + until_ms: untilMs, + ttl_ms: ttlMs, + reason: reason || undefined, + }); + + return `Elevated mode: on until ${new Date(untilMs).toISOString()}`; + }, }, });