Skip to content

Commit 99bec27

Browse files
Clément VALENTINclaude
andcommitted
security: add security headers and remove credential logging
nginx.conf: - Add Content-Security-Policy header - Add HSTS header (commented, enable in production) - Add Referrer-Policy and Permissions-Policy headers accounts.py: - Remove debug logging that could expose credentials - Simplify error handling in token endpoint 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 1edeb1d commit 99bec27

File tree

2 files changed

+14
-12
lines changed

2 files changed

+14
-12
lines changed

apps/api/src/routers/accounts.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -225,10 +225,6 @@ async def get_token(
225225
226226
Accepts credentials either in form data or in Authorization header (Basic Auth).
227227
"""
228-
logger.debug(f"[TOKEN] Received request - Content-Type: {request.headers.get('content-type')}")
229-
logger.debug(f"[TOKEN] Authorization header: {request.headers.get('authorization', 'None')[:50] if request.headers.get('authorization') else 'None'}")
230-
logger.debug(f"[TOKEN] Form client_id: {client_id}, client_secret: {'***' if client_secret else None}")
231-
232228
# Try to get credentials from Authorization header (Basic Auth)
233229
if not client_id or not client_secret:
234230
auth_header = request.headers.get('authorization')
@@ -238,28 +234,24 @@ async def get_token(
238234
encoded = auth_header.split(' ')[1]
239235
decoded = base64.b64decode(encoded).decode('utf-8')
240236
client_id, client_secret = decoded.split(':', 1)
241-
logger.debug(f"[TOKEN] From Basic Auth - client_id: {client_id}, client_secret: ***")
242-
except Exception as e:
243-
logger.error(f"[TOKEN] Failed to parse Basic Auth: {e}")
237+
except Exception:
238+
pass # Invalid Basic Auth format, will try form data next
244239

245240
# If still not found, try form data
246241
if not client_id or not client_secret:
247242
try:
248243
form_data = await request.form()
249-
logger.debug(f"[TOKEN] Form data keys: {list(form_data.keys())}")
250244
client_id_form = form_data.get('client_id')
251245
client_secret_form = form_data.get('client_secret')
252246
# Only use if it's a string (not UploadFile)
253247
if isinstance(client_id_form, str):
254248
client_id = client_id_form or client_id
255249
if isinstance(client_secret_form, str):
256250
client_secret = client_secret_form or client_secret
257-
logger.debug(f"[TOKEN] After form parse - client_id: {client_id}, client_secret: {'***' if client_secret else None}")
258-
except Exception as e:
259-
logger.error(f"[TOKEN] Failed to parse form: {e}")
251+
except Exception:
252+
pass # Form parsing failed, credentials may still be from Basic Auth
260253

261254
if not client_id or not client_secret:
262-
logger.debug(f"[TOKEN] Missing credentials - client_id: {client_id}, client_secret: {'***' if client_secret else None}")
263255
raise HTTPException(status_code=422, detail="client_id and client_secret are required (provide in form data or Basic Auth header)")
264256

265257
# Find user by client_id

apps/web/nginx.conf

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,14 @@ server {
2626
add_header X-Frame-Options "SAMEORIGIN" always;
2727
add_header X-Content-Type-Options "nosniff" always;
2828
add_header X-XSS-Protection "1; mode=block" always;
29+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
30+
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
31+
32+
# HSTS - Force HTTPS for 1 year (enable in production behind HTTPS proxy)
33+
# Uncomment when served over HTTPS:
34+
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
35+
36+
# Content Security Policy
37+
# Allows inline styles (Tailwind), scripts from self, and API connections
38+
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://logo.clearbit.com; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';" always;
2939
}

0 commit comments

Comments
 (0)