fix: get_tree returns 404 (not 403) for inaccessible trees — don't leak resource existence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-04-10 04:17:31 +00:00
parent 14304be383
commit 8f044849d4
2 changed files with 55 additions and 2 deletions

View File

@@ -392,9 +392,10 @@ async def get_tree(
)
if not tree.is_active or not can_access_tree(current_user, tree):
# Always 404, never 403. A 403 confirms the resource exists.
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have access to this tree"
status_code=status.HTTP_404_NOT_FOUND,
detail="Tree not found"
)
return build_full_tree_response(tree)

View File

@@ -447,3 +447,55 @@ class TestVisibilityFilter:
assert "author_name" in trees[0]
# visibility key should be present
assert "visibility" in trees[0]
@pytest.mark.asyncio
async def test_get_tree_returns_404_not_403_for_other_account_tree(
self, client: AsyncClient, auth_headers: dict, test_db: AsyncSession
):
"""Account A must not learn that Account B's private tree exists."""
from app.models.tree import Tree
from app.models.account import Account
from app.models.user import User
from app.core.security import get_password_hash
import uuid
# Create a second account and user
account_b = Account(name="Other Corp", display_code="OTH00001")
test_db.add(account_b)
await test_db.flush()
user_b = User(
email=f"user-b-{uuid.uuid4().hex[:6]}@example.com",
name="User B",
password_hash=get_password_hash("TestPass123!"),
is_active=True,
account_id=account_b.id,
account_role="engineer",
)
test_db.add(user_b)
await test_db.flush()
# Create a private tree belonging to account_b
private_tree = Tree(
name="Secret Tree",
account_id=account_b.id,
author_id=user_b.id,
visibility="private",
tree_type="troubleshooting",
tree_structure={"id": "root", "type": "start", "children": []},
is_active=True,
is_default=False,
is_public=False,
status="published",
)
test_db.add(private_tree)
await test_db.commit()
response = await client.get(
f"/api/v1/trees/{private_tree.id}",
headers=auth_headers,
)
assert response.status_code == 404, (
f"Expected 404 but got {response.status_code}"
"leaking tree existence to wrong tenant"
)