Skip to content

Commit 90b1762

Browse files
committed
fix: prevent IDOR vulnerability in environment update endpoint
Make the `project` field read-only during environment updates to prevent attackers from moving an environment to a different project they don't own. The vulnerability allowed an attacker with access to their own environment to modify the `project` field in the PUT request body, effectively moving their environment into a victim's project. Fix: Override __init__ in CreateUpdateEnvironmentSerializer to set project field as read-only when instance exists (update operation).
1 parent 5a26f45 commit 90b1762

File tree

2 files changed

+38
-0
lines changed

2 files changed

+38
-0
lines changed

api/environments/serializers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ class Meta(EnvironmentSerializerWithMetadata.Meta):
130130
)
131131
]
132132

133+
def __init__(self, *args: Any, **kwargs: Any) -> None:
134+
super().__init__(*args, **kwargs) # type: ignore[no-untyped-call]
135+
# Prevent IDOR: project cannot be changed after creation
136+
if self.instance is not None:
137+
self.fields["project"].read_only = True
138+
133139
def get_subscription(self) -> Subscription | None:
134140
view = self.context["view"]
135141

api/tests/unit/environments/test_unit_environments_views.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,38 @@ def test_environment_update_cannot_change_is_creating(
10831083
assert response.json()["is_creating"] is False
10841084

10851085

1086+
def test_environment_update_cannot_change_project(
1087+
environment: Environment,
1088+
project: Project,
1089+
organisation: Organisation,
1090+
admin_client_new: APIClient,
1091+
) -> None:
1092+
# Given - an environment in the original project
1093+
original_project = project
1094+
url = reverse("api-v1:environments:environment-detail", args=[environment.api_key])
1095+
1096+
# and a different project
1097+
other_project = Project.objects.create(
1098+
name="Other Project", organisation=organisation
1099+
)
1100+
1101+
data = {
1102+
"project": other_project.id,
1103+
"name": environment.name,
1104+
}
1105+
1106+
# When
1107+
response = admin_client_new.put(
1108+
url, data=json.dumps(data), content_type="application/json"
1109+
)
1110+
1111+
# Then - the project should NOT change
1112+
assert response.status_code == status.HTTP_200_OK
1113+
environment.refresh_from_db()
1114+
assert environment.project_id == original_project.id
1115+
assert response.json()["project"] == original_project.id
1116+
1117+
10861118
def test_get_document(
10871119
environment: Environment,
10881120
project: Project,

0 commit comments

Comments
 (0)