@etzelm/openclaw-memory-hindsight 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.2 (2026-11-06)
4
+
5
+ ### Security
6
+
7
+ - Refuse plain http to a non-loopback `hindsightApiUrl` unless the new `allowInsecureHttp` option
8
+ is `true` (CWE-319, cleartext transmission). Before, 0.1.1 sent the bearer token and
9
+ conversation text unencrypted and only warned when a token was set. Config resolution now
10
+ throws an error naming `allowInsecureHttp`; loopback http and https are unchanged.
11
+ - With the opt-in set, the service start warning says both the bearer token (if any) and
12
+ conversation text go unencrypted, even when no token is configured.
13
+ - Every request to Hindsight is sent with `redirect: "error"`, so an https endpoint cannot answer
14
+ 307/308 and move the body (conversation text) to plain http.
15
+ - Upgrade note: a config that used a remote `http://` URL must switch to https, a loopback
16
+ tunnel, or set `allowInsecureHttp: true`.
17
+
3
18
  ## 0.1.1 (2026-11-06)
4
19
 
5
20
  First version published to npm and ClawHub (0.1.0 was never published). Pre-publish hardening
package/README.md CHANGED
@@ -116,6 +116,7 @@ tool conflict.
116
116
  | `excludeProviders` | `[]` | Message providers that never recall or retain (for example `heartbeat`) |
117
117
  | `skipOperationalSessions` | `true` | Skip cron, heartbeat and subagent sessions |
118
118
  | `agentBankMap` | `{}` | Agent id to bank id, so several agents share one owner bank |
119
+ | `allowInsecureHttp` | `false` | Allow plain http to a non-loopback `hindsightApiUrl` (token and conversation text unencrypted) |
119
120
 
120
121
  Fixed values (not configurable): recall max tokens 1024, query cap 800 characters, recall
121
122
  timeout 10 s, retain and reflect timeout 30 s, memory get timeout 10 s (each combined with the
@@ -158,6 +159,15 @@ as of the vendor plugin 1.2.0:
158
159
 
159
160
  The vendor plugin has features this one does not port; see [Limitations](#limitations).
160
161
 
162
+ ## Security
163
+
164
+ Every request carries the bearer token (if set) and, for recall and retain, conversation text. The
165
+ plugin refuses a plain `http://` `hindsightApiUrl` to any host other than `localhost`, `127.0.0.0/8`
166
+ or `::1`, and config resolution fails with an error that names `allowInsecureHttp`. Prefer `https://`
167
+ or a loopback tunnel. To accept the risk on a trusted network, set `allowInsecureHttp: true`; the
168
+ plugin then warns at service start that the token and conversation text travel unencrypted.
169
+ Redirects are refused, so an https endpoint cannot forward a request body to plain http.
170
+
161
171
  ## Compatibility
162
172
 
163
173
  `openclaw.install.minHostVersion` and `openclaw.compat.pluginApi` are `>=2026.10.1`. Evidence
package/dist/client.js CHANGED
@@ -41,6 +41,7 @@ export class HindsightClient {
41
41
  const response = await this.fetchImpl(url, {
42
42
  method: init.method,
43
43
  headers: this.headers(hasBody),
44
+ redirect: "error",
44
45
  ...(hasBody ? { body: JSON.stringify(init.body) } : {}),
45
46
  ...(init.signal ? { signal: init.signal } : {}),
46
47
  });
@@ -110,6 +111,7 @@ export class HindsightClient {
110
111
  const response = await this.fetchImpl(`${apiUrl}/health`, {
111
112
  method: "GET",
112
113
  headers: this.headers(false),
114
+ redirect: "error",
113
115
  signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS),
114
116
  });
115
117
  return response.ok;
package/dist/config.js CHANGED
@@ -27,6 +27,7 @@ const CONFIG_KEYS = [
27
27
  "excludeProviders",
28
28
  "skipOperationalSessions",
29
29
  "agentBankMap",
30
+ "allowInsecureHttp",
30
31
  ];
31
32
  function isRecord(value) {
32
33
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -160,8 +161,13 @@ export const hindsightConfigSchema = {
160
161
  : readBool(value, "preferObservations", false);
161
162
  const retainMission = readString(value, "retainMission");
162
163
  const apiToken = readApiToken(value);
164
+ const apiUrl = normalizeApiUrl(readString(value, "hindsightApiUrl"));
165
+ const allowInsecureHttp = readBool(value, "allowInsecureHttp", false);
166
+ if (isPlainHttpRemote(apiUrl) && !allowInsecureHttp) {
167
+ throw new Error("hindsightApiUrl is plain http to a non-loopback host, so the bearer token and conversation text would travel unencrypted; use https or a loopback tunnel, or set allowInsecureHttp: true to accept the risk");
168
+ }
163
169
  return {
164
- apiUrl: normalizeApiUrl(readString(value, "hindsightApiUrl")),
170
+ apiUrl,
165
171
  ...(apiToken ? { apiToken } : {}),
166
172
  memoryMode: readEnum(value, "memoryMode", MEMORY_MODES, "hybrid"),
167
173
  autoRecall: readBool(value, "autoRecall", true),
@@ -175,6 +181,7 @@ export const hindsightConfigSchema = {
175
181
  excludeProviders: readStringList(value, "excludeProviders") ?? [],
176
182
  skipOperationalSessions: readBool(value, "skipOperationalSessions", true),
177
183
  agentBankMap: readBankMap(value),
184
+ allowInsecureHttp,
178
185
  };
179
186
  },
180
187
  };
package/dist/index.js CHANGED
@@ -79,11 +79,17 @@ export default definePluginEntry({
79
79
  return startupConfig;
80
80
  }
81
81
  };
82
- const service = createHindsightService({ config: currentConfig, logger: api.logger });
82
+ const service = createHindsightService({
83
+ config: currentConfig,
84
+ logger: api.logger,
85
+ });
83
86
  const flushDeduper = createFlushDeduper();
84
87
  const runStartBySession = new Map();
85
88
  const capability = {
86
- providerRuntime: createHindsightProviderRuntime({ providerId: PLUGIN_ID, service }),
89
+ providerRuntime: createHindsightProviderRuntime({
90
+ providerId: PLUGIN_ID,
91
+ service,
92
+ }),
87
93
  providerFlushPlanResolver: createProviderFlushPlanResolver(currentConfig),
88
94
  recallToolNames: RECALL_TOOL_NAMES,
89
95
  };
@@ -119,7 +125,10 @@ export default definePluginEntry({
119
125
  !config.autoRecall ||
120
126
  config.memoryMode === "tools" ||
121
127
  isIncognitoSessionKey(sessionKey) ||
122
- !sessionUsesMemory(config, { sessionKey, messageProvider: ctx.messageProvider })) {
128
+ !sessionUsesMemory(config, {
129
+ sessionKey,
130
+ messageProvider: ctx.messageProvider,
131
+ })) {
123
132
  return undefined;
124
133
  }
125
134
  const route = routeAudience(ctx.agentId, ctx.memoryAudience, config.agentBankMap);
@@ -149,7 +158,9 @@ export default definePluginEntry({
149
158
  }
150
159
  api.logger.info?.(`memory-hindsight: injecting ${results.length} memories`);
151
160
  // Before the user message, outside the system prompt, so the cached prefix stays stable.
152
- return { prependContext: buildRecallContext(results, config.recallPromptPreamble) };
161
+ return {
162
+ prependContext: buildRecallContext(results, config.recallPromptPreamble),
163
+ };
153
164
  }
154
165
  catch (error) {
155
166
  api.logger.warn?.(`memory-hindsight: auto-recall skipped: ${formatErrorMessage(error)}`);
@@ -170,7 +181,10 @@ export default definePluginEntry({
170
181
  !Array.isArray(event.messages) ||
171
182
  event.messages.length === 0 ||
172
183
  isIncognitoSessionKey(sessionKey) ||
173
- !sessionUsesMemory(config, { sessionKey, messageProvider: ctx.messageProvider })) {
184
+ !sessionUsesMemory(config, {
185
+ sessionKey,
186
+ messageProvider: ctx.messageProvider,
187
+ })) {
174
188
  return;
175
189
  }
176
190
  const route = routeAudience(ctx.agentId, ctx.memoryAudience, config.agentBankMap);
@@ -238,8 +252,9 @@ export default definePluginEntry({
238
252
  if (hasUnresolvedToken(raw)) {
239
253
  api.logger.warn("memory-hindsight: hindsightApiToken not resolved; requests go unauthenticated");
240
254
  }
241
- else if (config.apiToken && isPlainHttpRemote(config.apiUrl)) {
242
- api.logger.warn("memory-hindsight: hindsightApiUrl is plain http to a non-loopback host; the bearer token is sent unencrypted");
255
+ // Config parsing already refuses plain remote http without the opt-in; warn only when opted in.
256
+ if (config.allowInsecureHttp && isPlainHttpRemote(config.apiUrl)) {
257
+ api.logger.warn("memory-hindsight: allowInsecureHttp is set and hindsightApiUrl is plain http to a non-loopback host; the bearer token (if any) and conversation text are sent unencrypted");
243
258
  }
244
259
  api.logger.info(`memory-hindsight: initialized (mode: ${currentConfig().memoryMode})`);
245
260
  },
@@ -45,6 +45,11 @@
45
45
  "help": "Agent id to owner bank id, so several agents share one private bank (for example wb-dev and wb-dev2 to wb)",
46
46
  "advanced": true
47
47
  },
48
+ "allowInsecureHttp": {
49
+ "label": "Allow Insecure HTTP",
50
+ "help": "Permit plain http to a non-loopback host. Token and conversation text are sent unencrypted; prefer https or a loopback tunnel",
51
+ "advanced": true
52
+ },
48
53
  "retainMission": {
49
54
  "label": "Retain Mission",
50
55
  "help": "Applied to each bank before its first write: what Hindsight should extract",
@@ -66,6 +71,11 @@
66
71
  "type": "string",
67
72
  "enum": ["hybrid", "context", "tools"]
68
73
  },
74
+ "allowInsecureHttp": {
75
+ "type": "boolean",
76
+ "default": false,
77
+ "description": "Allow plain http to a non-loopback hindsightApiUrl; the bearer token and conversation text then travel unencrypted"
78
+ },
69
79
  "autoRecall": {
70
80
  "type": "boolean"
71
81
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etzelm/openclaw-memory-hindsight",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Native Hindsight memory engine for OpenClaw: memory slot provider runtime, auto-recall, auto-retain, pre-compaction flush and hindsight_* agent tools.",
5
5
  "keywords": [
6
6
  "hindsight",