codeant-ai-for-open-source[bot] commented on code in PR #41550:
URL: https://github.com/apache/superset/pull/41550#discussion_r3691343034


##########
superset-frontend/packages/superset-ui-core/src/components/DeleteModal/index.tsx:
##########
@@ -74,32 +81,34 @@ export function DeleteModal({
 
   return (
     <Modal
-      disablePrimaryButton={disableChange}
+      disablePrimaryButton={showConfirmationInput ? disableChange : false}

Review Comment:
   **Suggestion:** The confirmation state is not reset when the modal closes or 
when its mode changes. After a user successfully types DELETE, `disableChange` 
remains false, so reopening the same mounted modal allows a permanent deletion 
without re-entering the confirmation text; switching from recoverable mode back 
to confirmation mode can expose the same stale enabled state. Reset 
`disableChange` whenever the modal is hidden or when confirmation mode is 
re-entered. [logic error]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Permanent deletion can bypass the type-to-confirm safeguard.
   - ❌ Repeated modal use can cause accidental irreversible deletion.
   - ⚠️ Affects archived-item purge and other mounted delete dialogs.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=4a5ddcf350244fddb99261d7161c9aaf&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=4a5ddcf350244fddb99261d7161c9aaf&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** 
superset-frontend/packages/superset-ui-core/src/components/DeleteModal/index.tsx
   **Line:** 84:84
   **Comment:**
        *Logic Error: The confirmation state is not reset when the modal closes 
or when its mode changes. After a user successfully types DELETE, 
`disableChange` remains false, so reopening the same mounted modal allows a 
permanent deletion without re-entering the confirmation text; switching from 
recoverable mode back to confirmation mode can expose the same stale enabled 
state. Reset `disableChange` whenever the modal is hidden or when confirmation 
mode is re-entered.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41550&comment_hash=1f27d3cbabfecbe5ce8d122780a2cce009dd9b93730a17c48d7097262c9a0d69&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41550&comment_hash=1f27d3cbabfecbe5ce8d122780a2cce009dd9b93730a17c48d7097262c9a0d69&reaction=dislike'>👎</a>



##########
superset/commands/purge.py:
##########
@@ -0,0 +1,177 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Owner/admin-gated permanent delete (force-purge) of a soft-deleted entity.
+
+This is the RBAC-enforced REST surface anticipated by the deletion-retention
+force-purge contract: it verifies ownership (owners/admins, mirroring restore)
+on the soft-deleted row, then delegates the irreversible cascade removal to
+``ForcePurgeCommand`` (which handles dependents, M:N rows, version history,
+audit, and the commit). Restricted to *soft-deleted* rows so it only operates 
on
+items the user already sees in the archive.
+"""
+
+import logging
+from dataclasses import dataclass
+from typing import Any
+
+from sqlalchemy.exc import SQLAlchemyError
+
+from superset import is_feature_enabled, security_manager
+from superset.commands.base import BaseCommand
+from superset.commands.deletion_retention.force_purge import ForcePurgeCommand
+from superset.daos.base import BaseDAO
+from superset.daos.exceptions import DAODeleteFailedError
+from superset.exceptions import SupersetSecurityException
+from superset.models.helpers import SoftDeleteMixin
+from superset.tasks.utils import get_current_user
+
+#: Recorded when the audit trail cannot name the acting user. The purge routes
+#: are ``@protect()``-ed, so this should be unreachable; it exists so an
+#: anomaly is visible as one rather than disguised as a plausible username.
+logger = logging.getLogger(__name__)
+
+UNKNOWN_ACTOR = "unknown"
+
+
+@dataclass(frozen=True)
+class SoftDeleteBinding:
+    """Entity-specific bindings for the soft-delete purge command.
+
+    Lets one command serve every soft-delete type without a subclass per
+    entity. The REST route supplies the binding for its entity (see each
+    ``*RestApi``).
+    """
+
+    dao: type[BaseDAO[Any]]
+    not_found: type[Exception]
+    forbidden: type[Exception]
+    delete_failed: type[Exception]
+
+
+class PurgeArchivedCommand(BaseCommand):
+    """Permanently delete a single soft-deleted entity, by UUID."""
+
+    def __init__(self, model_uuid: str, binding: SoftDeleteBinding) -> None:
+        self._model_uuid = model_uuid
+        self._binding = binding
+        #: The authorized entity, resolved by ``validate()``. ``BaseCommand``
+        #: fixes ``validate()``'s return type as ``None``, so the model is
+        #: handed to ``run()`` here rather than returned.
+        self._model: SoftDeleteMixin | None = None
+
+    def run(self) -> None:
+        self.validate()
+        model = self._model
+        if model is None:  # pragma: no cover — validate() raises or sets it
+            raise self._binding.not_found(f"No row with 
uuid={self._model_uuid!r}")
+        try:
+            # ForcePurgeCommand owns the cascade + commit + audit.
+            #
+            # model_cls pins resolution to the type this route authorized:
+            # UUIDs are unique per table but not across them, so an
+            # unconstrained search could purge a different entity than the one
+            # validate() checked the caller against.
+            #
+            # require_archived re-asserts soft-deleted state at resolution
+            # time, closing the window between authorization and purge in which
+            # a concurrent restore would otherwise expose a live row.
+            result = ForcePurgeCommand(
+                self._model_uuid,
+                actor=get_current_user() or UNKNOWN_ACTOR,
+                model_cls=type(model),
+                require_archived=True,
+                # An end user's irreversible purge must never run unaudited;
+                # the CLI's fail-open default is an operator-trust decision
+                # that does not extend to REST principals.
+                require_audit=True,

Review Comment:
   **Suggestion:** The caller's editorship is checked only in `validate()`, but 
`ForcePurgeCommand` resolves the row again after the audit write and then 
performs the destructive cascade without rechecking authorization. If the 
user's ownership or admin role is revoked after validation succeeds, this 
request can still permanently purge the archived object. Tie the authorization 
check to the final row resolution or pass an authorization callback that is 
evaluated immediately before the cascade. [security]
   
   <details>
   <summary><b>Severity Level:</b> Critical 🚨</summary>
   
   ```mdx
   - ❌ Revoked editors can still permanently delete archived datasets during an 
in-flight request.
   - ❌ Purge removes the entity and dependents through `cascade_hard_delete()`.
   - ⚠️ The audit record does not establish that authorization remained valid.
   ```
   </details>
   
   [![Fix in 
Cursor](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-cursor-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=cursor&prompt_id=9e6ddb386aa64516acf3ed8ddbf7ff74&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
 [![Fix in VSCode 
Claude](https://new-codeant-butcket.s3.us-west-1.amazonaws.com/badges/fix-in-vscode-claude-flat.svg)](https://app.codeant.ai/fix-in-ide?tool=vscode-claude&prompt_id=9e6ddb386aa64516acf3ed8ddbf7ff74&service=github&base_url=https%3A%2F%2Fgithub.com&org=apache&repo=apache%2Fsuperset)
   
   *(Use Cmd/Ctrl + Click for best experience)*
   <details>
   <summary><b>Prompt for AI Agent 🤖 </b></summary>
   
   ```mdx
   This is a comment left during a code review.
   
   **Path:** superset/commands/purge.py
   **Line:** 92:100
   **Comment:**
        *Security: The caller's editorship is checked only in `validate()`, but 
`ForcePurgeCommand` resolves the row again after the audit write and then 
performs the destructive cascade without rechecking authorization. If the 
user's ownership or admin role is revoked after validation succeeds, this 
request can still permanently purge the archived object. Tie the authorization 
check to the final row resolution or pass an authorization callback that is 
evaluated immediately before the cascade.
   
   Validate the correctness of the flagged issue. If correct, How can I resolve 
this? If you propose a fix, implement it and please make it concise.
   Once fix is implemented, also check other comments on the same PR, and ask 
user if the user wants to fix the rest of the comments as well. if said yes, 
then fetch all the comments validate the correctness and implement a minimal fix
   ```
   </details>
   <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41550&comment_hash=5dd5c6c81ff4206a66028e9dce18e7b46ef62658a93de1f2763edcec94097cb6&reaction=like'>👍</a>
 | <a 
href='https://app.codeant.ai/feedback?pr_url=https%3A%2F%2Fgithub.com%2Fapache%2Fsuperset%2Fpull%2F41550&comment_hash=5dd5c6c81ff4206a66028e9dce18e7b46ef62658a93de1f2763edcec94097cb6&reaction=dislike'>👎</a>



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to