Skip to content

Bound native hook permission fingerprints#71758

Merged
fabianwilliams merged 3 commits into
mainfrom
pash/native-hook-relay-bounded-fingerprint
Apr 25, 2026
Merged

Bound native hook permission fingerprints#71758
fabianwilliams merged 3 commits into
mainfrom
pash/native-hook-relay-bounded-fingerprint

Conversation

@pashpashpash

Copy link
Copy Markdown
Contributor

PR #71707 added the Codex MCP native hook bridge and tightened the relay around native tool and permission events. A late security pass pointed out one remaining sharp edge in the new PermissionRequest dedupe path: even though relay payloads are already JSON-budgeted, the approval key still sorted attacker-controlled tool_input keys when building fallback and content fingerprints.

This follow-up keeps the same approval behavior but removes that sorting from the hot path. The fallback key now collects only a bounded set of own keys before sorting that small subset, and the content fingerprint hashes object keys in traversal order instead of materializing and sorting the full key list. PermissionRequest budget consumption also happens before the approval key is built, so repeated broad payloads defer to the provider approval path before doing the extra fingerprint work.

The regression test now proves broad PermissionRequest inputs can be fingerprinted without calling Object.keys, which is the specific operation the review flagged.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@aisle-research-bot

aisle-research-bot Bot commented Apr 25, 2026

Copy link
Copy Markdown

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Unbounded JSON hashing of permission-request toolInput can cause CPU DoS
1. 🟡 Unbounded JSON hashing of permission-request toolInput can cause CPU DoS
Property Value
Severity Medium
CWE CWE-400
Location src/agents/harness/native-hook-relay.ts:562-584

Description

permissionRequestContentFingerprint() computes a SHA-256 fingerprint of request.toolInput by recursively traversing the entire JSON value (updateJsonHash).

The recent change attempts to avoid sorting a very large keyset by only sorting the first MAX_PERMISSION_FINGERPRINT_SORT_KEYS keys, but when the object is larger (truncated === true) it still iterates and hashes every remaining own property in the object tail:

  • input: request.toolInput is derived from rawPayload.tool_input (readCodexToolInput)
  • operation: permissionRequestContentFingerprint() calls updateJsonHash(hash, request.toolInput)
  • issue: for large objects and/or deep nesting, hashing work scales with full payload size and depth

If an attacker can influence the native hook payload (e.g., via prompt/tool-input injection or untrusted hook source), they can submit an extremely large object (hundreds of thousands of keys / deep nested arrays) and force expensive hashing on the relay process, degrading availability.

Vulnerable code:

if (truncated) {
  const sortedKeySet = new Set(keys);
  hash.update("#object-tail:");
  for (const key in value) {
    if (!Object.prototype.hasOwnProperty.call(value, key) || sortedKeySet.has(key)) {
      continue;
    }
    hash.update(JSON.stringify(key));
    hash.update(":");
    updateJsonHash(hash, value[key]);
    hash.update(",");
  }
}

Recommendation

Add hard caps to fingerprinting work (total keys, total array items, and recursion depth), and when limits are exceeded, include a deterministic truncation marker rather than traversing the remainder.

Example approach:

const MAX_TOTAL_NODES = 10_000;
const MAX_DEPTH = 50;

function updateJsonHashBounded(hash: Hash, value: JsonValue, state = { nodes: 0, depth: 0 }): void {
  if (state.nodes++ > MAX_TOTAL_NODES || state.depth > MAX_DEPTH) {
    hash.update("#truncated");
    return;
  }// ...same logic as updateJsonHash, but:// - only hash first N keys/items// - avoid tail full traversal; append '#object-tail-truncated' marker// - increment/decrement state.depth for recursion
}

Also consider enforcing a maximum size/shape for tool_input at ingestion (readToolInput) so pathological payloads are rejected or truncated before hashing.


Analyzed PR: #71758 at commit 791ab05

Last updated on: 2026-04-25T21:06:08Z

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S maintainer Maintainer-authored PR labels Apr 25, 2026
@greptile-apps

greptile-apps Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR bounds the Object.keys enumeration surface in PermissionRequest fingerprinting by replacing full-key-list sorting with bounded traversal in readBoundedOwnKeys and insertion-order iteration in updateJsonHash, and moves the budget gate before fingerprint computation to skip CPU work on rate-limited requests.

  • Budget move breaks dedup for concurrent duplicates: consumeNativeHookRelayPermissionBudget now runs before the pendingPermissionApprovals lookup, so N concurrent identical requests each consume a budget slot instead of sharing one. With a window of 12, 12 duplicate in-flight requests exhaust the budget, blocking new unique permissions from being prompted in the same minute.
  • Content fingerprint is now insertion-order sensitive: updateJsonHash switched from sorted-key iteration to insertion-order traversal, so two logically identical toolInput objects with different key order produce different approval keys and won't deduplicate.

Confidence Score: 3/5

P1 behavioral regression in budget semantics could exhaust the rate-limit window under concurrent duplicate permission requests; safe fallback exists but dedup guarantee is broken.

One P1 finding (budget consumed before dedup check changes rate-limit semantics and drops pending-approval sharing for over-budget requests) pulls the score to 3. A P2 finding (order-dependent content fingerprint) adds further concern. The fallback to the provider approval path prevents a hard denial, but the correctness regression in the deduplication path is real.

src/agents/harness/native-hook-relay.ts — specifically the budget gate placement relative to the pending-approval map lookup (lines 438–454) and the updateJsonHash key iteration order (lines 559–569).

Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/agents/harness/native-hook-relay.ts
Line: 438-443

Comment:
**Budget consumed before dedup check exhausts window with concurrent identical requests**

Moving `consumeNativeHookRelayPermissionBudget` before the pending-approval lookup changes the budget semantics from "unique approval prompts launched" to "total relay permission calls processed." Under the old code, N concurrent requests for the same approval key each shared the one pending promise without consuming N budget slots. Now every concurrent duplicate consumes a slot, so 12 identical in-flight permission requests exhaust the full `MAX_PERMISSION_APPROVALS_PER_WINDOW` (12) budget — causing the 13th call for a genuinely new permission to return noop/defer in the same minute. Additionally, any request that arrives over-budget can no longer attach to an already-pending promise: it returns noop immediately rather than inheriting the in-flight decision.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/agents/harness/native-hook-relay.ts
Line: 559-569

Comment:
**Content fingerprint is now key-insertion-order dependent**

Replacing `Object.keys(value).toSorted()` with `for (const key in value)` means two objects with identical key/value pairs but different insertion orders (e.g. `{a:1, b:2}` vs `{b:2, a:1}`) will produce different SHA-256 fingerprints. Since the content fingerprint is part of `approvalKey`, those two requests won't deduplicate and the user could see duplicate approval prompts for the same logical operation. In practice JavaScript engines preserve insertion order and JSON deserialization is deterministic, but any code path that constructs `toolInput` differently (e.g. via object spread in a different order) will silently break dedup.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix: bound native hook permission finger..." | Re-trigger Greptile

Comment thread src/agents/harness/native-hook-relay.ts Outdated
Comment thread src/agents/harness/native-hook-relay.ts

@fabianwilliams fabianwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid hardening — dedup of pending PermissionRequest approvals before they consume budget, plus key-order-stable canonical fingerprints, plus a sensitivity test for tail mutations on broad inputs. The Object.keys-spy test catches future enumeration regressions. LGTM.

@fabianwilliams fabianwilliams merged commit bf7d156 into main Apr 25, 2026
63 of 66 checks passed
@fabianwilliams fabianwilliams deleted the pash/native-hook-relay-bounded-fingerprint branch April 25, 2026 21:08
ayesha-aziz123 pushed a commit to ayesha-aziz123/openclaw that referenced this pull request Apr 26, 2026
* fix: bound native hook permission fingerprints

* fix: address native hook fingerprint review

* test: isolate native jiti runtime assertions
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
* fix: bound native hook permission fingerprints

* fix: address native hook fingerprint review

* test: isolate native jiti runtime assertions
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
* fix: bound native hook permission fingerprints

* fix: address native hook fingerprint review

* test: isolate native jiti runtime assertions
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
* fix: bound native hook permission fingerprints

* fix: address native hook fingerprint review

* test: isolate native jiti runtime assertions
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
* fix: bound native hook permission fingerprints

* fix: address native hook fingerprint review

* test: isolate native jiti runtime assertions
jameslcowan pushed a commit to jameslcowan/openclaw that referenced this pull request Jun 2, 2026
* fix: bound native hook permission fingerprints

* fix: address native hook fingerprint review

* test: isolate native jiti runtime assertions
sablehead pushed a commit to sablehead/openclaw that referenced this pull request Jun 10, 2026
* fix: bound native hook permission fingerprints

* fix: address native hook fingerprint review

* test: isolate native jiti runtime assertions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants