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.
- Add a helper:
getCredentialsForKeyHash(sessionID, keyHash) → distinct credential labels from request_observation.
- 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.
- 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).
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 Contextblock (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 byrenderAccessContextLines()atsrc/server/routes/session.ts:124-127, consumed persrc/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, becausecredential_idis attached on both):In the same export, many requests carry observations from 2–4 distinct credentials for one
key_hash, yet only ~14% of credentialed requests havepage_visited_bypopulated (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 thevisited_bylabel the analysis reads.Fix — derive
visited_byfrom 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_byfrom the distinct set of credentials inrequest_observationfor the request'skey_hash, and feed it into the Access Context on both paths.getCredentialsForKeyHash(sessionID, keyHash)→ distinct credential labels fromrequest_observation.visited_byline inrenderAccessContextLines()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.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_observationrecords 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).