mirror of
https://github.com/xmanrui/OpenClaw-bot-review.git
synced 2026-08-14 08:51:57 +00:00
fix: document and harden operator token handling
- document OPENCLAW_OPERATOR_TOKEN setup for R&D/Self-Improvement APIs - compare operator tokens with timingSafeEqual - keep unauthenticated localhost mode explicitly opt-in only
This commit is contained in:
@@ -75,6 +75,28 @@ OPENCLAW_HOME=/opt/openclaw
|
||||
npm run start
|
||||
```
|
||||
|
||||
### R&D Council operator APIs
|
||||
|
||||
The Self-Improvement and R&D Council actions can write local files such as `rd-council-items.json`, `idea_ledger.json`, decision logs, and local work-order handoff state. For safety, those operator APIs require an explicit token by default:
|
||||
|
||||
```bash
|
||||
OPENCLAW_OPERATOR_TOKEN="replace-with-a-long-random-token" npm run start
|
||||
```
|
||||
|
||||
Then set the same token in the browser console for the local dashboard origin:
|
||||
|
||||
```js
|
||||
localStorage.setItem("openclaw_operator_token", "replace-with-a-long-random-token")
|
||||
```
|
||||
|
||||
For trusted local development only, you can opt out of token auth and rely on localhost checks:
|
||||
|
||||
```bash
|
||||
OPENCLAW_ALLOW_UNAUTHENTICATED_LOCAL_OPERATOR_UI=true npm run start
|
||||
```
|
||||
|
||||
Do not enable unauthenticated local mode on a dashboard exposed through a tunnel, shared host, reverse proxy, or public network.
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
You can also deploy the dashboard using Docker:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
@@ -121,16 +122,26 @@ function hostnameFromHostHeader(value) {
|
||||
return raw.split(':')[0];
|
||||
}
|
||||
|
||||
function constantTimeEqual(left, right) {
|
||||
const leftValue = nonEmptyString(left);
|
||||
const rightValue = nonEmptyString(right);
|
||||
if (!leftValue || !rightValue) return false;
|
||||
const leftBuffer = Buffer.from(leftValue);
|
||||
const rightBuffer = Buffer.from(rightValue);
|
||||
if (leftBuffer.length !== rightBuffer.length) return false;
|
||||
return crypto.timingSafeEqual(leftBuffer, rightBuffer);
|
||||
}
|
||||
|
||||
export function validateLocalOperatorRequest(request, opts = {}) {
|
||||
const expectedToken = nonEmptyString(process.env.OPENCLAW_OPERATOR_TOKEN);
|
||||
const allowUnauthenticatedLocal = process.env.OPENCLAW_ALLOW_UNAUTHENTICATED_LOCAL_OPERATOR_UI === 'true' || opts.allowUnauthenticatedLocal === true;
|
||||
const authHeader = nonEmptyString(request?.headers?.get?.('authorization'));
|
||||
const bearerToken = authHeader?.toLowerCase().startsWith('bearer ') ? authHeader.slice(7).trim() : null;
|
||||
const headerToken = nonEmptyString(request?.headers?.get?.('x-openclaw-operator-token')) || bearerToken;
|
||||
if (expectedToken && headerToken === expectedToken) {
|
||||
if (expectedToken && constantTimeEqual(headerToken, expectedToken)) {
|
||||
return { ok: true, reason: null };
|
||||
}
|
||||
if (expectedToken && headerToken !== expectedToken) {
|
||||
if (expectedToken && !constantTimeEqual(headerToken, expectedToken)) {
|
||||
return { ok: false, status: 401, reason: 'R&D Council operator token is missing or invalid.' };
|
||||
}
|
||||
if (!allowUnauthenticatedLocal) {
|
||||
|
||||
Reference in New Issue
Block a user