






















@@ -122,6 +122,7 @@ const MAX_NATIVE_HOOK_RELAY_HISTORY_ARRAY_ITEMS = 50;
122122const MAX_NATIVE_HOOK_RELAY_HISTORY_OBJECT_KEYS = 50;
123123const MAX_PERMISSION_FALLBACK_KEYS = 200;
124124const MAX_PERMISSION_FALLBACK_KEY_CHARS = 240;
125+const MAX_PERMISSION_FINGERPRINT_SORT_KEYS = 200;
125126const MAX_APPROVAL_TITLE_LENGTH = 80;
126127const MAX_APPROVAL_DESCRIPTION_LENGTH = 700;
127128const MAX_PERMISSION_APPROVALS_PER_WINDOW = 12;
@@ -442,7 +443,7 @@ async function runNativeHookRelayPermissionRequest(params: {
442443const pendingApproval = pendingPermissionApprovals.get(approvalKey);
443444try {
444445const decision = await (pendingApproval ??
445-requestNativeHookRelayPermissionApprovalWithBudget({
446+startNativeHookRelayPermissionApprovalWithBudget({
446447registration: params.registration,
447448 approvalKey,
448449 request,
@@ -463,7 +464,7 @@ async function runNativeHookRelayPermissionRequest(params: {
463464return params.adapter.renderNoopResponse(params.invocation.event);
464465}
465466466-async function requestNativeHookRelayPermissionApprovalWithBudget(params: {
467+async function startNativeHookRelayPermissionApprovalWithBudget(params: {
467468registration: NativeHookRelayRegistration;
468469approvalKey: string;
469470request: NativeHookRelayPermissionApprovalRequest;
@@ -505,18 +506,18 @@ function permissionRequestFallbackKey(request: NativeHookRelayPermissionApproval
505506506507function permissionRequestToolInputKeyFingerprint(toolInput: Record<string, unknown>): string {
507508let fingerprint = "";
508-let processed = 0;
509-for (const key of Object.keys(toolInput).toSorted()) {
510-if (processed >= MAX_PERMISSION_FALLBACK_KEYS) {
511-break;
512-}
509+const { keys, truncated } = readBoundedOwnKeys(toolInput, MAX_PERMISSION_FALLBACK_KEYS);
510+for (const key of keys) {
513511const separator = fingerprint ? "," : "";
514512const remaining = MAX_PERMISSION_FALLBACK_KEY_CHARS - fingerprint.length - separator.length;
515513if (remaining <= 0) {
516514break;
517515}
518516fingerprint += `${separator}${key.slice(0, remaining)}`;
519-processed += 1;
517+}
518+if (truncated && fingerprint.length < MAX_PERMISSION_FALLBACK_KEY_CHARS) {
519+const marker = `${fingerprint ? "," : ""}...`;
520+fingerprint += marker.slice(0, MAX_PERMISSION_FALLBACK_KEY_CHARS - fingerprint.length);
520521}
521522return fingerprint || "none";
522523}
@@ -559,15 +560,51 @@ function updateJsonHash(hash: ReturnType<typeof createHash>, value: JsonValue):
559560return;
560561}
561562hash.update("{");
562-for (const key of Object.keys(value).toSorted()) {
563+const { keys, truncated } = readBoundedOwnKeys(value, MAX_PERMISSION_FINGERPRINT_SORT_KEYS);
564+for (const key of keys) {
563565hash.update(JSON.stringify(key));
564566hash.update(":");
565567updateJsonHash(hash, value[key]);
566568hash.update(",");
567569}
570+if (truncated) {
571+// Keep ordinary objects order-independent without sorting a broad native
572+// hook payload. The tail remains content-sensitive in traversal order.
573+const sortedKeySet = new Set(keys);
574+hash.update("#object-tail:");
575+for (const key in value) {
576+if (!Object.prototype.hasOwnProperty.call(value, key) || sortedKeySet.has(key)) {
577+continue;
578+}
579+hash.update(JSON.stringify(key));
580+hash.update(":");
581+updateJsonHash(hash, value[key]);
582+hash.update(",");
583+}
584+}
568585hash.update("}");
569586}
570587588+function readBoundedOwnKeys(
589+value: Record<string, unknown>,
590+maxKeys: number,
591+): { keys: string[]; truncated: boolean } {
592+const keys: string[] = [];
593+let truncated = false;
594+for (const key in value) {
595+if (!Object.prototype.hasOwnProperty.call(value, key)) {
596+continue;
597+}
598+if (keys.length >= maxKeys) {
599+truncated = true;
600+break;
601+}
602+keys.push(key);
603+}
604+keys.sort();
605+return { keys, truncated };
606+}
607+571608function consumeNativeHookRelayPermissionBudget(relayId: string, now = Date.now()): boolean {
572609const windowStart = now - PERMISSION_APPROVAL_WINDOW_MS;
573610const timestamps = (permissionApprovalWindows.get(relayId) ?? []).filter(
@@ -1032,6 +1069,14 @@ export const __testing = {
10321069): string {
10331070return formatPermissionApprovalDescription(request);
10341071},
1072+permissionRequestContentFingerprintForTests(
1073+request: NativeHookRelayPermissionApprovalRequest,
1074+): string {
1075+return permissionRequestContentFingerprint(request);
1076+},
1077+permissionRequestToolInputKeyFingerprintForTests(toolInput: Record<string, unknown>): string {
1078+return permissionRequestToolInputKeyFingerprint(toolInput);
1079+},
10351080setNativeHookRelayPermissionApprovalRequesterForTests(
10361081requester: NativeHookRelayPermissionApprovalRequester,
10371082): void {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。