Skip to content

Commit 26dfa7e

Browse files
abhizipstackclaude
andcommitted
fix: add server-side RBAC to unprotected write/delete endpoints (VAPT #1)
Add @handle_permission decorator to 14 non-GET endpoints that were missing server-side permission checks, allowing privilege escalation via response tampering. Connection (connectiondetails): - delete_connection, delete_all_connections, test_connection Environment (environmentmodels): - test_environment Projects (projectdetails): - create_sample_project, set_project_schema, save_model_file - set_model_config_and_reference, set_model_transformation - delete_model_transformation, set_model_presentation - validate_model_file, write_database_file, generate_formula Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cbf44b8 commit 26dfa7e

3 files changed

Lines changed: 14 additions & 0 deletions

File tree

backend/backend/core/routers/connection/views.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ def connection_usage(request: Request, connection_id: str) -> Response:
112112

113113
@api_view([HTTPMethods.POST, HTTPMethods.DELETE])
114114
@handle_http_request
115+
@handle_permission
115116
def delete_connection(request: Request, connection_id: str) -> Response:
116117
con_context = ConnectionContext()
117118
con_context.delete_connection(connection_id=connection_id)
@@ -124,6 +125,7 @@ def delete_connection(request: Request, connection_id: str) -> Response:
124125

125126
@api_view([HTTPMethods.DELETE])
126127
@handle_http_request
128+
@handle_permission
127129
def delete_all_connections(request: Request) -> Response:
128130
con_context = ConnectionContext()
129131
result = con_context.delete_all_connections()
@@ -146,6 +148,7 @@ def reveal_connection_credentials(request: Request, connection_id: str) -> Respo
146148

147149
@api_view([HTTPMethods.POST])
148150
@handle_http_request
151+
@handle_permission
149152
def test_connection(request: Request) -> Response:
150153
con_context = ConnectionContext()
151154
request_data: dict[str, Union[dict[str, Any], str, None]] = request.data

backend/backend/core/routers/environment/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def environment_dependent_projects(request: Request, environment_id: str):
124124

125125
@api_view([HTTPMethods.POST])
126126
@handle_http_request
127+
@handle_permission
127128
def test_environment(request: Request):
128129
request_data: dict[str, Any] = request.data
129130
datasource: str = request_data.get("datasource")

backend/backend/core/routers/projects/views.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ def get_projects_list(request: Request) -> Response:
169169

170170
@api_view([HTTPMethods.POST])
171171
@handle_http_request
172+
@handle_permission
172173
def create_sample_project(request) -> Response:
173174
load_project_name = request.data.get("template", "jaffleshop_final")
174175

@@ -366,6 +367,7 @@ def export_model_content_csv(
366367

367368
@api_view([HTTPMethods.POST])
368369
@handle_http_request
370+
@handle_permission
369371
def set_project_schema(request: Request, project_id: str) -> Response:
370372
app = ApplicationContext(project_id=project_id)
371373
schema_name = request.data.get("schema_name")
@@ -679,6 +681,7 @@ def rollback_model_file_content(
679681

680682
@api_view([HTTPMethods.POST])
681683
@handle_http_request
684+
@handle_permission
682685
def save_model_file(request: Request, project_id: str, file_name: str) -> Response:
683686
# This method is used to save the model file inside the project
684687
# This API is depreciated and will be removed in next release
@@ -699,6 +702,7 @@ def save_model_file(request: Request, project_id: str, file_name: str) -> Respon
699702
@api_view([HTTPMethods.POST])
700703
@clear_cache(patterns=["model_content_{project_id}_*"])
701704
@handle_http_request
705+
@handle_permission
702706
def set_model_config_and_reference(
703707
request: Request, project_id: str, file_name: str
704708
) -> Response:
@@ -765,6 +769,7 @@ def set_model_config_and_reference(
765769
@api_view([HTTPMethods.POST])
766770
@clear_cache(patterns=["model_content_{project_id}_*"])
767771
@handle_http_request
772+
@handle_permission
768773
def set_model_transformation(
769774
request: Request, project_id: str, file_name: str
770775
) -> Response:
@@ -782,6 +787,7 @@ def set_model_transformation(
782787
@api_view([HTTPMethods.DELETE])
783788
@clear_cache(patterns=["model_content_{project_id}_*"])
784789
@handle_http_request
790+
@handle_permission
785791
def delete_model_transformation(
786792
request: Request, project_id: str, file_name: str
787793
) -> Response:
@@ -803,6 +809,7 @@ def delete_model_transformation(
803809
@api_view([HTTPMethods.POST])
804810
@clear_cache(patterns=["model_content_{project_id}_*"])
805811
@handle_http_request
812+
@handle_permission
806813
def set_model_presentation(
807814
request: Request, project_id: str, file_name: str
808815
) -> Response:
@@ -840,6 +847,7 @@ def get_transformation_columns(
840847

841848
@api_view([HTTPMethods.POST])
842849
@handle_http_request
850+
@handle_permission
843851
def validate_model_file(request: Request, project_id: str, file_name: str) -> Response:
844852
# This method is used to validate the model file inside the project
845853
request_data = request.data
@@ -852,6 +860,7 @@ def validate_model_file(request: Request, project_id: str, file_name: str) -> Re
852860

853861
@api_view([HTTPMethods.POST])
854862
@handle_http_request
863+
@handle_permission
855864
def write_database_file(request: Request, project_id: str) -> Response:
856865
# By default, the files will be uploaded in seeds path as of now
857866
# request_data = request.data
@@ -864,6 +873,7 @@ def write_database_file(request: Request, project_id: str) -> Response:
864873

865874
@api_view([HTTPMethods.POST])
866875
@handle_http_request
876+
@handle_permission
867877
def generate_formula(request: Request, project_id: str, model_name: str) -> Response:
868878
# Generate Excel Formula based on the User prompt with OpenAi support
869879
user_prompt = request.data["user_prompt"]

0 commit comments

Comments
 (0)