diff --git a/docs/channels.md b/docs/channels.md index d614d873..6477012c 100644 --- a/docs/channels.md +++ b/docs/channels.md @@ -230,7 +230,7 @@ await channels.resolveApproval({ id: 'the-approval-id', approved: true }); | Channel | Request | Response | | ------- | ------- | -------- | | `httpChannel({ name?, verify? })` | JSON `{ sessionKey, input }` (`input` a string or content parts); anything else is a 400. Without `verify`, a body sent without `Content-Type: application/json` is a `415` (`LOUSHO_UNSUPPORTED_MEDIA_TYPE`), so a web page cannot post a form to it | `200 { sessionId, text, finishReason }`; when paused, `approval` too and `text` is the prompt | -| `webhookChannel({ secret?, auth?, name?, principal? })` | The JSON body's `input` string, or the whole body; a one-shot session unless the body has a `sessionKey` | `200` with the turn's `ExecutionResult` | +| `webhookChannel({ secret?, auth?, name?, principal?, fullResult? })` | The JSON body's `input` string, or the whole body; a one-shot session unless the body has a `sessionKey` | `200` with `{ sessionId, text, finishReason, approval? }` (the full `ExecutionResult` with `fullResult: true`) | | `slackChannel({ signingSecret, botToken, name?, fetch?, approvers?, onError? })` | Slack Events API and interactivity requests (see [Slack](#slack)) | `200` at once; replies are posted in the Slack thread | | `discordChannel({ publicKey, applicationId, botToken?, name?, fetch?, approvers?, onError? })` | Discord slash-command and button interactions (see [Discord](#discord)) | Deferred ack at once; the reply edits the original response | | `telegramChannel({ botToken, secretToken, botUsername?, name?, fetch?, approvers?, onError? })` | Telegram bot webhook updates: messages and inline-keyboard taps (see [Telegram](#telegram)) | `200` at once; replies are sent with `sendMessage` as plain text | diff --git a/llms-full.txt b/llms-full.txt index b402109c..75b29ff2 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -4231,7 +4231,7 @@ await channels.resolveApproval({ id: 'the-approval-id', approved: true }); | Channel | Request | Response | | ------- | ------- | -------- | | `httpChannel({ name?, verify? })` | JSON `{ sessionKey, input }` (`input` a string or content parts); anything else is a 400. Without `verify`, a body sent without `Content-Type: application/json` is a `415` (`LOUSHO_UNSUPPORTED_MEDIA_TYPE`), so a web page cannot post a form to it | `200 { sessionId, text, finishReason }`; when paused, `approval` too and `text` is the prompt | -| `webhookChannel({ secret?, auth?, name?, principal? })` | The JSON body's `input` string, or the whole body; a one-shot session unless the body has a `sessionKey` | `200` with the turn's `ExecutionResult` | +| `webhookChannel({ secret?, auth?, name?, principal?, fullResult? })` | The JSON body's `input` string, or the whole body; a one-shot session unless the body has a `sessionKey` | `200` with `{ sessionId, text, finishReason, approval? }` (the full `ExecutionResult` with `fullResult: true`) | | `slackChannel({ signingSecret, botToken, name?, fetch?, approvers?, onError? })` | Slack Events API and interactivity requests (see [Slack](#slack)) | `200` at once; replies are posted in the Slack thread | | `discordChannel({ publicKey, applicationId, botToken?, name?, fetch?, approvers?, onError? })` | Discord slash-command and button interactions (see [Discord](#discord)) | Deferred ack at once; the reply edits the original response | | `telegramChannel({ botToken, secretToken, botUsername?, name?, fetch?, approvers?, onError? })` | Telegram bot webhook updates: messages and inline-keyboard taps (see [Telegram](#telegram)) | `200` at once; replies are sent with `sendMessage` as plain text | diff --git a/src/channels/httpChannel.test.ts b/src/channels/httpChannel.test.ts index 89b98ccb..85ece272 100644 --- a/src/channels/httpChannel.test.ts +++ b/src/channels/httpChannel.test.ts @@ -12,7 +12,9 @@ describe('httpChannel verify warning', () => { expect(warn).toHaveBeenCalledTimes(1); const message = String(warn.mock.calls[0]?.[0]); expect(message).toContain('[lousho]'); - expect(message).toMatch(/unauthenticated/); + expect(message).toMatch(/unless the server in front of it checks auth/); + expect(message).toMatch(/LOUSHO_API_TOKEN/); + expect(message).not.toMatch(/is unauthenticated/); expect(message).toMatch(/sessionKey/); expect(message).toMatch(/verify/); }); diff --git a/src/channels/httpChannel.ts b/src/channels/httpChannel.ts index 98e33787..e5ba75af 100644 --- a/src/channels/httpChannel.ts +++ b/src/channels/httpChannel.ts @@ -15,7 +15,7 @@ export interface HttpChannelOptions { * JSON `Content-Type` (else `415`), so a web page cannot post to it with a * form; under `lousho dev` its `Host` is checked like the dev routes'. * Omitting it logs a one-time warning: sessions are keyed by the - * caller-chosen `sessionKey`, so an unauthenticated channel exposes them. + * caller-chosen `sessionKey`, so without a `verify` (or auth in the host in front of it) anyone who can reach it can guess them. */ verify?: (req: ChannelRequest) => Promise; } @@ -38,9 +38,9 @@ export interface HttpChannelOptions { export function httpChannel(options: HttpChannelOptions = {}): Channel { if (!options.verify) { console.warn( - '[lousho] httpChannel() has no `verify`: the channel is unauthenticated, and sessions are keyed by the caller-chosen `sessionKey`, ' + - "so anyone who can reach it can read or continue another user's conversation by guessing a key. " + - 'Pass `verify: async (req) => req.headers.authorization === `Bearer ${token}`` (see docs/channels.md#securing-the-http-channel).' + '[lousho] httpChannel() has no `verify`: unless the server in front of it checks auth (the built node-server does when LOUSHO_API_TOKEN or auth.ts is set), ' + + 'anyone who can reach it can use the agent and guess `sessionKey` values to read or continue other conversations. ' + + 'Pass `verify: async (req) => req.headers.authorization === `Bearer ${token}`` to authenticate requests yourself (see docs/channels.md#securing-the-http-channel).' ); } return defineChannel({