From 758950435cf098b6dd47c0e6d1f6c7dbd809e207 Mon Sep 17 00:00:00 2001 From: Tuhin Kanti Sharma Date: Tue, 17 Feb 2026 22:54:27 -0800 Subject: [PATCH 1/2] Update README with Docker instructions --- README.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/README.md b/README.md index d54ff44..89e7251 100644 --- a/README.md +++ b/README.md @@ -46,3 +46,26 @@ This project implements a minimal viable version of OpenClaw in Java, focusing o - `src/main/java/ai/openclaw/channel` - Console channel implementation - `src/main/java/ai/openclaw/config` - Configuration loader - `src/main/java/ai/openclaw/session` - Session storage + +## Running with Docker + +You can run the application in a Docker container for an isolated environment. + +1. **Build the Docker image**: + ```bash + docker build -t openclaw-java . + ``` + +2. **Run the container**: + You must provide your Anthropic API key as an environment variable. + ```bash + docker run -it --rm \ + -e ANTHROPIC_API_KEY=sk-ant-... \ + -p 18789:18789 \ + openclaw-java + ``` + + The container runs as a non-root user (`openclaw`) for security. + - Code execution is confined to `/home/openclaw/workspace`. + - File access is restricted to the workspace directory. + - Network access to internal/private IPs is blocked. From 9e0a564942cd0ffd2cdfdd5c7fd4d07e2a34878c Mon Sep 17 00:00:00 2001 From: Tuhin Kanti Sharma Date: Tue, 17 Feb 2026 23:09:46 -0800 Subject: [PATCH 2/2] Add WebSocket authentication to GatewayServer - Require 'Authorization: Bearer ' header or '?token=' query param - Validate against configured token using constant-time comparison - Reject unauthorized connections in onOpen - Updated GatewayE2ETest to send auth headers --- .../ai/openclaw/gateway/GatewayServer.java | 53 ++++++++++++++++++- .../java/ai/openclaw/e2e/GatewayE2ETest.java | 4 ++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/main/java/ai/openclaw/gateway/GatewayServer.java b/src/main/java/ai/openclaw/gateway/GatewayServer.java index b3967b8..5aea425 100644 --- a/src/main/java/ai/openclaw/gateway/GatewayServer.java +++ b/src/main/java/ai/openclaw/gateway/GatewayServer.java @@ -29,8 +29,57 @@ public GatewayServer(OpenClawConfig config, RpcRouter router) { @Override public void onOpen(WebSocket conn, ClientHandshake handshake) { - logger.info("New connection from {}", conn.getRemoteSocketAddress()); - // Simple auth check could be added here + String remoteAddress = conn.getRemoteSocketAddress().toString(); + logger.info("New connection from {}", remoteAddress); + + String expectedToken = config.getGateway().getAuthToken(); + if (expectedToken == null || expectedToken.isEmpty()) { + logger.warn("No auth token configured! Accepting connection from {}", remoteAddress); + return; + } + + String providedToken = extractToken(handshake); + if (providedToken == null || !constantTimeEquals(expectedToken, providedToken)) { + logger.warn("Unauthorized connection attempt from {}", remoteAddress); + // Close with policy violation code (1008) or normal code (1000) with reason + conn.close(1008, "Unauthorized"); + return; + } + + logger.info("Authenticated connection from {}", remoteAddress); + } + + private String extractToken(ClientHandshake handshake) { + // 1. Check Authorization header + String authHeader = handshake.getFieldValue("Authorization"); + if (authHeader != null && authHeader.toLowerCase().startsWith("bearer ")) { + return authHeader.substring(7).trim(); + } + + // 2. Check query parameter + String descriptor = handshake.getResourceDescriptor(); + if (descriptor.contains("token=")) { + int index = descriptor.indexOf("token="); + String token = descriptor.substring(index + 6); + int end = token.indexOf('&'); + if (end != -1) { + token = token.substring(0, end); + } + return token; + } + return null; + } + + /** Constant-time string comparison to prevent timing attacks. */ + private boolean constantTimeEquals(String a, String b) { + if (a.length() != b.length()) { + return false; + } + int result = 0; + for (int i = 0; i < a.length(); i++) { + result |= a.charAt(i) ^ b.charAt(i); + } + return result == 0; } @Override diff --git a/src/test/java/ai/openclaw/e2e/GatewayE2ETest.java b/src/test/java/ai/openclaw/e2e/GatewayE2ETest.java index 0febe2b..3d1c3b0 100644 --- a/src/test/java/ai/openclaw/e2e/GatewayE2ETest.java +++ b/src/test/java/ai/openclaw/e2e/GatewayE2ETest.java @@ -108,6 +108,10 @@ public void onError(Exception ex) { } }; + final String authToken = "test-token"; // Use the known token directly or capture from config + if (authToken != null) { + client.addHeader("Authorization", "Bearer " + authToken); + } assertTrue(client.connectBlocking(5, TimeUnit.SECONDS)); // Send request