Skip to content

Access-control false positives on Firefox-captured traffic: derive visited_by from observed values instead of capture-time labels #99

Description

@Serhatcck

Problem

The authz / RBAC analysis produces false-positive access-control findings on traffic captured via the Firefox extension. Typical shape: a low-privilege (member) credential legitimately reaches a resource through normal UI browsing; the authz tester later replays that resource with both a low- and a high-privilege credential, sees both get 200, and reports "member can access an admin-only resource → broken access control" — when the member was in fact legitimately authorized (which is why both roles get 200).

This does not happen on hackbrowser-captured traffic. In a real export, every access-control finding came from Firefox (no-context) sessions and zero from hackbrowser sessions.

Root cause

The "legitimacy" signal the authz analysis needs — which credentials/roles actually reached this endpoint through the app's own UI — is surfaced only via the ## Access Context block (page_visited_by / element_roles / trigger_element). These fields are populated only at capture time by hackbrowser and are absent for Firefox-extension traffic (documented as such: src/session/request.ts:26-31, rendered by renderAccessContextLines() at src/server/routes/session.ts:124-127, consumed per src/agent/prompt/vuln/authz/prompt.txt:141-152). With that signal missing, the authz tester infers "should be admin-only" from path/data-sensitivity alone and over-claims.

Key insight — the data already exists on both paths

The multi-credential attribution is already recorded in request_observation, keyed per credential, and it is populated on both capture paths (Firefox included, because credential_id is attached on both):

request_observation(session_id, key_hash, credential_id, value_hash, slots, ...)
UNIQUE (session_id, key_hash, credential_id, value_hash)

In the same export, many requests carry observations from 2–4 distinct credentials for one key_hash, yet only ~14% of credentialed requests have page_visited_by populated (the hackbrowser ones). So the signal to say "roles A and B both reached this endpoint" is present in the data — it is simply never turned into the visited_by label the analysis reads.

Fix — derive visited_by from observed values (both capture paths)

Rather than relying on the capture-time single-credential label (hackbrowser-only, and even there it reflects only the first observer), derive visited_by from the distinct set of credentials in request_observation for the request's key_hash, and feed it into the Access Context on both paths.

  1. Add a helper: getCredentialsForKeyHash(sessionID, keyHash) → distinct credential labels from request_observation.
  2. Populate the visited_by line in renderAccessContextLines() from that set (in addition to / instead of the capture-time field), so Firefox traffic gains the signal and hackbrowser traffic gains the complete multi-credential set it currently misses.
  3. The authz tester then sees reached by: <role A>, <role B> and can apply the guard it already has (authz/prompt.txt:152: "both roles return 200 with role-appropriate data → not a vulnerability").

This fixes the root (provide the missing legitimacy signal) rather than withholding findings, works uniformly across both capture paths, and reuses attribution data the pipeline already stores.

Caveat (scoping)

request_observation records who reached an endpoint, not who the app intended to authorize. For manual UI browsing — the false-positive case here — reaching a resource through the app's own navigation IS evidence of legitimate authorization, so this is the correct signal. It is slightly weaker for forced/crawler-issued requests, so keep the signal advisory (it corroborates legitimate access; it should not by itself suppress a genuinely response-proven horizontal-IDOR or unauthenticated-access finding).

Files

src/session/request.ts (observation lookup), src/server/routes/session.ts:124-127 (renderAccessContextLines), src/tool/task.ts:222-233 (subagent dispatch reuse of the same builder), src/agent/prompt/vuln/authz/prompt.txt:141-152 (consumer).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions