Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,18 +122,21 @@ async def lifespan(app: FastAPI):

if not frontend_url:
if is_production:
raise ValueError(
"FRONTEND_URL environment variable is required for security in production. "
"Set it to your frontend URL (e.g., https://your-app.netlify.app)."
logger.warning(
"FRONTEND_URL environment variable is MISSING in production. "
"Defaulting to wildcard '*' for CORS to allow startup. "
"PLEASE SET THIS IN RENDER DASHBOARD."
)
frontend_url = "*" # Allow all origins temporarily to fix deployment
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0: Security: Wildcard CORS with credentials in production. Replacing raise ValueError with a fallback to "*" removes a critical security guardrail. Because allow_credentials=True is set on the CORS middleware (line 156), Starlette will reflect the requesting Origin header back instead of sending *, meaning any website can make authenticated requests to this API. The previous behavior of refusing to start without a valid FRONTEND_URL was the correct, secure default. "Temporary" workarounds like this tend to become permanent. Restore the hard failure, or at minimum set allow_credentials=False when using a wildcard origin.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/main.py, line 130:

<comment>**Security: Wildcard CORS with credentials in production.** Replacing `raise ValueError` with a fallback to `"*"` removes a critical security guardrail. Because `allow_credentials=True` is set on the CORS middleware (line 156), Starlette will reflect the requesting `Origin` header back instead of sending `*`, meaning **any** website can make authenticated requests to this API. The previous behavior of refusing to start without a valid `FRONTEND_URL` was the correct, secure default. "Temporary" workarounds like this tend to become permanent. Restore the hard failure, or at minimum set `allow_credentials=False` when using a wildcard origin.</comment>

<file context>
@@ -122,18 +122,21 @@ async def lifespan(app: FastAPI):
+            "Defaulting to wildcard '*' for CORS to allow startup. "
+            "PLEASE SET THIS IN RENDER DASHBOARD."
         )
+        frontend_url = "*" # Allow all origins temporarily to fix deployment
     else:
         logger.warning("FRONTEND_URL not set. Defaulting to http://localhost:5173 for development.")
</file context>
Suggested change
frontend_url = "*" # Allow all origins temporarily to fix deployment
raise ValueError(
"FRONTEND_URL environment variable is required for security in production. "
"Set it to your frontend URL (e.g., https://your-app.netlify.app)."
)
Fix with Cubic

else:
logger.warning("FRONTEND_URL not set. Defaulting to http://localhost:5173 for development.")
frontend_url = "http://localhost:5173"

if not (frontend_url.startswith("http://") or frontend_url.startswith("https://")):
raise ValueError(
f"FRONTEND_URL must be a valid HTTP/HTTPS URL. Got: {frontend_url}"
if frontend_url != "*" and not (frontend_url.startswith("http://") or frontend_url.startswith("https://")):
logger.warning(
f"FRONTEND_URL format invalid: {frontend_url}. Expected HTTP/HTTPS URL. Using '*' as fallback."
)
frontend_url = "*"
Comment on lines +125 to +139
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Security: Wildcard CORS origin with allow_credentials=True is dangerous in production.

When FRONTEND_URL is missing or invalid in production, frontend_url falls back to "*". Combined with allow_credentials=True on Line 156, Starlette's CORSMiddleware will reflect any requesting Origin header back as Access-Control-Allow-Origin while also sending Access-Control-Allow-Credentials: true. This means any website can make fully credentialed (cookie/auth-bearing) cross-origin requests to your API — a textbook CSRF/credential-theft vector.

A safer fallback would be to either refuse to start or use a known safe origin. If you truly need a permissive deployment escape hatch, at minimum disable credentials when using a wildcard:

Proposed fix
 if not frontend_url:
     if is_production:
         logger.warning(
             "FRONTEND_URL environment variable is MISSING in production. "
-            "Defaulting to wildcard '*' for CORS to allow startup. "
+            "Defaulting to wildcard '*' for CORS to allow startup (credentials will be DISABLED). "
             "PLEASE SET THIS IN RENDER DASHBOARD."
         )
         frontend_url = "*"
     else:
         logger.warning("FRONTEND_URL not set. Defaulting to http://localhost:5173 for development.")
         frontend_url = "http://localhost:5173"
 
 if frontend_url != "*" and not (frontend_url.startswith("http://") or frontend_url.startswith("https://")):
     logger.warning(
         f"FRONTEND_URL format invalid: {frontend_url}. Expected HTTP/HTTPS URL. Using '*' as fallback."
     )
     frontend_url = "*"

Then at the middleware registration:

+allow_creds = frontend_url != "*"
+
 app.add_middleware(
     CORSMiddleware,
     allow_origins=allowed_origins,
-    allow_credentials=True,
+    allow_credentials=allow_creds,
     allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
     allow_headers=["*"],
 )
🤖 Prompt for AI Agents
In `@backend/main.py` around lines 125 - 139, The current fallback sets
frontend_url="*" which, combined with CORSMiddleware(...,
allow_credentials=True), will reflect origins and allow credentialed requests;
change the logic so that when frontend_url == "*" you do NOT enable credentials:
either (1) in production refuse to start (raise/exit) if FRONTEND_URL is
missing/invalid, or (2) if you must fallback to "*", ensure the CORSMiddleware
registration uses allow_credentials=False when frontend_url == "*"; update the
code paths around the frontend_url assignment and the CORSMiddleware setup
(referencing frontend_url and CORSMiddleware/allow_credentials) so credentials
are disabled for wildcard origins or the process aborts in prod.

Comment on lines +135 to +139
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Minor: Logging a raw, potentially-sensitive env var value.

Line 137 logs the full value of FRONTEND_URL when it fails validation. If it was accidentally set to a secret or contains tokens in a query string, it would end up in logs.

Consider masking or truncating the value:

-    logger.warning(
-        f"FRONTEND_URL format invalid: {frontend_url}. Expected HTTP/HTTPS URL. Using '*' as fallback."
-    )
+    logger.warning(
+        "FRONTEND_URL format invalid (does not start with http:// or https://). Using '*' as fallback."
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if frontend_url != "*" and not (frontend_url.startswith("http://") or frontend_url.startswith("https://")):
logger.warning(
f"FRONTEND_URL format invalid: {frontend_url}. Expected HTTP/HTTPS URL. Using '*' as fallback."
)
frontend_url = "*"
if frontend_url != "*" and not (frontend_url.startswith("http://") or frontend_url.startswith("https://")):
logger.warning(
"FRONTEND_URL format invalid (does not start with http:// or https://). Using '*' as fallback."
)
frontend_url = "*"
🤖 Prompt for AI Agents
In `@backend/main.py` around lines 135 - 139, The warning logs the raw
frontend_url when validation fails; change the logging to avoid exposing secrets
by masking or truncating the value before logging (e.g., create a safe string
via a helper or inline logic that replaces query/fragment and shows only
host/first N chars), then call logger.warning with the masked/truncated value
instead of frontend_url; update the code around frontend_url and the
logger.warning call so the fallback behavior (setting frontend_url = "*")
remains unchanged.

Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Security: Invalid URL silently falls back to wildcard CORS. If an operator sets FRONTEND_URL to a malformed value (e.g., a typo like htps://myapp.com), the app silently falls back to "*" instead of failing fast. This is worse than the missing-variable case because the operator believes CORS is configured. A ValueError (the old behavior) is the correct response to misconfiguration — fail loudly rather than silently becoming insecure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/main.py, line 139:

<comment>**Security: Invalid URL silently falls back to wildcard CORS.** If an operator sets `FRONTEND_URL` to a malformed value (e.g., a typo like `htps://myapp.com`), the app silently falls back to `"*"` instead of failing fast. This is worse than the missing-variable case because the operator believes CORS is configured. A `ValueError` (the old behavior) is the correct response to misconfiguration — fail loudly rather than silently becoming insecure.</comment>

<file context>
@@ -122,18 +122,21 @@ async def lifespan(app: FastAPI):
+    logger.warning(
+        f"FRONTEND_URL format invalid: {frontend_url}. Expected HTTP/HTTPS URL. Using '*' as fallback."
     )
+    frontend_url = "*"
 
 allowed_origins = [frontend_url]
</file context>
Suggested change
frontend_url = "*"
raise ValueError(
f"FRONTEND_URL must be a valid HTTP/HTTPS URL. Got: {frontend_url}"
)
Fix with Cubic


allowed_origins = [frontend_url]

Expand Down
1 change: 0 additions & 1 deletion backend/requirements-render.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ psycopg2-binary
async-lru
huggingface-hub
httpx
python-magic
pywebpush
Pillow
firebase-functions
Expand Down
41 changes: 23 additions & 18 deletions backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
import shutil
import logging
import io
import magic
try:
import magic
except ImportError:
magic = None
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Security: MIME type validation via libmagic is silently skipped when python-magic is not installed. This degrades upload security without any log warning to alert operators. At minimum, log a warning at import time so deployments without libmagic are clearly flagged.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils.py, line 14:

<comment>Security: MIME type validation via `libmagic` is silently skipped when `python-magic` is not installed. This degrades upload security without any log warning to alert operators. At minimum, log a warning at import time so deployments without `libmagic` are clearly flagged.</comment>

<file context>
@@ -8,7 +8,10 @@
+try:
+    import magic
+except ImportError:
+    magic = None
 from typing import Optional
 
</file context>
Suggested change
magic = None
magic = None
logging.getLogger(__name__).warning(
"python-magic is not installed. MIME type validation for uploads will be skipped. "
"Install python-magic for enhanced file upload security."
)
Fix with Cubic

from typing import Optional

from backend.cache import user_upload_cache
Expand Down Expand Up @@ -73,17 +76,18 @@ def _validate_uploaded_file_sync(file: UploadFile) -> Optional[Image.Image]:

# Check MIME type from content using python-magic
try:
# Read first 1024 bytes for MIME detection
file_content = file.file.read(1024)
file.file.seek(0) # Reset file pointer
if magic:
# Read first 1024 bytes for MIME detection
file_content = file.file.read(1024)
file.file.seek(0) # Reset file pointer

detected_mime = magic.from_buffer(file_content, mime=True)
detected_mime = magic.from_buffer(file_content, mime=True)

if detected_mime not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
)
if detected_mime not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
)

# Additional content validation: Try to open with PIL to ensure it's a valid image
try:
Expand Down Expand Up @@ -158,15 +162,16 @@ def process_uploaded_image_sync(file: UploadFile) -> tuple[Image.Image, bytes]:

# Check MIME type
try:
file_content = file.file.read(1024)
file.file.seek(0)
detected_mime = magic.from_buffer(file_content, mime=True)
if magic:
file_content = file.file.read(1024)
file.file.seek(0)
detected_mime = magic.from_buffer(file_content, mime=True)

if detected_mime not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
)
if detected_mime not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
)

try:
img = Image.open(file.file)
Expand Down
6 changes: 3 additions & 3 deletions start-backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def validate_environment():
print(f" - {var}")
print("\nPlease set these variables or create a .env file.")
print("See backend/.env.example for reference.")
return False
# We don't return False here to allow the app to start and serve health check
print("⚠️ Proceeding with missing variables (some features may be broken)")
Comment on lines +32 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Misleading success message when required variables are missing.

After printing "⚠️ Proceeding with missing variables", Line 51 unconditionally prints "✅ Environment validation passed", which is contradictory and confusing in logs.

Proposed fix
+    has_missing = False
     if missing_vars:
         print("❌ Missing required environment variables:")
         for var in missing_vars:
             print(f"   - {var}")
         print("\nPlease set these variables or create a .env file.")
         print("See backend/.env.example for reference.")
         print("⚠️  Proceeding with missing variables (some features may be broken)")
+        has_missing = True
 
     # ... optional variable defaults ...
 
-    print("✅ Environment validation passed")
+    if not has_missing:
+        print("✅ Environment validation passed")
+    else:
+        print("⚠️  Environment validation completed with warnings")
     return True

Also applies to: 51-51

🤖 Prompt for AI Agents
In `@start-backend.py` around lines 32 - 33, The code prints a warning "⚠️ 
Proceeding with missing variables" but then always prints "✅ Environment
validation passed", which is contradictory; update the environment validation
flow so that the success message ("✅ Environment validation passed") is only
emitted when no required variables are missing — e.g., introduce or use a
boolean like has_missing_vars / missing_vars list within the validation logic
and conditionally print the success message only when that flag/list is empty,
otherwise keep or enhance the warning message (the two prints with the exact
strings "⚠️  Proceeding with missing variables (some features may be broken)"
and "✅ Environment validation passed" identify the locations to change).

Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When required environment variables are missing, the function now falls through to unconditionally print "✅ Environment validation passed", contradicting the warning printed just above. The validation did not pass — it was merely downgraded to non-fatal. Add an else or an early return-after-warning so the success message is only printed when all required variables are actually present.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At start-backend.py, line 33:

<comment>When required environment variables are missing, the function now falls through to unconditionally print `"✅ Environment validation passed"`, contradicting the warning printed just above. The validation did *not* pass — it was merely downgraded to non-fatal. Add an `else` or an early return-after-warning so the success message is only printed when all required variables are actually present.</comment>

<file context>
@@ -29,7 +29,8 @@ def validate_environment():
         print("See backend/.env.example for reference.")
-        return False
+        # We don't return False here to allow the app to start and serve health check
+        print("⚠️  Proceeding with missing variables (some features may be broken)")
 
     # Set defaults for optional variables
</file context>
Fix with Cubic


# Set defaults for optional variables
if not os.getenv("DATABASE_URL"):
Expand Down Expand Up @@ -60,8 +61,7 @@ def main():
"""Main startup function"""
print("🚀 Starting VishwaGuru Backend")

if not validate_environment():
sys.exit(1)
validate_environment()

create_data_directory()

Expand Down