T09 · Insecure Skill Coding Practices
- Location
SKILL.md:286- Finding
Unauthenticated API Endpoints Expose User Information
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md, lines 286–313
Vulnerability Type: Missing authentication and authorization
Risk Level: MediumVulnerable Code
python @router.get("/", response_model=list[UserResponse]) async def list_users( skip: int = 0, limit: int = 100, db: AsyncSession = Depends(get_db), ): return await user_crud.get_users(db, skip=skip, limit=limit) @router.get("/{user_id}", response_model=UserResponse) async def get_user( user_id: int, db: AsyncSession = Depends(get_db), ): user = await user_crud.get_user(db, user_id) if not user: raise HTTPException(status_code=404, detail="User not found") return userTechnical Analysis
The user-listing and individual-user endpoints do not include the
get_current_userauthentication dependency. Consequently, the generated application permits anonymous clients to enumerate user records and retrieve individual users by numeric ID.The configured
UserResponseschema includes users' email addresses, IDs, activity status, and creation timestamps. Pagination does not provide an authorization boundary because an anonymous client can repeatedly changeskipandlimit.The predictable integer identifiers accepted by
GET /users/{user_id}also enable systematic account enumeration. Different responses for existing and nonexistent IDs further reveal which accounts exist.Attack Path
- An unauthenticated attacker sends
GET /api/v1/users/?skip=0&limit=100. - The application returns user IDs, email addresses, activity states, and creation timestamps.
- The attacker increments
skipto enumerate additional records. - Alternatively, the attacker requests sequential IDs through
GET /api/v1/users/{user_id}. - The collected account identifiers can be used for phishing, credential stuffing, or user profiling.
Impact Assessment
No authenticated application privileges are required. A remote anonymous client can a ...[truncated 273 chars]
- An unauthenticated attacker sends
- Remediation
View remediation
Remediation Suggestions
Require authentication on both endpoints and apply explicit authorization rules:
python @router.get("/", response_model=list[UserResponse]) async def list_users( skip: int = 0, limit: int = 100, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): if not current_user.is_superuser: raise HTTPException(status_code=403, detail="Not authorized") return await user_crud.get_users(db, skip=skip, limit=limit) @router.get("/{user_id}", response_model=UserResponse) async def get_user( user_id: int, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user), ): if user_id != current_user.id and not current_user.is_superuser: raise HTTPException(status_code=403, detail="Not authorized") user = await user_crud.get_user(db, user_id) if not user: raise HTTPException(status_code=404, detail="User not found") return userAdditional hardening measures:
- Restrict user-list access to administrators or remove the endpoint when it is unnecessary.
- Permit ordinary users to retrieve only their own profile.
- Use a separate public response schema that omits email addresses and other sensitive metadata.
- Enforce bounded pagination values to reduce bulk extraction risk.
- Consider returning uniform authorization responses where appropriate to reduce account enumeration.
- Add tests confirming that anonymous requests receive HTTP 401 and unauthorized authenticated requests receive HTTP 403.
