Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 14 additions & 6 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export type EvidenceConfig = Readonly<Record<string, readonly EvidenceRule[]>>

// 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 = {
Expand All @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/session/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'}`
Expand Down