mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 05:41:24 +00:00
Add explicit ownerDisplaySecret for owner ID hash obfuscation (#22520)
* feat(config): add owner display secret setting * feat(prompt): add explicit owner hash secret to obfuscation path * test(prompt): assert owner hash secret mode behavior * Update src/agents/system-prompt.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
@@ -86,6 +86,11 @@ export function buildSystemPrompt(params: {
|
||||
defaultThinkLevel: params.defaultThinkLevel,
|
||||
extraSystemPrompt: params.extraSystemPrompt,
|
||||
ownerNumbers: params.ownerNumbers,
|
||||
ownerDisplay: params.config?.commands?.ownerDisplay,
|
||||
ownerDisplaySecret:
|
||||
params.config?.commands?.ownerDisplaySecret ??
|
||||
params.config?.gateway?.auth?.token ??
|
||||
params.config?.gateway?.remote?.token,
|
||||
reasoningTagHint: false,
|
||||
heartbeatPrompt: params.heartbeatPrompt,
|
||||
docsPath: params.docsPath,
|
||||
|
||||
@@ -484,6 +484,11 @@ export async function compactEmbeddedPiSessionDirect(
|
||||
reasoningLevel: params.reasoningLevel ?? "off",
|
||||
extraSystemPrompt: params.extraSystemPrompt,
|
||||
ownerNumbers: params.ownerNumbers,
|
||||
ownerDisplay: params.config?.commands?.ownerDisplay,
|
||||
ownerDisplaySecret:
|
||||
params.config?.commands?.ownerDisplaySecret ??
|
||||
params.config?.gateway?.auth?.token ??
|
||||
params.config?.gateway?.remote?.token,
|
||||
reasoningTagHint,
|
||||
heartbeatPrompt: isDefaultAgent
|
||||
? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt)
|
||||
|
||||
@@ -443,6 +443,11 @@ export async function runEmbeddedAttempt(
|
||||
reasoningLevel: params.reasoningLevel ?? "off",
|
||||
extraSystemPrompt: params.extraSystemPrompt,
|
||||
ownerNumbers: params.ownerNumbers,
|
||||
ownerDisplay: params.config?.commands?.ownerDisplay,
|
||||
ownerDisplaySecret:
|
||||
params.config?.commands?.ownerDisplaySecret ??
|
||||
params.config?.gateway?.auth?.token ??
|
||||
params.config?.gateway?.remote?.token,
|
||||
reasoningTagHint,
|
||||
heartbeatPrompt: isDefaultAgent
|
||||
? resolveHeartbeatPrompt(params.config?.agents?.defaults?.heartbeat?.prompt)
|
||||
|
||||
@@ -14,6 +14,8 @@ export function buildEmbeddedSystemPrompt(params: {
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
extraSystemPrompt?: string;
|
||||
ownerNumbers?: string[];
|
||||
ownerDisplay?: "raw" | "hash";
|
||||
ownerDisplaySecret?: string;
|
||||
reasoningTagHint: boolean;
|
||||
heartbeatPrompt?: string;
|
||||
skillsPrompt?: string;
|
||||
@@ -55,6 +57,8 @@ export function buildEmbeddedSystemPrompt(params: {
|
||||
reasoningLevel: params.reasoningLevel,
|
||||
extraSystemPrompt: params.extraSystemPrompt,
|
||||
ownerNumbers: params.ownerNumbers,
|
||||
ownerDisplay: params.ownerDisplay,
|
||||
ownerDisplaySecret: params.ownerDisplaySecret,
|
||||
reasoningTagHint: params.reasoningTagHint,
|
||||
heartbeatPrompt: params.heartbeatPrompt,
|
||||
skillsPrompt: params.skillsPrompt,
|
||||
|
||||
@@ -16,6 +16,45 @@ describe("buildAgentSystemPrompt", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("hashes owner numbers when ownerDisplay is hash", () => {
|
||||
const prompt = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
ownerNumbers: ["+123", "+456", ""],
|
||||
ownerDisplay: "hash",
|
||||
});
|
||||
|
||||
expect(prompt).toContain("## Authorized Senders");
|
||||
expect(prompt).toContain("Authorized senders:");
|
||||
expect(prompt).not.toContain("+123");
|
||||
expect(prompt).not.toContain("+456");
|
||||
expect(prompt).toMatch(/[a-f0-9]{12}/);
|
||||
});
|
||||
|
||||
it("uses a stable, keyed HMAC when ownerDisplaySecret is provided", () => {
|
||||
const secretA = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
ownerNumbers: ["+123"],
|
||||
ownerDisplay: "hash",
|
||||
ownerDisplaySecret: "secret-key-A",
|
||||
});
|
||||
|
||||
const secretB = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
ownerNumbers: ["+123"],
|
||||
ownerDisplay: "hash",
|
||||
ownerDisplaySecret: "secret-key-B",
|
||||
});
|
||||
|
||||
const lineA = secretA.split("## Authorized Senders")[1]?.split("\n")[1];
|
||||
const lineB = secretB.split("## Authorized Senders")[1]?.split("\n")[1];
|
||||
const tokenA = lineA?.match(/[a-f0-9]{12}/)?.[0];
|
||||
const tokenB = lineB?.match(/[a-f0-9]{12}/)?.[0];
|
||||
|
||||
expect(tokenA).toBeDefined();
|
||||
expect(tokenB).toBeDefined();
|
||||
expect(tokenA).not.toBe(tokenB);
|
||||
});
|
||||
|
||||
it("omits owner section when numbers are missing", () => {
|
||||
const prompt = buildAgentSystemPrompt({
|
||||
workspaceDir: "/tmp/openclaw",
|
||||
|
||||
@@ -5,6 +5,7 @@ import { listDeliverableMessageChannels } from "../utils/message-channel.js";
|
||||
import type { ResolvedTimeFormat } from "./date-time.js";
|
||||
import type { EmbeddedContextFile } from "./pi-embedded-helpers.js";
|
||||
import { sanitizeForPromptLiteral } from "./sanitize-for-prompt.js";
|
||||
import { createHmac, createHash } from "node:crypto";
|
||||
|
||||
/**
|
||||
* Controls which hardcoded sections are included in the system prompt.
|
||||
@@ -13,6 +14,7 @@ import { sanitizeForPromptLiteral } from "./sanitize-for-prompt.js";
|
||||
* - "none": Just basic identity line, no sections
|
||||
*/
|
||||
export type PromptMode = "full" | "minimal" | "none";
|
||||
type OwnerIdDisplay = "raw" | "hash";
|
||||
|
||||
function buildSkillsSection(params: {
|
||||
skillsPrompt?: string;
|
||||
@@ -73,6 +75,30 @@ function buildUserIdentitySection(ownerLine: string | undefined, isMinimal: bool
|
||||
return ["## Authorized Senders", ownerLine, ""];
|
||||
}
|
||||
|
||||
function formatOwnerDisplayId(ownerId: string, ownerDisplaySecret?: string) {
|
||||
const hasSecret = ownerDisplaySecret?.trim();
|
||||
const digest = hasSecret
|
||||
? createHmac("sha256", hasSecret).update(ownerId).digest("hex")
|
||||
: createHash("sha256").update(ownerId).digest("hex");
|
||||
return digest.slice(0, 16);
|
||||
}
|
||||
|
||||
function buildOwnerIdentityLine(
|
||||
ownerNumbers: string[],
|
||||
ownerDisplay: OwnerIdDisplay,
|
||||
ownerDisplaySecret?: string,
|
||||
) {
|
||||
const normalized = ownerNumbers.map((value) => value.trim()).filter(Boolean);
|
||||
if (normalized.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const displayOwnerNumbers =
|
||||
ownerDisplay === "hash"
|
||||
? normalized.map((ownerId) => formatOwnerDisplayId(ownerId, ownerDisplaySecret))
|
||||
: normalized;
|
||||
return `Authorized senders: ${displayOwnerNumbers.join(", ")}. These senders are allowlisted; do not assume they are the owner.`;
|
||||
}
|
||||
|
||||
function buildTimeSection(params: { userTimezone?: string }) {
|
||||
if (!params.userTimezone) {
|
||||
return [];
|
||||
@@ -172,6 +198,8 @@ export function buildAgentSystemPrompt(params: {
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
extraSystemPrompt?: string;
|
||||
ownerNumbers?: string[];
|
||||
ownerDisplay?: OwnerIdDisplay;
|
||||
ownerDisplaySecret?: string;
|
||||
reasoningTagHint?: boolean;
|
||||
toolNames?: string[];
|
||||
toolSummaries?: Record<string, string>;
|
||||
@@ -322,11 +350,12 @@ export function buildAgentSystemPrompt(params: {
|
||||
const execToolName = resolveToolName("exec");
|
||||
const processToolName = resolveToolName("process");
|
||||
const extraSystemPrompt = params.extraSystemPrompt?.trim();
|
||||
const ownerNumbers = (params.ownerNumbers ?? []).map((value) => value.trim()).filter(Boolean);
|
||||
const ownerLine =
|
||||
ownerNumbers.length > 0
|
||||
? `Authorized senders: ${ownerNumbers.join(", ")}. These senders are allowlisted; do not assume they are the owner.`
|
||||
: undefined;
|
||||
const ownerDisplay = params.ownerDisplay === "hash" ? "hash" : "raw";
|
||||
const ownerLine = buildOwnerIdentityLine(
|
||||
params.ownerNumbers ?? [],
|
||||
ownerDisplay,
|
||||
params.ownerDisplaySecret,
|
||||
);
|
||||
const reasoningHint = params.reasoningTagHint
|
||||
? [
|
||||
"ALL internal reasoning MUST be inside <think>...</think>.",
|
||||
|
||||
Reference in New Issue
Block a user