diff --git a/src/config.test.ts b/src/config.test.ts index f09d12c..9dd0ccd 100644 --- a/src/config.test.ts +++ b/src/config.test.ts @@ -55,6 +55,13 @@ describe('parseConfig', () => { expect(parseConfig({ helioBaseUrl: 'not-a-url' }).ok).toBe(false) }) + it('rejects a parseable but non-http(s) base URL', () => { + // URL.canParse alone accepts ftp:/file:/etc; the sideband only speaks HTTP. + expect(parseConfig({ helioBaseUrl: 'ftp://127.0.0.1:3200' }).ok).toBe(false) + expect(parseConfig({ helioBaseUrl: 'file:///etc/passwd' }).ok).toBe(false) + expect(parseConfig({ helioBaseUrl: 'https://helio.internal:3200' }).ok).toBe(true) + }) + it('rejects an evidence rule with an empty path', () => { const result = parseConfig({ evidence: { send_email: [{ key: 'recipient', path: [] }] } }) expect(result.ok).toBe(false) diff --git a/src/config.ts b/src/config.ts index aa48ee3..30c15d6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,11 +17,11 @@ export type EvidenceConfig = Readonly> // Resolved adapter configuration. Mirrors the manifest `configSchema` in openclaw.plugin.json. export interface AdapterConfig { - helioBaseUrl: string - tokenEnv: string - origin: string - evaluateTimeoutMs: number - evidence: EvidenceConfig + readonly helioBaseUrl: string + readonly tokenEnv: string + readonly origin: string + readonly evaluateTimeoutMs: number + readonly evidence: EvidenceConfig } export const DEFAULT_CONFIG: AdapterConfig = { @@ -46,7 +46,15 @@ const evidenceRuleSchema = z const adapterConfigSchema = z.object({ helioBaseUrl: z .string() - .refine((s) => URL.canParse(s), 'must be a valid URL') + .refine((s) => { + // Must be a parseable http(s) URL — the sideband only speaks HTTP, so reject ftp:/file:/etc. + try { + const { protocol } = new URL(s) + return protocol === 'http:' || protocol === 'https:' + } catch { + return false + } + }, 'must be an http(s) URL') .default(DEFAULT_CONFIG.helioBaseUrl), tokenEnv: z.string().min(1).default(DEFAULT_CONFIG.tokenEnv), origin: z diff --git a/src/session/mapping.ts b/src/session/mapping.ts index 36dace4..1d9651b 100644 --- a/src/session/mapping.ts +++ b/src/session/mapping.ts @@ -4,6 +4,12 @@ import type { PluginHookToolContext } from '../types.js' * Map an OpenClaw tool-call context to a stable Helio `session_id`, prefixed `oc:`. * Prefers `sessionId`, then `sessionKey`, then `channelId`; falls back to a stable sentinel * so a session-less call still correlates deterministically (and fails closed on ambiguity). + * + * NOTE: this id feeds both the Helio `session_id` (which scopes session-level policies/limits) and + * the no-ID correlation lane. In the fallback cases — `channelId` (one channel may span several + * logical sessions) or the `'unknown'` sentinel — distinct sessions can collapse onto one id. That + * is correlation-safe (the registry fails closed on the resulting ambiguity), but operators relying + * on per-session scoping should ensure the host supplies `sessionId`/`sessionKey`. */ export function mapSession(ctx: PluginHookToolContext): string { return `oc:${ctx.sessionId ?? ctx.sessionKey ?? ctx.channelId ?? 'unknown'}`