Skip to content

Use daydream API to check if plugin is allowed#646

Merged
mjh1 merged 2 commits intomainfrom
mh/plugin-check
Mar 11, 2026
Merged

Use daydream API to check if plugin is allowed#646
mjh1 merged 2 commits intomainfrom
mh/plugin-check

Conversation

@mjh1
Copy link
Copy Markdown
Contributor

@mjh1 mjh1 commented Mar 10, 2026

Summary by CodeRabbit

  • Improvements
    • Plugin access now uses remote API checks so allowlists stay up-to-date; plugins not permitted will be blocked.
    • Validation now handles API pagination and network errors more robustly, reducing false allow/block outcomes.
    • Service base URL handling improved with a sensible default to ensure validation continues when configuration is missing.

We can approve/revoke plugins in the daydream admin pages now and this will be automatically reflected in the front end as well as this back end check.
Screenshot of admin page:
Screenshot 2026-03-10 at 13 17 09

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 10, 2026

📝 Walkthrough

Walkthrough

Replaced local environment whitelist checks with an API-driven plugin allowlist: added get_daydream_api_base(), URL normalization, and asynchronous is_plugin_allowed() that queries the Daydream API with pagination and robust error handling; user validation now uses the new base URL helper.

Changes

Cohort / File(s) Summary
Plugin Validation Migration
src/scope/cloud/fal_app.py
Added get_daydream_api_base() and normalize_plugin_url(). Replaced inline env-based plugin whitelist with async is_plugin_allowed(package) that fetches allowed plugins from the Daydream API (pagination, errors, logging). Updated calls to await the new check and return 403 when disallowed.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Normalize as normalize_plugin_url()
    participant Validator as is_plugin_allowed()
    participant API as Daydream API
    participant Handler as Response Handler

    App->>Normalize: plugin_url
    Normalize-->>App: normalized_url
    App->>Validator: await is_plugin_allowed(normalized_url)
    Validator->>API: GET /allowed-plugins?page=1
    loop while more pages
        API-->>Validator: plugins batch (paginated)
        Validator->>API: GET next page
    end
    Validator-->>App: True / False or raise error
    alt True
        App->>Handler: proceed with plugin
    else False
        App->>Handler: return 403 Forbidden
    else Error
        App->>Handler: log error and handle gracefully
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hops a byte and twitches nose so bright,

Calls to Daydream chase the old whitelist night,
URLs tidy, pages danced through the seam,
Plugins cleared or halted by API stream,
I nibble logs and celebrate the scheme.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: replacing local whitelist-based plugin checks with dynamic Daydream API validation, which is the core objective of the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mh/plugin-check

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/scope/cloud/fal_app.py (1)

845-867: Advance pagination from the response, not the request.

Line 867 always adds the requested limit. If the /v1/plugins endpoint returns fewer items than requested (common on the final page or due to internal API limits), this skips entries and can incorrectly reject an allowed plugin. Advance offset by the actual number of items returned instead.

🔄 Proposed fix
                             while True:
                                 resp = await client.get(
                                     base_url,
                                     params={
                                         "remoteOnly": "true",
                                         "limit": limit,
                                         "offset": offset,
                                     },
                                     timeout=10.0,
                                 )
                                 resp.raise_for_status()
                                 data = resp.json()
-                                for plugin in data.get("plugins", []):
+                                plugins = data.get("plugins", [])
+                                for plugin in plugins:
                                     plugin_url = plugin.get("repositoryUrl", "")
                                     if (
                                         plugin_url
                                         and normalized_package
                                         == normalize_plugin_url(plugin_url)
                                     ):
                                         return True
                                 if not data.get("hasMore", False):
                                     break
-                                offset += limit
+                                offset += len(plugins)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scope/cloud/fal_app.py` around lines 845 - 867, The pagination increments
offset by the requested limit which can skip items when the server returns fewer
results; in the loop that calls client.get for "/v1/plugins" (using variables
client, base_url, params limit/offset and checking plugins from data), change
the offset advancement to use the actual number of items returned (e.g.,
len(data.get("plugins", [])) or a returned count field) instead of always adding
limit, so references to normalized_package and normalize_plugin_url are checked
against every returned plugin without skipping any.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/scope/cloud/fal_app.py`:
- Around line 31-32: The get_daydream_api_base function returns the raw
DAYDREAM_API_BASE env var which can include a trailing slash; update
get_daydream_api_base to read os.getenv("DAYDREAM_API_BASE",
"https://api.daydream.live") and strip any trailing slashes (e.g., using
rstrip('/')) before returning so callers (validation and plugin install paths)
never get a base URL ending with '/'.
- Around line 843-872: The code currently swallows all network/HTTP/JSON errors
and returns False from the allowlist lookup loop, making outages
indistinguishable from a denied plugin; change the exception path in the async
HTTP loop to return None instead of False (i.e., in the except Exception as e
block where you log with log_prefix() and base_url), and then update the caller
logic that inspects the lookup result (the check that compares
normalized_package via normalize_plugin_url and the subsequent boolean/403
decision) to detect None and respond with a 503/Service Unavailable and an
explanatory message rather than treating it as a plugin-not-found deny; ensure
you preserve existing True/False semantics for found/not-found but use None to
signal Daydream/API lookup failure.

---

Nitpick comments:
In `@src/scope/cloud/fal_app.py`:
- Around line 845-867: The pagination increments offset by the requested limit
which can skip items when the server returns fewer results; in the loop that
calls client.get for "/v1/plugins" (using variables client, base_url, params
limit/offset and checking plugins from data), change the offset advancement to
use the actual number of items returned (e.g., len(data.get("plugins", [])) or a
returned count field) instead of always adding limit, so references to
normalized_package and normalize_plugin_url are checked against every returned
plugin without skipping any.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b59022b4-de83-4094-9580-afd0ae47e82c

📥 Commits

Reviewing files that changed from the base of the PR and between 1ac85de and 08e4e23.

📒 Files selected for processing (1)
  • src/scope/cloud/fal_app.py

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 10, 2026

🚀 fal.ai Preview Deployment

App ID daydream/scope-pr-646--preview
WebSocket wss://fal.run/daydream/scope-pr-646--preview/ws
Commit 774927a

Testing

Connect to this preview deployment by running this on your branch:

uv run build && SCOPE_CLOUD_APP_ID="daydream/scope-pr-646--preview/ws" uv run daydream-scope

🧪 E2E tests will run automatically against this deployment.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 10, 2026

✅ E2E Tests passed

Status passed
fal App daydream/scope-pr-646--preview
Run View logs

Test Artifacts

Check the workflow run for screenshots.

@mjh1 mjh1 requested review from emranemran and thomshutt March 10, 2026 13:18
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/scope/cloud/fal_app.py (1)

822-824: ⚠️ Potential issue | 🟡 Minor

Short-circuit invalid package values before the allowlist lookup.

Lines 822-824 currently allow requested_package to be empty or non-string. Empty values force a full Daydream pagination walk before 403, and non-strings raise in normalize_plugin_url() and come back as a spurious 503. Return a client error — or let the Scope API validate first — before calling is_plugin_allowed().

🩹 Proposed fix
             if method == "POST" and normalized_path == "/api/v1/plugins":
-                requested_package = (
-                    body.get("package", "") if isinstance(body, dict) else ""
-                )
+                requested_package = body.get("package") if isinstance(body, dict) else None
+                if not isinstance(requested_package, str) or not requested_package.strip():
+                    return {
+                        "type": "api_response",
+                        "request_id": request_id,
+                        "status": 400,
+                        "error": "Missing or invalid plugin package",
+                    }
+                requested_package = requested_package.strip()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scope/cloud/fal_app.py` around lines 822 - 824, The code currently sets
requested_package from body without validating type or emptiness, which lets
empty or non-string values proceed into normalize_plugin_url() and
is_plugin_allowed(); fix by short-circuiting: before calling
is_plugin_allowed(), check that requested_package is an instance of str and not
empty (or otherwise return a 400 client error / forward to Scope API
validation), and reject non-string or empty values early to avoid triggering
full pagination or normalize_plugin_url() errors; update the logic around the
requested_package assignment and the subsequent call to is_plugin_allowed() to
perform this validation first.
♻️ Duplicate comments (1)
src/scope/cloud/fal_app.py (1)

31-32: ⚠️ Potential issue | 🟡 Minor

Strip trailing slashes in get_daydream_api_base().

Line 32 still returns the raw env var, so DAYDREAM_API_BASE=https://api.daydream.live/ yields //v1/users/... and //v1/plugins in the two current call sites.

🩹 Proposed fix
 def get_daydream_api_base() -> str:
-    return os.getenv("DAYDREAM_API_BASE", "https://api.daydream.live")
+    return os.getenv("DAYDREAM_API_BASE", "https://api.daydream.live").rstrip("/")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scope/cloud/fal_app.py` around lines 31 - 32, get_daydream_api_base
currently returns the raw DAYDREAM_API_BASE which can include a trailing slash
causing double-slash URLs; update get_daydream_api_base to strip any trailing
slashes from the env value (e.g., use rstrip('/') on os.getenv(...) result or on
the resulting string) and return that normalized base (ensure the default
"https://api.daydream.live" is treated the same) so callers building paths don't
produce double slashes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/scope/cloud/fal_app.py`:
- Around line 822-824: The code currently sets requested_package from body
without validating type or emptiness, which lets empty or non-string values
proceed into normalize_plugin_url() and is_plugin_allowed(); fix by
short-circuiting: before calling is_plugin_allowed(), check that
requested_package is an instance of str and not empty (or otherwise return a 400
client error / forward to Scope API validation), and reject non-string or empty
values early to avoid triggering full pagination or normalize_plugin_url()
errors; update the logic around the requested_package assignment and the
subsequent call to is_plugin_allowed() to perform this validation first.

---

Duplicate comments:
In `@src/scope/cloud/fal_app.py`:
- Around line 31-32: get_daydream_api_base currently returns the raw
DAYDREAM_API_BASE which can include a trailing slash causing double-slash URLs;
update get_daydream_api_base to strip any trailing slashes from the env value
(e.g., use rstrip('/') on os.getenv(...) result or on the resulting string) and
return that normalized base (ensure the default "https://api.daydream.live" is
treated the same) so callers building paths don't produce double slashes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73524538-a504-4946-aed4-82027247bf7e

📥 Commits

Reviewing files that changed from the base of the PR and between 08e4e23 and 774927a.

📒 Files selected for processing (1)
  • src/scope/cloud/fal_app.py

@mjh1 mjh1 merged commit 6fbf39e into main Mar 11, 2026
10 checks passed
@mjh1 mjh1 deleted the mh/plugin-check branch March 11, 2026 14:36
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.

2 participants