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
20 changes: 10 additions & 10 deletions .github/workflows/pr-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,17 @@ jobs:
name: Install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

# Node 22.x ships npm 10, whose peer resolver crashes on MemoryCore's
# vitest@4 peer graph ("Cannot read properties of null (reading 'edgesOut')").
# Node 24 ships npm 11, which resolves it; engines allows >=22.16.0.
- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "24"

- name: Cache node_modules
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cache-deps
with:
path: MemoryCore/node_modules
Expand All @@ -51,14 +51,14 @@ jobs:
needs: install
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- uses: actions/setup-node@v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "24"

- name: Restore node_modules
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: MemoryCore/node_modules
key: deps-${{ runner.os }}-${{ hashFiles('MemoryCore/package.json') }}
Expand All @@ -70,7 +70,7 @@ jobs:
run: npm pack

- name: Upload .tgz artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: package-tarball
path: "MemoryCore/*.tgz"
Expand All @@ -81,7 +81,7 @@ jobs:
name: Manifest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4

- name: Validate openclaw.plugin.json
run: |
Expand Down Expand Up @@ -124,7 +124,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Download tarball
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: package-tarball
path: MemoryCore
Expand Down Expand Up @@ -155,7 +155,7 @@ jobs:
name: Skill Queue Isolation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0 # 需要完整历史以便 git diff base..HEAD

Expand Down
5 changes: 3 additions & 2 deletions MemoryCore/src/gateway/chat-memory-e2e-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,13 @@ export async function post<T = unknown>(
// ── user / team / agent 装置 ──

let adminKey: string | undefined;
let fixtureSeq = 0;

async function seedAdminOnce(): Promise<string> {
if (adminKey) return adminKey;
const r = await post<{ user_key?: string; default_user_key?: string }>(
"/v3/internal/meta/user/init-admin",
{ username: `admin-${Math.random().toString(36).slice(2, 8)}` },
{ username: `admin-${++fixtureSeq}` },
);
expect(r.body.code).toBe(0);
adminKey = r.body.data!.user_key ?? r.body.data!.default_user_key!;
Expand All @@ -117,7 +118,7 @@ export async function makeFixture(tag: string, messageCount = 3): Promise<Fixtur
const key = await seedAdminOnce();
const u = await post<{ user_id: string; default_user_key: string }>(
"/v3/meta/user/create",
{ username: `u-${tag}-${Math.random().toString(36).slice(2, 6)}` },
{ username: `u-${tag}-${++fixtureSeq}` },
{ userKey: key },
);
expect(u.body.code).toBe(0);
Expand Down
5 changes: 2 additions & 3 deletions MemoryCore/src/metadata/utils/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* v3.1 起元数据 User 域不再使用 password 哈希;本模块仅保留 generateUserKey 与历史 hash 工具。
*/

import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
import { randomBytes, randomInt, scryptSync, timingSafeEqual } from "node:crypto";
import { USER_KEY_PREFIX } from "./user-key.js";

/**
Expand All @@ -26,10 +26,9 @@ const PASSWORD_CHARSET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01

/** 生成随机密码(默认 12 位,字符集:大小写字母、数字、下划线)。 */
export function generatePassword(length = 12): string {
const bytes = randomBytes(length);
let out = "";
for (let i = 0; i < length; i++) {
out += PASSWORD_CHARSET[bytes[i]! % PASSWORD_CHARSET.length];
out += PASSWORD_CHARSET[randomInt(PASSWORD_CHARSET.length)];
}
return out;
}
Expand Down
4 changes: 2 additions & 2 deletions MemoryCore/src/metadata/utils/id-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* - 关联关系表:UUID v4(`randomUUID()`)
*/

import { randomUUID } from "node:crypto";
import { randomInt, randomUUID } from "node:crypto";

const CHARS = "abcdefghijklmnopqrstuvwxyz0123456789";
const BASE = CHARS.length; // 36
Expand Down Expand Up @@ -43,7 +43,7 @@ function encodeBase36(value: number, length: number): string {
function randomBase36(length: number): string {
let out = "";
for (let i = 0; i < length; i++) {
out += CHARS[Math.floor(Math.random() * BASE)];
out += CHARS[randomInt(BASE)];
}
return out;
}
Expand Down
8 changes: 7 additions & 1 deletion MemoryCore/src/offload/backend-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ import { traceOffloadModelIo } from "./opik-tracer.js";
import * as https from "node:https";
import * as http from "node:http";

function stripTrailingSlashes(value: string): string {
let end = value.length;
while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
return value.slice(0, end);
}

// ─── Request / Response Types ────────────────────────────────────────────────

export interface L1Request {
Expand Down Expand Up @@ -126,7 +132,7 @@ export class BackendClient {
userIdFn?: () => string | null,
taskIdFn?: () => string | null,
) {
this.baseUrl = baseUrl.replace(/\/+$/, "");
this.baseUrl = stripTrailingSlashes(baseUrl);
this.apiKey = apiKey;
this.logger = logger;
this.sessionKeyFn = sessionKeyFn ?? (() => null);
Expand Down
6 changes: 3 additions & 3 deletions MemoryProxy/src/anthropicHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,9 +421,9 @@ async function forwardWithRetry(
const prefixEnd = anchorIdx >= 0 ? anchorIdx + 1 : msgs.length;
const msgsPrefixStr = JSON.stringify(msgs.slice(0, prefixEnd));

const sysFullMd5 = createHash("md5").update(sysFullStr).digest("hex").slice(0, 12);
const sysTextMd5 = createHash("md5").update(sysTextStr).digest("hex").slice(0, 12);
const msgsPrefixMd5 = createHash("md5").update(msgsPrefixStr).digest("hex").slice(0, 12);
const sysFullMd5 = createHash("sha256").update(sysFullStr).digest("hex").slice(0, 12);
const sysTextMd5 = createHash("sha256").update(sysTextStr).digest("hex").slice(0, 12);
const msgsPrefixMd5 = createHash("sha256").update(msgsPrefixStr).digest("hex").slice(0, 12);

// eslint-disable-next-line no-console
console.log(
Expand Down
3 changes: 3 additions & 0 deletions MemoryProxy/src/identity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ export function extractClientIdentity(
}
}

// Lookup / pseudonymization index for trace grouping, not password storage:
// the input is a high-entropy API key and nothing is persisted for comparison,
// so a fast hash is intentional and CWE-916 does not apply.
const keyId = apiKey
? createHash("sha256").update(apiKey).digest("hex").slice(0, 8)
: "unknown";
Expand Down
3 changes: 3 additions & 0 deletions MemoryProxy/src/langfuse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ export async function shutdownLangfuse(): Promise<void> {
* 与官方 `createTraceId(seed)` 算法一致:SHA-256(seed) 的 hex 取前 32 位。
* 同一 turn 内每次请求都用相同 (sessionKey, turnSeq) → 得到相同 traceId →
* 在 Langfuse 中归并到同一个 trace。
*
* 这是 trace 归并用的确定性索引,不是密码存储/校验:输入为高熵 API key 派生的
* sessionKey,且不落库做比对,因此使用快速哈希是有意为之,CWE-916 不适用。
*/
export function langfuseTurnTraceId(sessionKey: string, turnSeq: number): string {
const seed = `${sessionKey}:${turnSeq}`;
Expand Down
8 changes: 7 additions & 1 deletion MemoryProxy/src/opik.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@ function uuidv7(): string {
return `${p1}-${p2}-${p3}-${p4}-${p5}`;
}

/** Derive an 8-char key ID from an API key (SHA-256 first 8 hex chars). */
/** Derive an 8-char key ID from an API key (SHA-256 first 8 hex chars).
*
* This is a lookup / pseudonymization index for trace grouping, not password
* storage or verification: the input is a high-entropy API key and nothing is
* persisted for later comparison. A fast hash is therefore intentional and
* CWE-916 (insufficient password hash) does not apply.
*/
export function apiKeyToKeyId(apiKey: string): string {
return createHash("sha256").update(apiKey).digest("hex").slice(0, 8);
}
Expand Down
3 changes: 2 additions & 1 deletion sdk/memory-core/typescript/src/cos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/

import { TDAMError } from "./errors.js";
import { stripTrailingSlashes } from "./http.js";
import { createHmac, createHash } from "node:crypto";

// ============================
Expand Down Expand Up @@ -123,7 +124,7 @@ export class StsCredentialManager {
config: MemoryFileReaderConfig,
bufferMs = 120_000,
) {
this.endpoint = config.endpoint.replace(/\/+$/, "");
this.endpoint = stripTrailingSlashes(config.endpoint);
this.apiKey = config.apiKey;
this.serviceId = config.serviceId;
this.timeout = config.timeout ?? 30_000;
Expand Down
8 changes: 7 additions & 1 deletion sdk/memory-core/typescript/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@ export interface HttpTransportOptions {
rejectUnauthorized?: boolean;
}

export function stripTrailingSlashes(value: string): string {
let end = value.length;
while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
return value.slice(0, end);
}

export class HttpTransport {
private readonly endpoint: string;
private readonly headers: Record<string, string>;
private readonly timeout: number;
private dispatcher: unknown;

constructor(opts: HttpTransportOptions) {
this.endpoint = opts.endpoint.replace(/\/+$/, "");
this.endpoint = stripTrailingSlashes(opts.endpoint);
this.timeout = opts.timeout ?? 30_000;
this.headers = {
Authorization: `Bearer ${opts.apiKey}`,
Expand Down
4 changes: 2 additions & 2 deletions sdk/memory-core/typescript/src/v3/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Agent } from "undici";
import { ParamError, TDAMError } from "../errors.js";
import type { HttpTransportOptions } from "../http.js";
import { stripTrailingSlashes, type HttpTransportOptions } from "../http.js";
import type { ApiResponseEnvelope } from "../types.js";

export class V3HttpTransport {
Expand All @@ -28,7 +28,7 @@ export class V3HttpTransport {
throw new ParamError("timeout must be a positive number");
}

this.endpoint = opts.endpoint.replace(/\/+$/, "");
this.endpoint = stripTrailingSlashes(opts.endpoint);
this.timeout = timeout;
this.headers = {
Authorization: `Bearer ${opts.apiKey}`,
Expand Down
Loading