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
2 changes: 2 additions & 0 deletions .github/workflows/gemini-invoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:

- name: 'Run Gemini CLI'
id: 'run_gemini'
if: |-
${{ secrets.GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '' || vars.GCP_WIF_PROVIDER != '' }}
uses: 'google-github-actions/run-gemini-cli@v0' # ratchet:exclude
env:
TITLE: '${{ github.event.pull_request.title || github.event.issue.title }}'
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/gemini-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ jobs:
uses: 'actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8' # ratchet:actions/checkout@v5

- name: 'Run Gemini pull request review'
if: |-
${{ secrets.GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != '' || vars.GCP_WIF_PROVIDER != '' }}
Comment on lines +45 to +46
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Same secrets context issue as in gemini-invoke.yml

This if condition references secrets.GEMINI_API_KEY and secrets.GOOGLE_API_KEY, which are not available in step-level if expressions for workflow_call-triggered workflows. Apply the same workaround (e.g., a prior step that checks credentials via env and sets an output).

🧰 Tools
🪛 actionlint (1.7.11)

[error] 45-45: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)


[error] 45-45: context "secrets" is not allowed here. available contexts are "env", "github", "inputs", "job", "matrix", "needs", "runner", "steps", "strategy", "vars". see https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability for more details

(expression)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/gemini-review.yml around lines 45 - 46, The step-level if
referencing secrets.GEMINI_API_KEY and secrets.GOOGLE_API_KEY is invalid for
workflow_call-triggered workflows; add a preliminary step (e.g.,
"check-credentials" or similar) that reads those secrets into env and sets a
boolean output (like credentials_present) based on checking env.GEMINI_API_KEY,
env.GOOGLE_API_KEY, or vars.GCP_WIF_PROVIDER, then replace the current
step-level if (${{ secrets.GEMINI_API_KEY != '' || secrets.GOOGLE_API_KEY != ''
|| vars.GCP_WIF_PROVIDER != '' }}) with a check against that step output (e.g.,
if: steps.check-credentials.outputs.credentials_present == 'true') so the step
uses the computed output rather than inaccessible secrets in the if expression.

uses: 'google-github-actions/run-gemini-cli@v0' # ratchet:exclude
id: 'gemini_pr_review'
env:
Expand Down
51 changes: 51 additions & 0 deletions DEPLOY_RENDER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Deploying to Render (Free Tier)

This guide explains how to deploy the Email Intelligence Platform to Render's Free Tier (512MB RAM, 0.1 CPU).

## Optimized Architecture

To fit within the free tier limits, this deployment uses:
* **Single Service**: The Python backend serves the built React frontend as static files.
* **Lite Mode**: Heavy AI libraries (PyTorch, Transformers, Scikit-learn) are **disabled** to save memory.
* **Ephemeral Storage**: Data is stored in local JSON files which **will be lost** on restart/redeploy.

## Deployment Steps

1. **Create a New Web Service** on [Render Dashboard](https://dashboard.render.com/).
2. Connect your GitHub repository.
3. Select **"Manual Deployment"** (if prompted) or proceed to configuration.
4. Configure the service details:

* **Name**: `email-intelligence` (or your choice)
* **Region**: Any (e.g., Oregon)
* **Branch**: `main` (or your working branch)
* **Root Directory**: `.` (leave empty)
* **Runtime**: **Python 3**
* **Build Command**: `./setup_env.sh`
* **Start Command**: `uvicorn src.backend.python_backend.main:app --host 0.0.0.0 --port $PORT`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for src/main.py
echo "=== Checking src/main.py ==="
if [ -f "src/main.py" ]; then
  echo "File exists"
  echo "Content:"
  cat -n src/main.py
else
  echo "File does not exist"
fi

echo -e "\n=== Checking src/backend/python_backend/main.py ==="
if [ -f "src/backend/python_backend/main.py" ]; then
  echo "File exists"
  echo "Content:"
  cat -n src/backend/python_backend/main.py
else
  echo "File does not exist"
fi

echo -e "\n=== Searching for main.py files under src/ ==="
fd "main.py" src/ --type f 2>/dev/null || echo "No main.py files found under src/"

Repository: MasumRab/EmailIntelligence

Length of output: 50382


🏁 Script executed:

cat -n DEPLOY_RENDER.md

Repository: MasumRab/EmailIntelligence

Length of output: 2736


Update start command to reference the current entry point instead of a deprecated module

The start command on line 25 references src.backend.python_backend.main:app, which is explicitly marked deprecated and slated for removal. The current, active entry point is defined in src/main.py and uses a factory pattern. Update the start command to:

uvicorn src.main:create_app --factory --host 0.0.0.0 --port $PORT

Using the deprecated module means the deployment will break when that module is removed in a future release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DEPLOY_RENDER.md` at line 25, Update the deployment start command to use the
current entry point function create_app from src.main instead of the deprecated
src.backend.python_backend.main:app; modify the command string used for starting
Uvicorn to reference src.main:create_app and include the --factory flag along
with the existing host and port options so the app factory pattern is invoked at
runtime.


5. **Environment Variables**:
Add the following environment variables:

| Key | Value | Description |
| :--- | :--- | :--- |
| `RENDER` | `true` | Activates "Lite Mode" and triggers frontend build. |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

RENDER=true is automatically injected by the Render platform — manual entry is redundant

Render sets this value to "true" for all runtimes — code can check this value to detect whether it's running on Render. The table entry in Step 5 is therefore unnecessary and may give users the impression that Lite Mode requires manual activation. Consider updating the note to clarify that RENDER is already set automatically, so users don't need to add it. This also makes setup_env.sh's [ "$RENDER" = "true" ] check activate automatically on Render without any user action.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DEPLOY_RENDER.md` at line 32, The table entry claiming users must set
`RENDER=true` is misleading—Render injects the `RENDER` env var automatically;
update the documentation (the table row for `RENDER`) to state that `RENDER` is
provided by the Render platform and does not need to be added manually, and add
a short note referencing the `setup_env.sh` check (`[ "$RENDER" = "true" ]`) to
explain it will run automatically on Render without user action.

| `PYTHON_VERSION` | `3.11.0` | Ensures correct Python version. |
| `NODE_VERSION` | `20.0.0` | Ensures correct Node.js version for frontend build. |
| `PORT` | `10000` | (Render sets this automatically, but good to know). |

6. **Create Web Service**.

## Verification

Once deployed:
* Navigate to your Render URL (e.g., `https://email-intelligence.onrender.com`).
* The React frontend should load.
* The `/api/health` (if exists) or other API endpoints should work.
* **Note**: Local AI features will be disabled.
Comment on lines +41 to +45
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (typo): Consider rephrasing "(if exists)" to "if it exists" or "if present" for correct grammar.

A possible revision: "The /api/health endpoint, if it exists, or other API endpoints should work."

Suggested change
Once deployed:
* Navigate to your Render URL (e.g., `https://email-intelligence.onrender.com`).
* The React frontend should load.
* The `/api/health` (if exists) or other API endpoints should work.
* **Note**: Local AI features will be disabled.
Once deployed:
* Navigate to your Render URL (e.g., `https://email-intelligence.onrender.com`).
* The React frontend should load.
* The `/api/health` endpoint, if it exists, or other API endpoints should work.
* **Note**: Local AI features will be disabled.


## Troubleshooting

* **Build Failures**: Check the logs. Ensure `client/package.json` dependencies are compatible with Node 20.
* **Memory Issues**: If the app crashes with "Out of Memory", ensure `RENDER=true` is set.
* **Data Loss**: Remember that data is not persistent. For persistence, you would need to connect a managed PostgreSQL database (paid or external free tier).
51 changes: 51 additions & 0 deletions fix_path_traversal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import os

file_path = 'src/main.py'

with open(file_path, 'r') as f:
lines = f.readlines()

new_logic = """ # Catch-all for SPA routing (excluding API and UI)
@app.get("/{full_path:path}")
async def catch_all(full_path: str):
if full_path.startswith("api") or full_path.startswith("ui"):
raise HTTPException(status_code=404, detail="Not found")

# Sanitize and validate path to prevent directory traversal
clean_path = os.path.normpath(full_path).lstrip('/')
file_path = os.path.join(static_dir, clean_path)

# Ensure file_path is within static_dir
if not os.path.commonpath([file_path, static_dir]) == static_dir:
raise HTTPException(status_code=403, detail="Access denied")

# Check if file exists in static dir
if os.path.exists(file_path) and os.path.isfile(file_path):
return FileResponse(file_path)

# Otherwise return index.html
return FileResponse(os.path.join(static_dir, "index.html"))
"""

# Find the catch_all function block to replace
start_idx = -1
end_idx = -1

for i, line in enumerate(lines):
if '@app.get("/{full_path:path}")' in line:
start_idx = i
if start_idx != -1 and 'return FileResponse(os.path.join(static_dir, "index.html"))' in line:
end_idx = i
break

if start_idx != -1 and end_idx != -1:
# Remove old block
del lines[start_idx:end_idx+1]
# Insert new block
lines.insert(start_idx, new_logic)

with open(file_path, 'w') as f:
f.writelines(lines)
print("Fixed path traversal in src/main.py")
else:
print("Could not find catch_all block")
Loading
Loading