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:
Vincent Koc
2026-02-21 03:13:56 -05:00
committed by GitHub
parent fe609c0c77
commit 9abab6a2c9
10 changed files with 107 additions and 5 deletions

View File

@@ -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>.",