This project implements multiple layers of security across both the backend (FastAPI) and frontend (Vanilla JS). Below is a full breakdown of every security measure implemented and why.
RATE_LIMIT_REQUESTS = 20 # max requests
RATE_LIMIT_WINDOW = 60 # per 60 seconds- Every
/askand/ingestrequest is checked against a per-IP sliding window - Exceeding the limit returns HTTP
429 Too Many Requests - Prevents abuse, API key exhaustion, and denial-of-service on the Groq endpoint
- Implementation: in-memory
defaultdictβ no Redis dependency for local demo
MAX_QUESTION_LENGTH = 1000 # characters
MIN_QUESTION_LENGTH = 3 # characters- Questions shorter than 3 chars or longer than 1000 chars are rejected with HTTP
400 - Prevents prompt injection via oversized payloads
- Matches the
maxlength="1000"attribute on the frontend textarea (belt-and-suspenders)
def sanitize_text(text: str) -> str:
return re.sub(r'[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]', '', text)- Strips null bytes and dangerous control characters before the text reaches the LLM
- Preserves legitimate
\nand\tcharacters - Prevents null byte injection and control character exploits
Applied to every response via SecurityHeadersMiddleware:
| Header | Value | Purpose |
|---|---|---|
X-Frame-Options |
DENY |
Prevents clickjacking via iframes |
X-Content-Type-Options |
nosniff |
Prevents MIME-type sniffing |
X-XSS-Protection |
1; mode=block |
Legacy XSS filter for older browsers |
Referrer-Policy |
no-referrer |
No referrer data sent on outbound requests |
Permissions-Policy |
geolocation=(), camera=(), microphone=() |
Blocks unnecessary browser APIs |
allow_origins=["http://localhost:8000", "http://127.0.0.1:8000"]- Changed from
allow_origins=["*"]to localhost-only - Prevents cross-origin requests from external domains
- Only same-origin frontend can call the API
TrustedHostMiddleware(allowed_hosts=["localhost", "127.0.0.1", ...])- Rejects requests with unexpected
Hostheaders - Prevents host header injection attacks
SHUTDOWN_TOKEN = secrets.token_hex(16)/shutdownendpoint requires a cryptographically random token generated at startup- Token is printed to server console β only someone with local server access can shut it down
- Uses
secrets.compare_digest()to prevent timing attacks - Prevents accidental or malicious remote server termination
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self';
style-src 'self' https://fonts.googleapis.com;
font-src 'self' https://fonts.gstatic.com;
connect-src 'self' http://localhost:8000;
img-src 'self' data:;
frame-ancestors 'none';" />- Blocks all inline scripts β prevents XSS via injected
<script>tags - Only allows connections to
localhost:8000β no data exfiltration - Blocks the page from being embedded in iframes anywhere (
frame-ancestors 'none') - Allows only Google Fonts CDN for external resources
function escapeHtml(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'")
.replace(/\//g, "/");
}- Applied to all user input before rendering in the DOM
- Applied to all API responses (AI answers, source titles, previews)
- Escapes 6 characters including
'and/which many implementations miss - Prevents stored and reflected XSS from both user queries and LLM responses
const RATE_LIMIT = 3; // max queries
const RATE_WINDOW = 10000; // per 10 seconds- Client-side sliding window prevents rapid-fire requests before they hit the backend
- Shows a user-facing error message with countdown:
"Wait Xs before next query" - Complements (does not replace) backend rate limiting
- HTML:
maxlength="1000"attribute on textarea - JS:
sanitizeInput()hard-slices at 1000 chars as fallback - Visual: character counter turns amber at 700, red at 900
- Strips control characters client-side before sending to API
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 30000);- All API requests abort after 30 seconds
- Prevents the UI from hanging indefinitely on slow responses
- Shows user-friendly timeout message
<meta http-equiv="X-XSS-Protection" content="1; mode=block" />
<meta http-equiv="X-Content-Type-Options" content="nosniff" />
<meta http-equiv="Referrer-Policy" content="no-referrer" />
<meta name="robots" content="noindex, nofollow" />noindex, nofollowβ prevents local demo from being indexed by search engines- Redundant with backend headers for defense-in-depth
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BROWSER β
β CSP meta tag β blocks inline scripts & iframes β
β escapeHtml() β all DOM insertions sanitized β
β Frontend rate limit β 3 req / 10s β
β Input maxlength + sanitization β
β 30s request timeout β
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β HTTPS / localhost
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β FASTAPI SERVER β
β SecurityHeadersMiddleware β every response β
β TrustedHostMiddleware β host header validation β
β CORS β localhost only β
β Rate limiting β 20 req / 60s per IP β
β Input validation β 3-1000 chars β
β Input sanitization β control char stripping β
β Shutdown token β secrets.token_hex(16) β
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β GROQ API β
β API key stored in .env (never in code) β
β .env in .gitignore (never committed) β
β .env.example provided for setup guidance β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
GROQ_API_KEYis loaded exclusively from.envviapython-dotenv.envis listed in.gitignoreβ never committed to version control.env.exampleis provided showing required keys without values- No API keys are hardcoded anywhere in the codebase
| Limitation | Reason | Production Fix |
|---|---|---|
| In-memory rate limiting | No Redis dependency for local demo | Replace with slowapi + Redis |
| No HTTPS | Local execution only | Nginx reverse proxy + Let's Encrypt |
| No authentication | Single-user local tool | JWT tokens + user sessions |
| Shutdown token in console | Demo convenience | Environment variable injection |
This is a local demo project built for i2e Hireathon 2026. It is not intended for public deployment.
If you find a security issue for educational purposes, feel free to open an issue or contact:
Somala Ajay β jaydeveloper010@gmail.com
Security implementation by Somala Ajay β i2e Hireathon 2026