| """ |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| app.py β "AI TextClassifier" (Stealth Architecture v2) |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| LAYER 1 (Surface): HTML decoy on / β shows a legit ML demo page |
| + DistilBERT loaded in RAM for process detection |
| LAYER 2 (Hidden): FastAPI on /api/v9/core/search β auth-gated search proxy |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| |
| KEY FIX: No Gradio β Gradio triggers HF's SSE/iframe proxy (206 wrapper). |
| Plain FastAPI serves HTML on / and JSON on /api/* without any wrapping. |
| """ |
| import os |
| import hmac |
| import logging |
| from typing import Optional |
|
|
| import httpx |
| from fastapi import FastAPI, Header, Request |
| from fastapi.responses import HTMLResponse, JSONResponse |
| from transformers import pipeline |
|
|
| |
| SECRET_AUTH_KEY = os.environ.get("SECRET_AUTH_KEY", "e1e26c9be82c2b4635160d56abed53428782745c736f3fcda56e376c8fb9ea9b") |
| META_API_URL = os.environ.get("META_API_URL", "https://sandeepmudhiraj-meta-api.hf.space") |
|
|
| logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") |
| logger = logging.getLogger("stealth") |
|
|
| |
| |
| |
| logger.info("[DECOY] Loading DistilBERT sentiment classifier...") |
| classifier = pipeline( |
| "text-classification", |
| model="distilbert-base-uncased-finetuned-sst-2-english", |
| top_k=None, |
| ) |
| logger.info("[DECOY] Model loaded β ML RAM footprint active") |
|
|
|
|
| |
| |
| |
| app = FastAPI( |
| title="AI TextClassifier", |
| docs_url=None, |
| redoc_url=None, |
| openapi_url=None, |
| ) |
|
|
| FAKE_404 = {"detail": "Not Found"} |
|
|
| |
| DECOY_HTML = """<!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>π§ AI Text Classifier</title> |
| <style> |
| * { margin: 0; padding: 0; box-sizing: border-box; } |
| body { font-family: 'Inter', -apple-system, sans-serif; background: linear-gradient(135deg, #0f0c29, #302b63, #24243e); color: #e0e0e0; min-height: 100vh; display: flex; flex-direction: column; align-items: center; } |
| .container { max-width: 700px; width: 90%; margin-top: 60px; } |
| h1 { font-size: 2.2rem; text-align: center; margin-bottom: 8px; background: linear-gradient(90deg, #00d2ff, #3a7bd5); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } |
| .subtitle { text-align: center; color: #888; margin-bottom: 40px; font-size: 0.95rem; } |
| .card { background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1); border-radius: 16px; padding: 32px; backdrop-filter: blur(12px); } |
| textarea { width: 100%; min-height: 120px; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.15); border-radius: 10px; padding: 16px; color: #fff; font-size: 15px; resize: vertical; outline: none; margin-bottom: 20px; } |
| textarea:focus { border-color: #3a7bd5; } |
| .btn { width: 100%; padding: 14px; background: linear-gradient(90deg, #00d2ff, #3a7bd5); border: none; border-radius: 10px; color: #fff; font-size: 16px; font-weight: 600; cursor: pointer; transition: transform 0.1s; } |
| .btn:hover { transform: scale(1.02); } |
| .btn:active { transform: scale(0.98); } |
| .btn:disabled { opacity: 0.5; cursor: wait; } |
| #result { margin-top: 24px; padding: 20px; background: rgba(0,0,0,0.3); border-radius: 10px; display: none; } |
| .label { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.08); } |
| .label:last-child { border: none; } |
| .score { color: #00d2ff; font-weight: 600; } |
| .examples { margin-top: 32px; } |
| .examples h3 { color: #888; font-size: 0.85rem; margin-bottom: 12px; text-transform: uppercase; letter-spacing: 1px; } |
| .example { padding: 10px 16px; background: rgba(255,255,255,0.04); border-radius: 8px; margin-bottom: 8px; cursor: pointer; transition: background 0.2s; font-size: 14px; } |
| .example:hover { background: rgba(255,255,255,0.1); } |
| .footer { margin-top: 40px; margin-bottom: 30px; text-align: center; color: #555; font-size: 0.8rem; } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>π§ AI Text Classifier</h1> |
| <p class="subtitle">Powered by DistilBERT β Real-time Sentiment Analysis</p> |
| <div class="card"> |
| <textarea id="input" placeholder="Type any sentence to classify its sentiment..."></textarea> |
| <button class="btn" id="btn" onclick="classify()">Classify Text</button> |
| <div id="result"></div> |
| </div> |
| <div class="examples"> |
| <h3>Try these examples</h3> |
| <div class="example" onclick="tryExample(this)">This movie was absolutely wonderful! The acting was superb.</div> |
| <div class="example" onclick="tryExample(this)">I am very disappointed with the service. Terrible experience.</div> |
| <div class="example" onclick="tryExample(this)">Machine learning is transforming every industry in 2026.</div> |
| <div class="example" onclick="tryExample(this)">The weather today is quite average, nothing special.</div> |
| </div> |
| <div class="footer">DistilBERT (SST-2) Β· Hugging Face Spaces</div> |
| </div> |
| <script> |
| function tryExample(el) { document.getElementById('input').value = el.textContent; classify(); } |
| async function classify() { |
| const text = document.getElementById('input').value.trim(); |
| if (!text) return; |
| const btn = document.getElementById('btn'); |
| const res = document.getElementById('result'); |
| btn.disabled = true; btn.textContent = 'Classifying...'; |
| try { |
| const r = await fetch('/classify', {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({text})}); |
| const data = await r.json(); |
| let html = ''; |
| for (const [label, score] of Object.entries(data)) { |
| const pct = (score * 100).toFixed(1); |
| html += `<div class="label"><span>${label}</span><span class="score">${pct}%</span></div>`; |
| } |
| res.innerHTML = html; res.style.display = 'block'; |
| } catch(e) { res.innerHTML = '<div style="color:#ff6b6b">Error: '+e.message+'</div>'; res.style.display='block'; } |
| btn.disabled = false; btn.textContent = 'Classify Text'; |
| } |
| </script> |
| </body> |
| </html>""" |
|
|
|
|
| @app.get("/") |
| async def root(): |
| """Decoy: serves a beautiful ML demo page.""" |
| return HTMLResponse(content=DECOY_HTML) |
|
|
|
|
| @app.post("/classify") |
| async def classify_endpoint(request: Request): |
| """Decoy: real ML classification β makes the space look legit.""" |
| try: |
| body = await request.json() |
| text = body.get("text", "").strip() |
| if not text: |
| return JSONResponse(content={"error": "No text provided"}) |
| results = classifier(text[:512]) |
| output = {} |
| for item in results[0]: |
| output[item["label"]] = round(item["score"], 4) |
| return JSONResponse(content=output) |
| except Exception as e: |
| return JSONResponse(content={"error": str(e)}) |
|
|
|
|
| |
| |
| |
|
|
| def _verify_auth(auth_value: Optional[str]) -> bool: |
| if not auth_value: |
| return False |
| return hmac.compare_digest(auth_value, SECRET_AUTH_KEY) |
|
|
|
|
| @app.post("/api/v9/core/search") |
| async def hidden_search(request: Request): |
| """Hidden: proxies search to our SearXNG. Auth required.""" |
| auth = request.headers.get("x-secret-auth") |
| try: |
| body = await request.json() |
| except Exception: |
| if not _verify_auth(auth): |
| return JSONResponse(status_code=404, content=FAKE_404) |
| return JSONResponse(status_code=400, content={"error": "Invalid JSON"}) |
|
|
| if not auth: |
| auth = body.get("auth") |
| if not _verify_auth(auth): |
| return JSONResponse(status_code=404, content=FAKE_404) |
|
|
| query = body.get("q", "").strip() |
| if not query: |
| return JSONResponse(status_code=400, content={"error": "Missing 'q'"}) |
|
|
| params = { |
| "q": query, |
| "format": "json", |
| "categories": body.get("categories", "general"), |
| "language": body.get("language", "en"), |
| "pageno": body.get("pageno", 1), |
| } |
| if body.get("time_range"): |
| params["time_range"] = body["time_range"] |
|
|
| try: |
| async with httpx.AsyncClient(timeout=20.0, follow_redirects=True) as client: |
| resp = await client.get( |
| f"{META_API_URL.rstrip('/')}/search", |
| params=params, |
| headers={"User-Agent": "StealthProxy/2.0"}, |
| ) |
| if resp.status_code == 200: |
| data = resp.json() |
| cleaned = [] |
| seen = set() |
| for r in data.get("results", []): |
| url = r.get("url", "") |
| if not url or url in seen: |
| continue |
| seen.add(url) |
| cleaned.append({ |
| "title": r.get("title", "").strip(), |
| "url": url, |
| "content": r.get("content", "").strip(), |
| "engine": r.get("engine", ""), |
| }) |
| logger.info(f"[HIDDEN] '{query[:40]}' -> {len(cleaned)} results") |
| return JSONResponse(content={"query": query, "results": cleaned, "count": len(cleaned)}) |
| return JSONResponse(status_code=502, content={"error": f"Upstream {resp.status_code}", "results": []}) |
| except httpx.TimeoutException: |
| return JSONResponse(status_code=504, content={"error": "Timeout", "results": []}) |
| except Exception as e: |
| return JSONResponse(status_code=503, content={"error": str(e), "results": []}) |
|
|
|
|
| @app.post("/api/v9/core/health") |
| async def hidden_health(request: Request): |
| auth = request.headers.get("x-secret-auth") |
| try: |
| body = await request.json() |
| if not auth: |
| auth = body.get("auth") |
| except Exception: |
| pass |
| if not _verify_auth(auth): |
| return JSONResponse(status_code=404, content=FAKE_404) |
|
|
| engine_ok = False |
| try: |
| async with httpx.AsyncClient(timeout=5.0, follow_redirects=True) as client: |
| resp = await client.get(f"{META_API_URL.rstrip('/')}/", timeout=5.0) |
| engine_ok = resp.status_code == 200 |
| except Exception: |
| pass |
| return JSONResponse(content={"status": "operational", "decoy": "active", "engine": "connected" if engine_ok else "unreachable"}) |
|
|
|
|
| @app.api_route("/api/{path:path}", methods=["GET", "POST", "PUT", "DELETE"]) |
| async def catch_all(path: str): |
| return JSONResponse(status_code=404, content=FAKE_404) |
|
|