Skip to content

UN-3739 [FIX] Prompt Studio owner-access check validates requester, not just profile creator - #2183

Merged
athul-rs merged 6 commits into
mainfrom
fix/un-3739-owner-access
Jul 17, 2026
Merged

UN-3739 [FIX] Prompt Studio owner-access check validates requester, not just profile creator#2183
athul-rs merged 6 commits into
mainfrom
fix/un-3739-owner-access

Conversation

@athul-rs

@athul-rs athul-rs commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What

  • Prompt Studio's validate_profile_manager_owner_access() now receives the requesting user (request_user, the User object) and passes immediately when the requester is an org admin.
  • Adds a service-account bypass for the profile creator (platform-API-created projects).
  • Denial messages now name the profile creator who actually lacks access (flagging offboarded creators explicitly) with remediation steps, instead of the misleading "You do not have access"; also fixes the message being raised as a 1-tuple.
  • Denials log both identities: profile creator and requester.

Why

An org admin indexing a Prompt Studio project could get Permission Error: You do not have access to <adapter>. The check validated only the profile creator's adapter access — the requester was never passed in — so admins were denied on any project whose profile creator lost adapter access (share revoked, creator offboarded — offboarding auto-wipes share rows — or profile created by a service account, which holds no shares by design). The UN-3479 admin bypass (#1993) couldn't help: it was applied to the creator, the only identity the function received. This contradicts the admin model everywhere else (admins see all adapters and are hidden from share dialogs since UN-3318), and the only workaround was sharing adapters org-wide.

How

  • validate_profile_manager_owner_access(profile_manager, request_user=None) — evaluation order: requester-is-admin → creator None → creator service-account → creator admin → creator has all-4 adapter access → else deny.
  • The requester is plumbed as a separate request_user: User parameter (the object views already hold — no re-resolution from a non-unique user_id string) through all four view entry points (build_index_payload, build_fetch_response_payload, build_bulk_fetch_response_payload, build_single_pass_payload) and their internal chains; views pass request.user.
  • On the four builders request_user is keyword-only with no default — dropping the plumb is a TypeError, not a silent revert to pre-fix behavior; a signature test pins this, and each builder has a forwarding test.
  • Deliberately separate from the existing user_id parameter, which is the file-path owner (tool.created_by.user_id) and must not change — a regression test pins that the two identities are never conflated.

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • No. The requester parameter is deliberately two-tier: on the four view-facing builders request_user is required keyword-only with no default — omitting it is a TypeError, so a dropped or refactored-away plumb fails loudly instead of silently reverting UN-3739 (do not re-add = None there; a signature test pins this). On the internal/worker-path helpers (validate_profile_manager_owner_access, _build_summarize_params, index_document, prompt_responder, and the single-prompt/single-pass chain) it stays None-defaulted, so callers without a request context get exactly the pre-fix validation behavior. The revocation guard is kept — a non-admin requester is still denied when the profile creator lacks adapter access — and delegated use (shared-project users piggybacking on the creator's adapter access) is unchanged and covered by tests.
  • Service-account-creator bypass — intended semantic (sign-off): a profile created by a platform-API service account passes the guard for any requester. This is a deliberate authz decision, not a side effect: platform-API profiles hold no adapter shares by design (UN-3479 [FIX] Provide service-account access to resources for org-migration via python client #1987 trusts service accounts on every other permission path), and only org admins can mint API keys, which bounds the reach. The interaction with created_by=SET_NULL on key deletion is pre-existing and now tracked as UN-3750.
  • Note: denial messages are PII-free — the profile creator is referenced generically ("another user") and identified only in server logs; remediation points at an org admin, since after the requester-admin bypass only non-admin collaborators can hit these errors.

Database Migrations

  • None.

Env Config

  • None.

Relevant Docs

  • N/A

Related Issues or PRs

Dependencies Versions

  • No changes.

Notes on Testing

  • New unit suite test_validate_profile_manager_owner_access.py covers the full case matrix: admin requester passes on lapsed-creator profiles, non-admin requester still blocked, service-account creator passes, admin creator passes, created_by=None passes, delegated use passes, plus message contract (names creator + adapter, flags former members, plain-string detail).
  • test_build_index_payload.py::TestOwnerAccessPlumbing pins that the validator receives the request_user object, distinct from the file-path user_id.
  • 59/59 backend tests pass at HEAD (prompt_studio/, adapter_processor_v2/); ruff 0.3.4 check + format clean.
  • QA repro/verify steps (revoke-share, offboarded-creator, and API-created variants) documented in UN-3739.

🤖 Generated with Claude Code

athul-rs and others added 2 commits July 16, 2026 14:01
…access check

Prompt Studio's validate_profile_manager_owner_access() only checked the
profile creator's adapter access — the requesting user was never passed
in. Org admins were denied on any project whose profile creator lost
adapter access (share revoked, member offboarded, or the profile was
created by a service account via the platform API), with an error that
blamed the requester.

- Plumb request_user_id into the check from all call sites (including
  the single-pass and summarize paths, which previously dropped user_id)
- Pass when the requester is an org admin (implicit access)
- Pass when the profile creator is a service account (platform-API /
  org-migration projects hold no adapter shares by design)
- Keep the revocation guard: non-admin requesters are still denied when
  the creator lacks adapter access (delegated use unchanged)
- Error message now names the profile creator who lacks access (and
  flags offboarded creators) instead of misattributing to "You"; also
  fixes the message accidentally being raised as a 1-tuple

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e-path owner

Devil's-advocate review of the previous commit found it defeated itself:
every Prompt Studio run/index view passes user_id=tool.created_by.user_id
(the project creator, who owns the document directory), so forwarding
user_id as the requester re-validated the creator, not the person
clicking Index.

- Add an explicit request_user_id parameter through all four view entry
  points (index, fetch, bulk fetch, single pass) and their internal
  chains; views now pass request.user.user_id
- Keep user_id untouched everywhere — it addresses the project
  creator's file storage path, not an identity to authorize
- Plumbing regression test now pins request_user_id != user_id so the
  two identities can't be conflated again

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1439eb13-1a8b-4803-9f38-9dbb2cc2134e

📥 Commits

Reviewing files that changed from the base of the PR and between 4c8bbe1 and ba313f4.

📒 Files selected for processing (2)
  • backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
  • backend/prompt_studio/prompt_studio_core_v2/tests/test_validate_profile_manager_owner_access.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/prompt_studio/prompt_studio_core_v2/tests/test_validate_profile_manager_owner_access.py
  • backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py

Summary by CodeRabbit

  • New Features

    • Improved access validation for Prompt Studio profiles based on the requesting user and profile owner.
    • Organization administrators, service accounts, and eligible profile owners now receive appropriate access handling.
    • Added clearer permission-denied messages identifying affected adapters and recommended administrator action.
    • Requester identity is consistently applied across document indexing, response fetching, summarization, and single-pass extraction.
  • Bug Fixes

    • Prevented incorrect access denials when shared adapter access or requester privileges apply.
    • Removed personal information from permission error details.

Walkthrough

The profile access guard now evaluates requester-aware bypasses and detailed adapter denials. Request identity is propagated through IDE views, payload builders, prompt execution, response fetching, and summarization, with tests covering forwarding and authorization behavior.

Changes

Profile access validation

Layer / File(s) Summary
Authorization guard and denial contract
backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py, backend/prompt_studio/prompt_studio_core_v2/tests/test_validate_profile_manager_owner_access.py
Owner validation now supports requester and creator bypasses, per-adapter checks, membership-aware errors, structured denial logging, and PII-free permission messages.
Requester propagation through execution
backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
request_user is threaded through summarization, payload builders, indexing, prompt execution, response fetching, and profile validation.
View wiring and forwarding coverage
backend/prompt_studio/prompt_studio_core_v2/views.py, backend/prompt_studio/prompt_studio_core_v2/tests/test_build_index_payload.py, backend/prompt_studio/prompt_studio_core_v2/tests/test_validate_profile_manager_owner_access.py
IDE views pass request.user, and tests verify requester identity plumbing, forwarding, and keyword-only API contracts.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: chandrasekharan-zipstack

Sequence Diagram(s)

sequenceDiagram
  participant Request
  participant views
  participant PromptStudioHelper
  participant OrganizationMemberService
  participant has_group_access
  Request->>views: provide request.user
  views->>PromptStudioHelper: build payload with request_user
  PromptStudioHelper->>OrganizationMemberService: check requester and creator status
  PromptStudioHelper->>has_group_access: evaluate adapter access
  has_group_access-->>PromptStudioHelper: return access results
  PromptStudioHelper-->>Request: continue execution or raise PermissionError
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: requester-aware owner-access validation for Prompt Studio.
Description check ✅ Passed The description covers What, Why, How, breaking changes, migrations, config, docs, related issues, and testing; only screenshots and checklist are omitted.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/un-3739-owner-access

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a permission-check bug in Prompt Studio where org admins were incorrectly denied when indexing or fetching from projects whose profile creator had lost adapter access. validate_profile_manager_owner_access now receives the requesting user as a first-class parameter and short-circuits immediately for admin requesters before evaluating the creator's adapter shares.

  • request_user is threaded as a required keyword-only parameter through all four view-facing builders and _build_summarize_params; omitting it is a compile-time TypeError, preventing silent regression.
  • Adds a service-account-creator bypass and fixes the pre-existing tuple-wrapping bug in error messages.
  • Comprehensive unit tests cover the full case matrix: admin-requester bypass, non-admin guard still active, service-account creator, None creator, delegated use, PII-free message contract, and signature invariants.

Confidence Score: 5/5

Safe to merge — the requester-admin bypass is correctly ordered first, all four view entry points pass the requesting user, and the keyword-only no-default signature guarantees any future dropped plumb is a loud TypeError rather than a silent regression.

The access-check rewrite is logically correct: is_user_organization_admin(None) safely returns False so legacy worker paths see pre-fix behavior; the service-account flag is tested and distinct from the admin path; the owner.pk == request_user.pk identity comparison is safe even for AnonymousUser; the tuple-wrapping bug in the old error message is fixed and pinned by a test. No pre-existing call sites of the four builders exist outside views.py and tests.

No files require special attention — all changed paths are covered by the new test suite.

Important Files Changed

Filename Overview
backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Core logic change: validate_profile_manager_owner_access gains a request_user parameter with correct evaluation order. request_user threaded through all four builders as required keyword-only. Error messages fixed from tuple to plain string.
backend/prompt_studio/prompt_studio_core_v2/views.py All four view entry points now pass request_user=request.user to their respective builder. Clean and complete.
backend/prompt_studio/prompt_studio_core_v2/tests/test_validate_profile_manager_owner_access.py New test suite with full case matrix covering all bypass paths, access disjuncts, PII-free message contract, and builder-signature invariants.
backend/prompt_studio/prompt_studio_core_v2/tests/test_build_index_payload.py Adds TestOwnerAccessPlumbing to verify the sentinel request_user reaches validate_profile_manager_owner_access, pinning that user_id and request_user are never conflated.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[validate_profile_manager_owner_access] --> B{request_user is org admin?}
    B -- Yes --> PASS1[Return bypass]
    B -- No --> C{owner is None?}
    C -- Yes --> PASS2[Return bypass]
    C -- No --> D{owner is service account?}
    D -- Yes --> PASS3[Return bypass]
    D -- No --> E{owner is org admin?}
    E -- Yes --> PASS4[Return bypass]
    E -- No --> F[Check all 4 adapters]
    F --> G{All accessible by owner?}
    G -- Yes --> PASS5[Return ok]
    G -- No --> H{request_user IS owner?}
    H -- Yes --> ERR1[PermissionError: you lost access]
    H -- No --> I{owner still a member?}
    I -- No --> ERR2[PermissionError: former member]
    I -- Yes --> ERR3[PermissionError: creator lacks access]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A[validate_profile_manager_owner_access] --> B{request_user is org admin?}
    B -- Yes --> PASS1[Return bypass]
    B -- No --> C{owner is None?}
    C -- Yes --> PASS2[Return bypass]
    C -- No --> D{owner is service account?}
    D -- Yes --> PASS3[Return bypass]
    D -- No --> E{owner is org admin?}
    E -- Yes --> PASS4[Return bypass]
    E -- No --> F[Check all 4 adapters]
    F --> G{All accessible by owner?}
    G -- Yes --> PASS5[Return ok]
    G -- No --> H{request_user IS owner?}
    H -- Yes --> ERR1[PermissionError: you lost access]
    H -- No --> I{owner still a member?}
    I -- No --> ERR2[PermissionError: former member]
    I -- Yes --> ERR3[PermissionError: creator lacks access]
Loading

Reviews (5): Last reviewed commit: "UN-3739 [FIX] Address review round 3: cr..." | Re-trigger Greptile

Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py`:
- Around line 264-290: Remove owner.email from the user-facing profile_ref used
by the PermissionError and replace it with a non-PII identifier, preferably the
available display name or a generic project-creator label. Preserve the existing
email-bearing server-side log in the surrounding permission-check flow, and keep
all denial branches’ guidance unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f4249cce-0f6b-44ef-a8b5-a86d144e03c7

📥 Commits

Reviewing files that changed from the base of the PR and between 94d78da and fcf48ba.

📒 Files selected for processing (4)
  • backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
  • backend/prompt_studio/prompt_studio_core_v2/tests/test_build_index_payload.py
  • backend/prompt_studio/prompt_studio_core_v2/tests/test_validate_profile_manager_owner_access.py
  • backend/prompt_studio/prompt_studio_core_v2/views.py

Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
…uest_user_id

- Denial messages no longer include the profile creator's email — after
  the requester-admin bypass, the only audience for these errors is
  non-admin collaborators, so remediation now points at an org admin and
  the creator's identity stays in server logs only (CodeRabbit)
- Add missing str | None annotations on the two private single-pass
  helpers (Greptile)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py (1)

268-269: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unnecessary f prefix from the string literal.

The second part of the string literal does not contain any formatting expressions, so the f prefix can be safely removed.

♻️ Proposed fix
         profile_ref = (
             f"This project's LLM profile '{profile_manager.profile_name}' was"
-            f" created by another user"
+            " created by another user"
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py` around
lines 268 - 269, Remove the unnecessary f-string prefix from the second literal
in the profile ownership message while preserving the existing concatenated
message and formatting in the first literal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py`:
- Around line 268-269: Remove the unnecessary f-string prefix from the second
literal in the profile ownership message while preserving the existing
concatenated message and formatting in the first literal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5da1b8d3-72ba-412d-b3d5-98d13941fb50

📥 Commits

Reviewing files that changed from the base of the PR and between fcf48ba and 9d33951.

📒 Files selected for processing (2)
  • backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
  • backend/prompt_studio/prompt_studio_core_v2/tests/test_validate_profile_manager_owner_access.py

@ritwik-g ritwik-g 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.

Reviewed against fcf48ba5. I ran an independent RCA of the underlying bug first (call chain, permission model, created_by provenance, git history of the check), then an adversarial verification pass over this diff.

Verdict: the fix is correct and lands at the right layer. Both real gaps are closed — the admin requester bypass (prompt_studio_helper.py:227) and the service-account creator bypass (:234). The SA bypass is load-bearing rather than belt-and-braces: is_user_organization_admin returns False for service accounts (organization_member_service.py:29-30), so the creator-admin bypass could never have fired for this customer's profile.

Particular credit for carrying the requester as a separate request_user_id instead of reusing user_id. user_id is tool.created_by.user_id — the file-path owner — at all four entry points (views.py:451/530/683/789), so the ticket's own "Proposed fix" wording ("call sites already have user_id") would have re-validated the creator and no-oped on the exact reported scenario. test_build_index_payload.py:212 pinning request_user_id != user_id is the right guard against that regressing.

The tests are genuinely behavioral, not theatre — I checked by installing main's pre-fix body behind the new signature: 5 of 10 fail, including both bypass tests and all three message tests. The 1-tuple error_msg defect is real (DRF renders .detail as a JSON array; confirmed by executing against the pinned DRF 3.17.1), and isinstance(exc.detail, str) is the only assertion that catches it — str(exc) on a list-detail still contains the email substring. Good catch.

Comments below: one P2 worth closing pre-merge (a test gap that makes future breakage silent), one sign-off ask, and three follow-ups. Nothing blocking beyond the P2.

Nit: "49/49 backend tests pass" in the description doesn't match a reproducible scope (I get 15 for the subtree, 137 for the backend unit tier).

Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
@athul-rs
athul-rs requested a review from ritwik-g July 17, 2026 03:20
…ng contract

- Pass the requester as a User object (request_user) instead of a
  user_id string: deletes the re-resolution helper (3 queries → 1),
  stops keying an authz decision on the non-unique User.user_id
  CharField, and uses is_user_organization_admin directly so the
  service-account exclusion stays in one place
- Make request_user keyword-only with NO default on the four
  view-facing builders — a dropped plumb is now a loud TypeError
  instead of a silent revert to pre-fix behavior; signature test pins it
- Add forwarding tests for the three previously-untested builders
  (parametrized, sentinel-abort pattern)
- Pin the three non-ownership access disjuncts (org-share, user-share,
  group-share) that a sabotage-check showed were untested
- Document the created_by-None bypass honestly in the docstring;
  the platform-API label-set gap it interacts with is filed as UN-3750

Sonar: dedup forwarding tests (new-code duplication gate) and keep a
single throwing invocation inside pytest.raises blocks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
backend/prompt_studio/prompt_studio_core_v2/tests/test_build_index_payload.py (1)

207-215: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Also pin requester forwarding into _build_summarize_params.

This test only verifies the default-profile validator call. Dropping request_user from the summary-profile path would still pass, causing admin requesters to be denied when a distinct summarization profile is used.

Proposed assertion
 def test_owner_access_receives_requesting_user(self) -> None:
     validate_owner_mock = MagicMock(return_value=None)
+    summarize_mock = MagicMock(return_value=(None, "", MagicMock()))
     _dispatch_build(
         check_return=True,
         read_return="extracted text",
         validate_owner_mock=validate_owner_mock,
+        build_summarize_mock=summarize_mock,
     )
     _args, kwargs = validate_owner_mock.call_args
     assert kwargs.get("request_user") is _REQUEST_USER
+    assert summarize_mock.call_args.kwargs["request_user"] is _REQUEST_USER
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/prompt_studio/prompt_studio_core_v2/tests/test_build_index_payload.py`
around lines 207 - 215, The test test_owner_access_receives_requesting_user
currently verifies only the default-profile validator; extend coverage to the
_build_summarize_params summary-profile path and assert that the requesting user
is forwarded there as well. Ensure the test exercises a distinct summarization
profile and checks the validator’s request_user argument remains _REQUEST_USER.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@backend/prompt_studio/prompt_studio_core_v2/tests/test_build_index_payload.py`:
- Around line 207-215: The test test_owner_access_receives_requesting_user
currently verifies only the default-profile validator; extend coverage to the
_build_summarize_params summary-profile path and assert that the requesting user
is forwarded there as well. Ensure the test exercises a distinct summarization
profile and checks the validator’s request_user argument remains _REQUEST_USER.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e837b26d-6ce9-4e60-af8d-e188043730f1

📥 Commits

Reviewing files that changed from the base of the PR and between 9d33951 and 153e4dc.

📒 Files selected for processing (4)
  • backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
  • backend/prompt_studio/prompt_studio_core_v2/tests/test_build_index_payload.py
  • backend/prompt_studio/prompt_studio_core_v2/tests/test_validate_profile_manager_owner_access.py
  • backend/prompt_studio/prompt_studio_core_v2/views.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/prompt_studio/prompt_studio_core_v2/views.py

@athul-rs
athul-rs requested a review from jaseemjaskp July 17, 2026 08:22
UN-2202 (#1797) made adapter created_by audit-only and replaced
shared_users with owner/viewer ResourceMembership roles. The guard now
resolves the profile creator's adapter access via main's
_adapter_accessible_by bridge; the branch-local _user_has_adapter_access
helper (which read the dropped shared_users M2M) is deleted, and the
disjunct tests are rewired to owner/viewer/org/group.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@ritwik-g ritwik-g 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.

Approving — the fix is correct, and I re-verified it still holds after the UN-2202 merge.

Verified at 4c8bbe13 by execution rather than reading. All four builders are genuinely keyword-only with no default (inspect.signatureKEYWORD_ONLY, default=empty), and every bypass is load-bearing under sabotage: deleting the admin-requester branch, the service-account branch, or the None-creator branch each fails exactly one targeted test. The rewired disjunct tests reproduce the original signature under the new model — reducing _adapter_accessible_by to owner-only fails exactly 3, and exactly the right 3. _adapter_accessible_by is byte-identical to main's (adopted, not reinvented), and no coverage was lost in the rewire: 15 test defs on both sides, the only delta a rename and a forced mock rewire. Backend unit tier green.

Worth calling out two judgement calls that went the right way: declining the Greptile suggestion (it would have dropped the service-account exclusion from is_user_organization_admin and duplicated admin-role resolution outside its one home), and catching during implementation that user_id is the file-path owner rather than plumbing it through blind — that would have no-oped on the exact reported bug.

One thing to fix before merge — the description, not the code

The "Can this PR break any existing features" section still says:

"All new parameters are trailing and optional (None default): callers that don't pass a requester (worker/legacy paths) get exactly the pre-fix validation behavior."

That was true at fcf48ba5 and was falsified by 153e4dcc — the very commit that hardened the builders. It wants rewriting rather than deleting, because the two-tier truth is the useful part:

  • the four view-facing builders take request_user as required keyword-only — a dropped plumb is a TypeError;
  • internal/worker paths remain None-defaulted and get pre-fix behavior.

As written it's the sentence a future contributor reads before re-adding = None and silently reverting UN-3739 — and the service-account sign-off currently sits underneath it, which undercuts the sign-off too.

Two smaller ones while you're in there:

  • request_user_id in What / How / Notes on Testing — it's a User object now. That's the exact confusion your own commit removed from the code; leaving it in the body invites its return.
  • "49/49 backend tests pass (prompt_studio/, adapter_processor_v2/)" — that scope collects 59 at HEAD. Also worth naming the three classes that are the review response (TestCreatorAccessDisjuncts, TestBuilderSignatures, TestBuilderForwarding), since they're the interesting part of the test plan.

Best follow-up if you want one (not a gate)

The plumb type is unpinned. Changing any view to request_user=request.user.user_id passes the entire suite green — but a str is truthy and lacks .is_authenticated, so is_user_organization_admin short-circuits to False, the admin bypass silently dies, and UN-3739 is back with no failing test and no log line. It's the same shape as the hole you just closed, one layer up, and it's invited by user_id=tool.created_by.user_id sitting on the adjacent line. One thin view-level test closes it.

Related, same family: request_user is still None-defaulted on 8 functions including the guard itself — only the 4 builders are hardened. Latent today (every forward is correct), but it's the remediation surface if you ever want to make the contract uniform.

@athul-rs

Copy link
Copy Markdown
Contributor Author

Thanks for the approval and the triage framing — verified all three before acting:

Dead code (mine): confirmed, filed as UN-3756. Re-ran the caller sweep on both repos: cloud's prompt_responder hits are Simple Prompt Studio's own SPSProjectHelper.prompt_responder (no delegation), cloud imports only get_select_fields/get_tool_from_tool_id/fetch_prompt_from_tool from this helper, and the sdk/worker index_document hits are vector_db.index_document. So your 4-of-9 unreachable-call-sites count stands. Kept the deletion out of this PR since it's approved — UN-3756 scopes the 7 functions plus their now-pointless request_user plumbing.

UN-1258 (mine): confirmed Jira-only. It exists — "BUG_REG (0.60.1): shared LLM selected in profile manager → permission error", May 2024, Closed — but there's no corresponding commit in this repo's history, so it's cited in UN-3739 as same-failure-family context, not code precedent. I've annotated the ticket's Related section accordingly.

Agentic-table (Hari's call): one data point to help the triage — it's not just the view branch skipping build_fetch_response_payload; the cloud plugin's build_agentic_table_payload doesn't run validate_adapter_status or the owner-access check on its side either, so the path currently has zero adapter validation end-to-end. Leaving the ticket/ownership decision with Hari as you framed it.

🤖 Generated with Claude Code

@jaseemjaskp jaseemjaskp 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.

Review summary — approving with nits

Reviewed with six agents (code review, silent-failure, type design, test coverage, comments, simplification). No blocking findings; the authz core is sound and I could not break it:

  • The admin/service-account bypasses grant nothing new. Both match the platform's existing adapter model — AdapterInstanceModelManager.for_user already returns the unfiltered queryset for org admins and grants service accounts every non-frictionless adapter. Not a privilege escalation.
  • Every failure mode fails closed. is_user_organization_admin swallowing exceptions → False only ever removes a bypass. Under a real DB outage _adapter_accessible_by issues uncaught queries, so you get a loud 500, not a bogus 403. MultipleObjectsReturned is ruled out by the (organization, user) unique constraint.
  • No breaking change. All four builders are called only from views.py, and all four pass request_user.
  • Real bug fixed in passing: the old error_msg = (f"...",) trailing comma made the DRF detail a tuple; now a plain string, and pinned.
  • The tests are strong. Mutation testing killed 11/15 mutants, including every bypass removal. _adapter() setting shared_to_org = False and _user() setting is_service_account explicitly are load-bearing — a bare MagicMock is auto-truthy and would have made every denial test pass vacuously. Correctly handled.

The findings worth your time, in order:

  1. profile_ref tells users their own profile "was created by another user" (helper.py:273) — reachable via the most common revocation shape, and a regression against this PR's own goal.
  2. _build_summarize_params is the one live hop that silently reverts (helper.py:339) — and mutation testing shows nothing catches it.
  3. The summarize-profile validator call is entirely uncovered (test_build_index_payload.py) — same bug class as UN-3739, for summarize-enabled projects.
  4. The new suite's Case N citations are systematically wrong (test_validate_...py:117) — two are a straight swap, three cite cases that don't exist.

The rest are genuine nits. Deliberately not raising: the getattr(owner, "is_service_account", False) idiom (matches the house convention at 8+ sites incl. organization_member_service.py:29; getattr(request_user, "user_id", None) is outright correct since request_user is legitimately None), and any value-object/NewType wrapper for the two identities — str vs User plus keyword-only already covers the residual conflation risk. The explicit-parameter plumb is the right call over a StateStore thread-local; the wider diff buys you an identity flow that can't be silently lost again.

Follow-up ticket material, not this PR: PromptStudioHelper.index_document / prompt_responder and their chain have zero callers — the request_user additions there are inert. Deleting the chain would also let you drop the = None defaults. Renaming user_idfile_owner_user_id on the four builders would make the invariant self-evident rather than test-enforced.

🤖 Generated with Claude Code

Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
Comment thread backend/prompt_studio/prompt_studio_core_v2/prompt_studio_helper.py Outdated
…arize hop contract

- Denial message no longer claims "created by another user" when the
  requester IS the creator (share revoked on their own profile) — they
  are addressed directly, still PII-free
- _build_summarize_params: request_user is now required keyword-only —
  it guards a live second validator call, so a dropped plumb must
  TypeError, not silently revert (added to the signature test; direct
  forwarding test added for the hop)
- Denial logging collapsed to one record carrying profile, creator,
  denied adapter ids, and requester; ERROR_MSG constant deleted
- Adapter names deduped in messages (adapters of different types can
  share a name); single-vs-multi message branches pinned by tests
- Docstring no longer claims nonexistent "worker paths"; test case
  citations renumbered to the module docstring's contract; stale
  access-model comment reduced to a pointer; redundant None guard
  dropped (callee handles None)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

Unstract test results

Per-group results

Status Group Tier Passed Failed Errors Skipped Duration (s)
e2e-coowners e2e 1 0 0 0 1.3
e2e-login e2e 2 0 0 0 1.4
e2e-smoke e2e 2 0 0 0 1.0
integration-backend integration 118 0 0 27 66.8
integration-connectors integration 1 0 0 7 7.8
unit-backend unit 151 0 0 0 16.1
unit-connectors unit 63 0 0 0 9.5
unit-core unit 27 0 0 0 0.9
unit-platform-service unit 15 0 0 0 2.2
unit-rig unit 71 0 0 0 3.4
unit-sdk1 unit 435 0 0 0 24.0
unit-workers unit 723 0 0 0 33.8
TOTAL 1609 0 0 34 168.2

Critical paths

⚠️ Critical paths not yet covered

  • workflow-create-execute — Create a workflow, configure source+destination, execute, poll, fetch result. (declared coverage: e2e-workflow)
  • api-deployment-run — Deploy a workflow as an API, POST a document, receive structured JSON. (declared coverage: e2e-api-deployment)
  • prompt-studio-fetch-response — Prompt Studio: create project, add prompt, run single-pass, get response. (declared coverage: e2e-prompt-studio)
  • pipeline-etl-execute — Run an ETL pipeline from source connector to destination. (declared coverage: no groups declared)
  • usage-token-tracking — Per-execution token usage is recorded and retrievable. (declared coverage: no groups declared)
  • workflow-execution-fan-out — Multi-file workflow execution fans out to file-processing workers and rejoins. (declared coverage: no groups declared)
  • callback-result-delivery — Async results are posted back via the callback worker. (declared coverage: no groups declared)
✅ Covered critical paths
  • auth-login — covered by e2e-login
  • adapter-register-llm — covered by integration-backend
  • workflow-author — covered by integration-backend
  • co-owner-manage — covered by integration-backend, e2e-coowners
  • api-deployment-provision — covered by integration-backend
  • api-deployment-auth — covered by integration-backend
  • prompt-studio-author — covered by integration-backend
  • connector-register-test — covered by integration-backend
  • usage-aggregate-read — covered by integration-backend

@athul-rs
athul-rs merged commit 70eebac into main Jul 17, 2026
11 checks passed
@athul-rs
athul-rs deleted the fix/un-3739-owner-access branch July 17, 2026 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants