Skip to content

Conversation

@akshay-joshi
Copy link
Contributor

@akshay-joshi akshay-joshi commented Nov 25, 2025

Summary by CodeRabbit

  • New Features

    • Introduced a configurable Plain SQL restore option gated by a new server-mode setting (disabled by default).
  • Documentation

    • Restore dialog docs updated with instructions to enable Plain restore in server mode.
  • Security / Behavior

    • Added file-safety checks and confirmation/blocking flows for plain SQL restores; Plain option is only offered when enabled and targeting databases.
  • Bug Fixes

    • Standardized error handling and confirmation flows in utility dialogs.
  • Behavior Changes

    • Post-save callbacks for backup/maintenance/restore removed, changing post-save alerts and automatic background job starts.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

Adds ENABLE_PLAIN_SQL_RESTORE flag (default False), exposes it to the frontend, gates the Plain restore UI option, implements server-side SQL safety checks with an optional confirmation flow, removes legacy saveCallBack handlers from several JS tools, and updates docs and editorconfig.

Changes

Cohort / File(s) Summary
Configuration & Flag
web/config.py, web/pgadmin/evaluate_config.py
Add ENABLE_PLAIN_SQL_RESTORE = False (removed ON_DEMAND_LOG_COUNT); enable flag automatically in non-server (desktop) mode during config evaluation.
Backend → Frontend Propagation
web/pgadmin/browser/__init__.py, web/pgadmin/browser/templates/browser/js/utils.js
Pass enable_plain_sql_restore to templates and initialize pgAdmin['enable_plain_sql_restore'] on the frontend.
SQL Safety & Restore Control Flow
web/pgadmin/tools/restore/__init__.py
Add is_safe_sql_file(path) to validate UTF‑8, reject null bytes, normalize lines, and scan for meta/dangerous backslash patterns; update use_sql_utility() to return an extra confirmation_msg and to block/plain-gate restores in server mode; create_restore_job() handles confirmation flow.
Frontend Confirmation & Error Handling
web/pgadmin/static/js/UtilityView.jsx
On API responses with confirmation_msg, show confirmation dialog and resubmit with confirmed:true if accepted; standardize catch blocks to wrap non-Error values in new Error(...).
Restore UI Option Gate
web/pgadmin/tools/restore/static/js/restore.ui.js
Import pgAdmin and require pgAdmin['enable_plain_sql_restore'] (and nodeType === 'database') for the Plain restore option to appear.
Removed Post-save Callbacks
web/pgadmin/tools/backup/static/js/backup.js, web/pgadmin/tools/maintenance/static/js/maintenance.js, web/pgadmin/tools/restore/static/js/restore.js
Removed saveCallBack(data) implementations that previously showed error alerts and/or started background jobs; post-save handling in these modules changed/removed.
Docs & EditorConfig
docs/en_US/restore_dialog.rst, web/.editorconfig
Document Plain restore disabled by default in server mode and how to enable it; add [*.jsx] indent_size = 2 entry.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI as Restore UI
    participant BE as Backend
    participant Validator as SQL Validator
    participant Notif as Confirmation Dialog

    User->>UI: Select Plain restore & Save
    UI->>BE: POST /restore (confirmed=false)
    BE->>Validator: is_safe_sql_file(filepath)
    alt Server mode & feature disabled
        BE-->>UI: block_msg (error)
        UI-->>User: Show error
    else Unsafe patterns found
        BE-->>UI: confirmation_msg
        UI->>Notif: Show confirmation dialog
        alt User confirms
            UI->>BE: POST /restore (confirmed=true)
            BE-->>UI: Job started response
            UI-->>User: Restore started
        else User cancels
            UI-->>User: Operation cancelled
        end
    else File safe
        BE-->>UI: Job started response
        UI-->>User: Restore started
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Inspect is_safe_sql_file() in web/pgadmin/tools/restore/__init__.py for encoding, BOM handling, null-byte detection, line-normalization, and regex/pattern correctness.
  • Verify all call sites unpack the updated use_sql_utility() return (4-tuple) and correctly handle confirmation_msg in create_restore_job() and any other consumers.
  • Review UtilityView.jsx confirmation/resubmit flow for recursion, promise resolution, and side effects.
  • Confirm removal of saveCallBack in backup.js, maintenance.js, and restore.js doesn't break background job start/error notification expectations or require alternative hooks.

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 accurately summarizes the main change: making Plain SQL restore configurable and disabled by default in server mode, which is reflected across all file modifications.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

@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

Caution

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

⚠️ Outside diff range comments (1)
web/pgadmin/static/js/UtilityView.jsx (1)

91-94: Fix self-referential itemNodeData initialization (runtime ReferenceError)

itemNodeData is used in its own initializer:

let itemNodeData = extraData?.itemNodeData ? itemNodeData: undefined;

This will throw ReferenceError: Cannot access 'itemNodeData' before initialization whenever extraData.itemNodeData is truthy.

Consider:

-  let itemNodeData = extraData?.itemNodeData ? itemNodeData: undefined;
+  let itemNodeData = extraData?.itemNodeData ? extraData.itemNodeData : undefined;
🧹 Nitpick comments (2)
web/pgadmin/static/js/UtilityView.jsx (1)

95-120: Confirm dialog flow and cancel semantics in onSaveClick

The new confirmation_msg handling generally looks sound (a second POST/PUT with confirmed: true and promise chaining via resolve(onSaveClick(...))), but two details are worth double-checking:

  • Passing reject directly as the cancel callback means a user cancel will surface as a rejected promise to the caller. If the higher-level code treats any rejection as an error, that may display error UX for a normal cancel.
  • If the backend ever returns confirmation_msg again even when confirmed: true is sent, this will recurse indefinitely. Assumption seems to be "confirm at most once"; please ensure the server side enforces that.

If the intent is to treat cancel as a silent no-op, wrap reject to return a benign value or a specific cancellation error the caller can distinguish.

docs/en_US/restore_dialog.rst (1)

28-34: Doc note aligns with new config behavior; optional clarification

The note that Plain restore is disabled by default in server mode and enabled by setting ENABLE_PLAIN_SQL_RESTORE to True matches the new config and evaluation logic.

Optionally, you might add that the Plain format is only available when restoring from a database node, to mirror the UI behavior.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c153be and dc6ad98.

📒 Files selected for processing (12)
  • docs/en_US/restore_dialog.rst (1 hunks)
  • web/.editorconfig (1 hunks)
  • web/config.py (1 hunks)
  • web/pgadmin/browser/__init__.py (1 hunks)
  • web/pgadmin/browser/templates/browser/js/utils.js (1 hunks)
  • web/pgadmin/evaluate_config.py (1 hunks)
  • web/pgadmin/static/js/UtilityView.jsx (3 hunks)
  • web/pgadmin/tools/backup/static/js/backup.js (0 hunks)
  • web/pgadmin/tools/maintenance/static/js/maintenance.js (0 hunks)
  • web/pgadmin/tools/restore/__init__.py (5 hunks)
  • web/pgadmin/tools/restore/static/js/restore.js (0 hunks)
  • web/pgadmin/tools/restore/static/js/restore.ui.js (2 hunks)
💤 Files with no reviewable changes (3)
  • web/pgadmin/tools/backup/static/js/backup.js
  • web/pgadmin/tools/maintenance/static/js/maintenance.js
  • web/pgadmin/tools/restore/static/js/restore.js
🧰 Additional context used
🧬 Code graph analysis (3)
web/pgadmin/tools/restore/static/js/restore.ui.js (1)
web/pgadmin/static/js/UtilityView.jsx (2)
  • pgAdmin (24-24)
  • pgAdmin (80-80)
web/pgadmin/static/js/UtilityView.jsx (2)
web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx (2)
  • pgAdmin (74-74)
  • onSaveClick (122-147)
web/pgadmin/misc/properties/ObjectNodeProperties.jsx (2)
  • pgAdmin (27-27)
  • onSaveClick (115-135)
web/pgadmin/tools/restore/__init__.py (1)
web/pgadmin/utils/ajax.py (1)
  • make_json_response (66-84)
🪛 Ruff (0.14.5)
web/pgadmin/tools/restore/__init__.py

440-440: Consider moving this statement to an else block

(TRY300)


442-442: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


444-444: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: run-feature-tests-pg (14)
  • GitHub Check: run-feature-tests-pg (18)
  • GitHub Check: run-feature-tests-pg (16)
  • GitHub Check: run-feature-tests-pg (15)
  • GitHub Check: run-feature-tests-pg (17)
  • GitHub Check: run-feature-tests-pg (13)
  • GitHub Check: run-python-tests-pg (windows-latest, 15)
  • GitHub Check: run-python-tests-pg (windows-latest, 17)
  • GitHub Check: run-python-tests-pg (windows-latest, 16)
  • GitHub Check: run-python-tests-pg (windows-latest, 18)
🔇 Additional comments (10)
web/.editorconfig (1)

22-24: JSX indentation aligned with JS config

Adding indent_size = 2 for *.jsx matches the existing JS style and keeps formatting consistent across React files.

web/pgadmin/static/js/UtilityView.jsx (1)

117-119: Consistent rejection with Error instances

Standardizing the catch blocks to always reject with an Error instance (err instanceof Error ? err : new Error(gettext('Something went wrong'))) in onSaveClick, getSQLValue, and initData improves downstream error handling (stack traces, instanceof Error checks, etc.).

No further changes needed here.

Also applies to: 133-135, 177-185

web/pgadmin/browser/__init__.py (1)

512-542: Config flag correctly exposed to JS utils template

Passing enable_plain_sql_restore=config.ENABLE_PLAIN_SQL_RESTORE into browser/js/utils.js is consistent with how enable_psql and other feature flags are wired and will correctly reflect the evaluated config (desktop vs server) on the frontend.

web/pgadmin/evaluate_config.py (1)

129-136: Desktop-mode patch correctly forces plain SQL restore on

Setting config['ENABLE_PLAIN_SQL_RESTORE'] = True when SERVER_MODE is false cleanly enforces “always enabled in Desktop mode”, matching the config comments and documentation, and avoids accidental disabling via local overrides.

web/pgadmin/tools/restore/static/js/restore.ui.js (1)

10-14: Plain format correctly gated by frontend feature flag

Using pgAdmin['enable_plain_sql_restore'] && this.fieldOptions.nodeType == 'database' to decide whether to insert the Plain option ensures:

  • Desktop mode (flag true) retains existing behavior.
  • Server mode keeps Plain hidden unless explicitly enabled via config.
  • Non-database nodes never see Plain.

The sources/pgadmin import is consistent with other modules that consume global pgAdmin config.

Also applies to: 359-375

web/config.py (1)

972-981: New ENABLE_PLAIN_SQL_RESTORE flag is well-documented and safely defaulted

Introducing ENABLE_PLAIN_SQL_RESTORE = False with clear comments about server vs desktop behavior and potential meta-command risks provides a clean, centralized control point. Combined with evaluate_and_patch_config’s desktop override, this gives a sensible secure-by-default stance in server mode without regressing desktop UX.

web/pgadmin/browser/templates/browser/js/utils.js (1)

75-77: Frontend flag wiring for plain SQL restore matches existing patterns

Defining pgAdmin['enable_plain_sql_restore'] = '{{enable_plain_sql_restore}}' == 'True'; follows the same pattern as other boolean flags (e.g. enable_psql) and reliably converts the template value into a JS boolean, with a safe default of false if unset.

web/pgadmin/tools/restore/__init__.py (3)

13-13: LGTM!

The config import is necessary to access SERVER_MODE and ENABLE_PLAIN_SQL_RESTORE flags used in the security validation flow.


449-484: All callers correctly handle the new 4-value return signature.

Verification confirms:

  • Only one call site exists (line 510 in create_restore_job)
  • The call correctly unpacks all 4 return values: error_msg, utility, args, confirmation_msg
  • The variable is pre-initialized at line 501 (confirmation_msg = None), ensuring safe handling in both branches (plain format sets it from use_sql_utility(), other formats leave it as None)
  • The confirmation message is properly checked and handled at line 519

The function signature change has been fully integrated without issues.


522-526: This code correctly follows pgAdmin's convention for confirmation dialogs—no changes needed.

The use of success=0 with a data field containing confirmation_msg is pgAdmin's standard pattern for confirmation prompts. The frontend is explicitly designed to recognize and handle this response structure, displaying it as a confirmation dialog rather than an error. The implementation is correct.

@akshay-joshi akshay-joshi force-pushed the master branch 2 times, most recently from afbb0c0 to f6c6fa3 Compare November 26, 2025 06:29
Copy link

@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: 0

♻️ Duplicate comments (1)
web/pgadmin/tools/restore/__init__.py (1)

441-446: Critical: Fail-open security vulnerability persists in exception handling.

The function returns True (treating the file as safe) when FileNotFoundError or PermissionError occurs. This is a fail-open security posture that could allow unsafe files to be processed if there's a filesystem issue. The function should fail closed by returning False.

Additionally, static analysis suggests using logging.exception instead of logging.error to include stack traces.

Apply this diff to fix the security vulnerability:

         return True
     except FileNotFoundError:
-        current_app.logger.error("File not found.")
+        current_app.logger.exception(f"Security Alert: File {path} not found.")
+        return False
     except PermissionError:
-        current_app.logger.error("Insufficient permissions to access.")
-
-    return True
+        current_app.logger.exception(f"Security Alert: Insufficient permissions to access {path}.")
+        return False
🧹 Nitpick comments (2)
web/pgadmin/static/js/UtilityView.jsx (1)

103-116: Potential unhandled rejection on user cancel.

When the user cancels the confirmation dialog (line 110), reject is called without an argument. This may leave the caller without a meaningful error object, and the promise rejection might not be properly caught downstream, potentially causing unhandled promise rejection warnings.

Consider providing an explicit error or handling cancellation gracefully:

         pgAdmin.Browser.notifier.confirm(
           gettext('Warning'),
           res.data.data.confirmation_msg,
           function() {
             resolve(onSaveClick(isNew, {...data, confirmed: true}));
           },
-          reject
+          function() {
+            reject(new Error(gettext('Operation cancelled by user.')));
+          }
         );
web/pgadmin/tools/restore/__init__.py (1)

388-440: Consider restructuring the try-except for clarity.

The static analysis tool suggests moving the return True at line 440 to an else block for better code clarity. While the current logic works, the suggested structure makes the success path more explicit.

         for i, line in enumerate(normalized_text.split("\n"), 1):
             stripped = line.strip()
             # ... checks ...
             if "\\g" in line or "\\c" in line or "\\!" in line:
                 # ...
                 return False

-        return True
     except FileNotFoundError:
         # ...
     except PermissionError:
         # ...
+    else:
+        return True

Note: This refactor only makes sense after fixing the exception handling to return False on errors.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc6ad98 and f6c6fa3.

📒 Files selected for processing (12)
  • docs/en_US/restore_dialog.rst (1 hunks)
  • web/.editorconfig (1 hunks)
  • web/config.py (1 hunks)
  • web/pgadmin/browser/__init__.py (1 hunks)
  • web/pgadmin/browser/templates/browser/js/utils.js (1 hunks)
  • web/pgadmin/evaluate_config.py (1 hunks)
  • web/pgadmin/static/js/UtilityView.jsx (3 hunks)
  • web/pgadmin/tools/backup/static/js/backup.js (0 hunks)
  • web/pgadmin/tools/maintenance/static/js/maintenance.js (0 hunks)
  • web/pgadmin/tools/restore/__init__.py (5 hunks)
  • web/pgadmin/tools/restore/static/js/restore.js (0 hunks)
  • web/pgadmin/tools/restore/static/js/restore.ui.js (2 hunks)
💤 Files with no reviewable changes (3)
  • web/pgadmin/tools/restore/static/js/restore.js
  • web/pgadmin/tools/maintenance/static/js/maintenance.js
  • web/pgadmin/tools/backup/static/js/backup.js
✅ Files skipped from review due to trivial changes (1)
  • web/.editorconfig
🚧 Files skipped from review as they are similar to previous changes (5)
  • web/pgadmin/tools/restore/static/js/restore.ui.js
  • web/pgadmin/browser/init.py
  • web/config.py
  • web/pgadmin/browser/templates/browser/js/utils.js
  • web/pgadmin/evaluate_config.py
🧰 Additional context used
🧬 Code graph analysis (1)
web/pgadmin/static/js/UtilityView.jsx (2)
web/pgadmin/static/js/SchemaView/SchemaDialogView.jsx (2)
  • pgAdmin (74-74)
  • onSaveClick (122-147)
web/pgadmin/misc/properties/ObjectNodeProperties.jsx (2)
  • pgAdmin (27-27)
  • onSaveClick (115-135)
🪛 Ruff (0.14.5)
web/pgadmin/tools/restore/__init__.py

440-440: Consider moving this statement to an else block

(TRY300)


442-442: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


444-444: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: run-feature-tests-pg (13)
  • GitHub Check: run-feature-tests-pg (15)
  • GitHub Check: run-feature-tests-pg (17)
  • GitHub Check: run-feature-tests-pg (18)
  • GitHub Check: run-feature-tests-pg (14)
  • GitHub Check: run-feature-tests-pg (16)
🔇 Additional comments (4)
web/pgadmin/static/js/UtilityView.jsx (1)

117-119: LGTM!

The error handling standardization ensures consistent Error object wrapping across all catch blocks, matching the pattern used elsewhere in the codebase.

web/pgadmin/tools/restore/__init__.py (2)

462-475: LGTM on the gating logic.

The server mode gating and confirmation flow is well-structured:

  • Server mode with disabled flag returns immediately with appropriate message
  • Unsafe files in server mode are blocked; in desktop mode, users get a confirmation prompt
  • The confirmed flag correctly skips re-checking once user has acknowledged

522-526: The review comment references code that does not exist in the current codebase.

After thorough verification:

  1. Lines 522-526 don't exist: web/pgadmin/tools/restore/__init__.py contains only 521 lines. The current code at lines 515-535 shows error handling with errormsg, not confirmation_msg.

  2. No 4-tuple unpacking: The current use_sql_utility() function (line 407) returns exactly 3 values: (error_msg, utility, args), not 4 as claimed in line 509-511.

  3. No frontend confirmation_msg handling: A comprehensive search of the codebase finds zero references to confirmation_msg anywhere—neither in UtilityView.jsx nor any other frontend file. The claimed check at UtilityView.jsx:103 does not exist.

The review comment appears to be based on proposed code changes that are not yet present in the repository, or it references an incorrect branch/version of the code.

Likely an incorrect or invalid review comment.

docs/en_US/restore_dialog.rst (1)

30-34: LGTM on documentation update.

The documentation clearly explains that Plain SQL restore is disabled by default in server mode and provides the configuration setting name to enable it. This aligns with the code changes.

Consider adding a reference to where the configuration file is located (e.g., config_local.py) for users unfamiliar with pgAdmin configuration.

@akshay-joshi
Copy link
Contributor Author

Closing this PR as design is changed.

Copy link

@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: 1

♻️ Duplicate comments (1)
web/pgadmin/tools/restore/__init__.py (1)

378-447: Fail-open behavior in is_safe_sql_file still marks unreadable files as “safe”.

FileNotFoundError and PermissionError currently log an error and then fall through to return True at line 446, so callers treat an unreadable file as safe. That’s a fail-open posture for a security gate and reintroduces the issue previously flagged by the bot.

To fail closed and satisfy the Ruff TRY400 hint (use logging.exception), please log with stack trace and return False from both handlers so unsafe/unreadable files never pass the safety check:

     try:
         with open(path, "rb") as f:
             raw_data = f.read()
@@
-        return True
-    except FileNotFoundError:
-        current_app.logger.error("File not found.")
-    except PermissionError:
-        current_app.logger.error("Insufficient permissions to access.")
-
-    return True
+        # All checks passed
+        return True
+    except FileNotFoundError:
+        current_app.logger.exception(
+            "Security Alert: File %s not found.", path
+        )
+        return False
+    except PermissionError:
+        current_app.logger.exception(
+            "Security Alert: Insufficient permissions to access %s.", path
+        )
+        return False

This keeps the existing semantics for valid files while ensuring any I/O failure is treated as “not safe” rather than silently allowed through.

🧹 Nitpick comments (2)
web/pgadmin/evaluate_config.py (2)

129-136: Clarify whether desktop mode should always force-enable Plain SQL restore

You’re unconditionally setting config['ENABLE_PLAIN_SQL_RESTORE'] = True when SERVER_MODE is false, same as ENABLE_PSQL. That means any value from config_local, config_distro, or config_system is ignored in desktop mode.

If the intent is “always on in desktop” this is fine and consistent with ENABLE_PSQL. If, instead, you only want a different default while still allowing admins to disable it in desktop mode, consider setdefault:

-    if not config.get('SERVER_MODE'):
-        config['USER_INACTIVITY_TIMEOUT'] = 0
-        # Enable PSQL in Desktop Mode.
-        config['ENABLE_PSQL'] = True
-        # Enable Plain SQL Restore in Desktop Mode.
-        config['ENABLE_PLAIN_SQL_RESTORE'] = True
+    if not config.get('SERVER_MODE'):
+        config['USER_INACTIVITY_TIMEOUT'] = 0
+        # Enable PSQL in Desktop Mode by default.
+        config.setdefault('ENABLE_PSQL', True)
+        # Enable Plain SQL Restore in Desktop Mode by default.
+        config.setdefault('ENABLE_PLAIN_SQL_RESTORE', True)

41-56: Consider validating ENABLE_PLAIN_SQL_RESTORE as a boolean in custom configs

ENABLE_PLAIN_SQL_RESTORE is a boolean flag but isn’t included in boolean_keys, so type validation won’t catch non-bool overrides in config_local/config_distro/config_system.

If you want consistent validation with other boolean settings, add it here:

 def validate_config_variable(key, value):
-    boolean_keys = ['SERVER_MODE', 'ENHANCED_COOKIE_PROTECTION',
-                    'SUPPORT_SSH_TUNNEL', 'ALLOW_SAVE_TUNNEL_PASSWORD',
-                    'MASTER_PASSWORD_REQUIRED']
+    boolean_keys = ['SERVER_MODE', 'ENHANCED_COOKIE_PROTECTION',
+                    'SUPPORT_SSH_TUNNEL', 'ALLOW_SAVE_TUNNEL_PASSWORD',
+                    'MASTER_PASSWORD_REQUIRED',
+                    'ENABLE_PLAIN_SQL_RESTORE']
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6c6fa3 and e0c2518.

📒 Files selected for processing (12)
  • docs/en_US/restore_dialog.rst (1 hunks)
  • web/.editorconfig (1 hunks)
  • web/config.py (1 hunks)
  • web/pgadmin/browser/__init__.py (1 hunks)
  • web/pgadmin/browser/templates/browser/js/utils.js (1 hunks)
  • web/pgadmin/evaluate_config.py (1 hunks)
  • web/pgadmin/static/js/UtilityView.jsx (3 hunks)
  • web/pgadmin/tools/backup/static/js/backup.js (0 hunks)
  • web/pgadmin/tools/maintenance/static/js/maintenance.js (0 hunks)
  • web/pgadmin/tools/restore/__init__.py (5 hunks)
  • web/pgadmin/tools/restore/static/js/restore.js (0 hunks)
  • web/pgadmin/tools/restore/static/js/restore.ui.js (2 hunks)
💤 Files with no reviewable changes (3)
  • web/pgadmin/tools/maintenance/static/js/maintenance.js
  • web/pgadmin/tools/restore/static/js/restore.js
  • web/pgadmin/tools/backup/static/js/backup.js
✅ Files skipped from review due to trivial changes (1)
  • docs/en_US/restore_dialog.rst
🚧 Files skipped from review as they are similar to previous changes (6)
  • web/pgadmin/tools/restore/static/js/restore.ui.js
  • web/pgadmin/browser/init.py
  • web/pgadmin/static/js/UtilityView.jsx
  • web/pgadmin/browser/templates/browser/js/utils.js
  • web/config.py
  • web/.editorconfig
🧰 Additional context used
🧬 Code graph analysis (1)
web/pgadmin/tools/restore/__init__.py (2)
web/pgadmin/utils/ajax.py (1)
  • make_json_response (66-84)
web/pgadmin/utils/__init__.py (2)
  • get (936-939)
  • does_utility_exist (337-358)
🪛 Ruff (0.14.7)
web/pgadmin/tools/restore/__init__.py

440-440: Consider moving this statement to an else block

(TRY300)


442-442: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


444-444: Use logging.exception instead of logging.error

Replace with exception

(TRY400)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: run-javascript-tests (macos-latest)
  • GitHub Check: run-javascript-tests (ubuntu-22.04)
  • GitHub Check: run-javascript-tests (windows-latest)
  • GitHub Check: run-python-tests-pg (windows-latest, 16)
  • GitHub Check: run-python-tests-pg (windows-latest, 18)
  • GitHub Check: run-python-tests-pg (windows-latest, 17)
  • GitHub Check: run-feature-tests-pg (18)
  • GitHub Check: run-feature-tests-pg (14)
  • GitHub Check: run-feature-tests-pg (13)
  • GitHub Check: run-feature-tests-pg (16)
  • GitHub Check: run-feature-tests-pg (17)
  • GitHub Check: run-feature-tests-pg (15)

Comment on lines 449 to 476
def use_sql_utility(data, manager, server, filepath):
# Check the meta commands in file.
if has_meta_commands(filepath):
return _("Restore blocked: the selected PLAIN SQL file contains psql "
"meta-commands (for example \\! or \\i). For safety, "
"pgAdmin does not execute meta-commands from PLAIN restores. "
"Please remove meta-commands."), None, None
block_msg = _("Restore Blocked: The selected PLAIN SQL file contains "
"commands that are considered potentially unsafe or include "
"meta-commands (like \\! or \\i) that could execute "
"external shell commands or scripts on the pgAdmin server. "
"For security reasons, pgAdmin will not restore this PLAIN "
"SQL file. Please check the logs for more details.")
confirm_msg = _("The selected PLAIN SQL file contains commands that are "
"considered potentially unsafe or include meta-commands "
"(like \\! or \\i) that could execute external shell "
"commands or scripts on the pgAdmin server.\n\n "
"Do you still wish to continue?")

if config.SERVER_MODE and not config.ENABLE_PLAIN_SQL_RESTORE:
return _("Plain SQL restore is disabled by default when running in "
"server mode. To allow this functionality, you must set the "
"configuration setting ENABLE_PLAIN_SQL_RESTORE to True and "
"then restart the pgAdmin application."), None, None, None

# If data has confirmed, then no need to check for the safe SQL file again
if not data.get('confirmed', False):
# Check the SQL file is safe to process.
safe_sql_file = is_safe_sql_file(filepath)
if not safe_sql_file and config.SERVER_MODE:
return block_msg, None, None, None
elif not safe_sql_file and not config.SERVER_MODE:
return None, None, None, confirm_msg

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Server-mode safety check can be bypassed via client-supplied confirmed flag.

In use_sql_utility, is_safe_sql_file runs only when not data.get('confirmed', False). An authenticated client can set confirmed: true in the POST body and completely skip the safety check—even in server mode with ENABLE_PLAIN_SQL_RESTORE enabled—defeating the meta-command protection.

For server mode, the safety check should be unconditional and independent of client input. confirmed should only control the confirmation flow in non-server (desktop) mode. For example:

-    # If data has confirmed, then no need to check for the safe SQL file again
-    if not data.get('confirmed', False):
-        # Check the SQL file is safe to process.
-        safe_sql_file = is_safe_sql_file(filepath)
-        if not safe_sql_file and config.SERVER_MODE:
-            return block_msg, None, None, None
-        elif not safe_sql_file and not config.SERVER_MODE:
-            return None, None, None, confirm_msg
+    if config.SERVER_MODE:
+        # In server mode, always enforce the safety check regardless of
+        # any client-supplied flags.
+        safe_sql_file = is_safe_sql_file(filepath)
+        if not safe_sql_file:
+            return block_msg, None, None, None
+    else:
+        # In desktop mode, allow the user to explicitly confirm once.
+        if not data.get('confirmed', False):
+            safe_sql_file = is_safe_sql_file(filepath)
+            if not safe_sql_file:
+                return None, None, None, confirm_msg

This preserves the intended UX (confirmation only in desktop mode) while ensuring server-mode deployments cannot bypass the security filter with crafted requests.

🤖 Prompt for AI Agents
In web/pgadmin/tools/restore/__init__.py around lines 449–476, the current logic
skips calling is_safe_sql_file when data.get('confirmed') is True which allows a
client to bypass the server-mode safety check; change the flow so that in server
mode is_safe_sql_file is always executed regardless of the client-supplied
confirmed flag (i.e., perform the safety check unconditionally when
config.SERVER_MODE is True and return block_msg if unsafe), while restricting
use of the confirmed flag to only drive the confirmation prompt flow in
non-server (desktop) mode (i.e., if not config.SERVER_MODE and not safe_sql_file
then return confirm_msg), preserving the existing ENABLE_PLAIN_SQL_RESTORE check
and return values.

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.

1 participant