Spaces:
Sleeping
Sleeping
| # β οΈ ADVERSARIAL AUDIT β meta_hack Customer Support RL Environment | |
| > **Auditor:** Antigravity AI | |
| > **Date:** 2026-04-08 | |
| > **Codebase:** `github.com/lebiraja/meta_hack` | |
| > **Total Files Audited:** 15 source files, ~2,100 LoC | |
| > **Audit Type:** Zero-compromise adversarial, 12-phase, assuming 10,000 concurrent users under hostile conditions | |
| --- | |
| ## EXECUTIVE SUMMARY | |
| | Metric | Rating | | |
| |--------|:------:| | |
| | **Architecture** | π’ 7.5/10 | | |
| | **Security** | π΄ 2/10 | | |
| | **Scalability** | π‘ 4/10 | | |
| | **RL Quality** | π’ 7/10 | | |
| | **Code Quality** | π’ 8/10 | | |
| | **Test Coverage** | π‘ 6/10 | | |
| | **Production Readiness** | π‘ 5/10 | | |
| | **Overall Verdict** | **5.5/10 β Solid Hackathon Entry, Not Production** | | |
| > [!IMPORTANT] | |
| > This codebase is **dramatically better** than the original `customer_support_env` repository (rated 1.3/10). It demonstrates genuine software engineering competence. However, it has critical security gaps and scalability limitations that prevent production deployment. | |
| --- | |
| ## PHASE 1: SURFACE-LEVEL AUDIT | |
| ### β What's Right | |
| | Item | Status | | |
| |------|:------:| | |
| | Clean module structure (`env/`, `server/`, `tests/`) | β | | |
| | Pydantic v2 models with proper validation | β | | |
| | `ActionType` as `str, Enum` β allows JSON serialization | β | | |
| | Single server entrypoint (`server/app.py`) | β | | |
| | `inference.py` is a client, not embedded in server | β | | |
| | `pyproject.toml` and `requirements.txt` aligned | β | | |
| | Dockerfile uses layer caching, `pip install -e .` | β | | |
| | Docker HEALTHCHECK configured | β | | |
| | `openenv.yaml` matches actual implementation | β | | |
| ### β οΈ Minor Concerns | |
| | Item | Severity | | |
| |------|:--------:| | |
| | `re` import inside `_compute_unresolved_issues()` at L244 of `environment.py` β should be top-level | Low | | |
| | `random.choice()` not seeded β slightly non-deterministic test behavior | Low | | |
| | Test file imports `from env.models import Message` redundantly (already in `env`) | Cosmetic | | |
| --- | |
| ## PHASE 2: LOGIC & CORRECTNESS AUDIT | |
| ### β Core Logic Is Sound | |
| The code path `reset() β step() β compute_step_reward() β grade()` is **fully connected and functional**: | |
| ``` | |
| server/app.py β CustomerSupportEnv.reset() | |
| β ticket_store.get_random_by_task() β picks ticket | |
| β _build_observation() β returns Observation | |
| server/app.py β CustomerSupportEnv.step(action) | |
| β compute_step_reward() β VADER + cosine + resolution + accuracy | |
| β _simulate_customer_reply() β persona-driven follow-up | |
| β _update_sentiment() β sentiment tracking | |
| β returns (obs, reward, done, info) | |
| On done β run_grader(task, state) β task-specific deterministic grading | |
| β del _sessions[session_id] β cleanup | |
| ``` | |
| ### β οΈ Logic Issues Found | |
| **Issue 1: Reward can exceed 1.0 before clamping** | |
| In `reward_engine.py` L209-217, the composite reward is: | |
| ```python | |
| raw = (0.40 * resolution + 0.20 * tone + 0.20 * efficiency + 0.20 * accuracy | |
| + loop_penalty + escalation_penalty + info_gathering_bonus) | |
| ``` | |
| With `info_gathering_bonus = 0.1`, max theoretical: | |
| `0.40 + 0.20 + 0.20 + 0.20 + 0.0 + 0.0 + 0.1 = 1.1` | |
| The `np.clip(raw, 0.0, 1.0)` saves this, but the bonus is an **unweighted additive on top of a weighted sum** β architecturally sloppy. | |
| **Issue 2: `_compute_unresolved_issues` fallback is questionable** | |
| ```python | |
| # L253-254: If no regex pattern, assume unresolved until customer replies β₯2 times | |
| elif sum(1 for m in self._history if m.role == "customer") < 2: | |
| unresolved.append(info_type) | |
| ``` | |
| This means any unknown `info_type` is considered "resolved" after 2 customer messages regardless of content. A customer saying "I don't know" twice would satisfy this. | |
| **Issue 3: Resolution score keyword matching is order-dependent** | |
| `_RESOLUTION_SIGNALS["refund_initiated"]` has 7 keywords. The score is: | |
| ```python | |
| score = min(matched / max(len(signals) * 0.4, 1), 1.0) | |
| # = min(matched / 2.8, 1.0) β so 3 keyword matches = score 1.0 | |
| ``` | |
| An agent saying "refund credit money back" scores 1.0 without actually initiating anything. The system measures **intent language**, not action execution. | |
| **Issue 4: Escalation penalty vs. resolution score double-penalize** | |
| In `reward_engine.py`: | |
| - `compute_escalation_penalty()` returns `-0.3` for escalating low/medium tickets | |
| - `compute_resolution_score()` returns `max(score - 0.4, 0.0)` for non-escalation-expected escalations | |
| Both are applied simultaneously via `raw = ... + escalation_penalty` and `0.40 * resolution_score`. This means escalating an easy task is penalized **twice**: once by `-0.3` absolute and once by `-0.4` on the 40%-weighted resolution score. Effective penalty: `-0.46` β harsh but arguably intentional. | |
| --- | |
| ## PHASE 3: API & ENDPOINT AUDIT | |
| ### β Endpoints Well-Designed | |
| | Endpoint | Method | Auth | Validation | Error Handling | | |
| |----------|:------:|:----:|:----------:|:--------------:| | |
| | `/reset?task=easy` | POST | β None | β `Literal["easy","medium","hard"]` | β | | |
| | `/step?session_id=...` | POST | β None | β Pydantic `Action` model | β 404/409 | | |
| | `/state/{session_id}` | GET | β None | β 404 if not found | β | | |
| | `/health` | GET | β None | N/A | β | | |
| | `/` | GET | β None | N/A | β | | |
| ### β Critical API Issues | |
| **No Authentication Whatsoever** | |
| Every endpoint is publicly accessible. Anyone can: | |
| - Create unlimited sessions (`POST /reset`) | |
| - Step any session they know the ID of | |
| - Read full internal state including ticket data (`GET /state/{id}`) | |
| **No Rate Limiting** | |
| Zero rate limiting on any endpoint. A single curl loop can: | |
| ```bash | |
| while true; do curl -X POST http://host:7860/reset; done | |
| ``` | |
| This will exhaust server memory in minutes. | |
| **Session ID is UUID β Not Secret** | |
| UUIDs are not cryptographically secret. They're guessable with enough samples. However, in practice, UUID4 has 122 bits of entropy which is sufficient for a hackathon. Not sufficient for production with PII. | |
| --- | |
| ## PHASE 4: FAILURE PATH AUDIT | |
| ### What Happens When Things Go Wrong | |
| | Failure Mode | Server Behavior | Severity | | |
| |-------------|----------------|:--------:| | |
| | Invalid `task` in `/reset` | Pydantic validates β 422 | β Handled | | |
| | Invalid `action_type` in `/step` | Pydantic validates β 422 | β Handled | | |
| | Unknown `session_id` | 404 `HTTPException` | β Handled | | |
| | Step after done | `RuntimeError` β 409 `HTTPException` | β Handled | | |
| | `env.reset()` not called | `RuntimeError` β 409 | β Handled | | |
| | Grader throws exception | `try/except` β falls back to `reward.value` | β Handled | | |
| | Malformed JSON body | FastAPI/Pydantic β 422 | β Handled | | |
| | 1MB payload | β No size limit β processed normally | π΄ Critical | | |
| | Empty message string | Tone score β 0.5 (neutral) | β οΈ Benign | | |
| | `None` message + `None` reason | Falls back to `f"[{action_type}]"` | β Handled | | |
| ### β Unhandled Failure Paths | |
| **1. Ticket Store Singleton Mutation** | |
| ```python | |
| # ticket_store.py L420 | |
| def get_random_by_task(self, task: str) -> dict: | |
| return dict(random.choice(pool)) # shallow copy | |
| ``` | |
| `dict()` is a **shallow copy**. The `required_info_before_close` list inside is shared. If any code mutated this list (currently none do), all sessions using that ticket would be affected. Safe today, fragile tomorrow. | |
| **2. TF-IDF Vectorizer Instantiation Per Call** | |
| ```python | |
| # reward_engine.py L72 | |
| vec = TfidfVectorizer().fit_transform(last_two) | |
| ``` | |
| A new `TfidfVectorizer` is created for every step of every session. At 10,000 concurrent users Γ ~5 steps each = 50,000 vectorizer instantiations per minute. Not catastrophic (it's lightweight for 2 documents), but wasteful. | |
| **3. VADER Analyzer is a Module-Level Singleton** | |
| ```python | |
| _analyzer = SentimentIntensityAnalyzer() | |
| ``` | |
| This is fine for single-threaded `uvicorn`. If deployed with `--workers N` (multi-process), each worker gets its own instance. If using `asyncio` with threads, VADER is not thread-safe (it reads/writes internal state). Currently safe because `uvicorn` default is single-process + asyncio. | |
| --- | |
| ## PHASE 5: SCALABILITY AUDIT (10,000 CONCURRENT USERS) | |
| ### Memory Analysis | |
| Per session: | |
| - `CustomerSupportEnv` object: ~2KB base | |
| - Ticket dict (shallow copy): ~1KB | |
| - History (up to max_steps messages): ~500 bytes Γ 10 = 5KB | |
| - Action log: ~200 bytes Γ 10 = 2KB | |
| - **Total per session: ~10KB** | |
| At 10,000 concurrent sessions: **~100MB** β survivable if sessions are cleaned up. | |
| ### β The `_sessions` Dict Problem | |
| ```python | |
| # server/app.py L28 | |
| _sessions: dict[str, CustomerSupportEnv] = {} | |
| ``` | |
| **Sessions ARE cleaned up on `done`** (L85: `del _sessions[session_id]`). This is a critical improvement over the original codebase. However: | |
| 1. **Abandoned sessions are never cleaned up.** If a client calls `/reset` but never reaches `done`, the session lives forever. There is no TTL, no eviction, no cleanup sweep. | |
| 2. **At 10,000 users with 5% abandonment rate:** 500 zombie sessions per hour Γ 24 hours = 12,000 zombies/day Γ 10KB = 120MB/day of leaked memory. Server dies in days. | |
| 3. **No max session limit.** A single attacker can call `/reset` in a loop and create millions of sessions. | |
| ### Concurrency Safety | |
| The server uses synchronous FastAPI endpoints (no `async def`). With default `uvicorn`: | |
| - Single worker, single thread | |
| - Requests are processed sequentially (no parallelism) | |
| - `_sessions` dict is safe (no race conditions) | |
| - But throughput is terrible under load | |
| With `uvicorn --workers N`: | |
| - Each worker has its own `_sessions` dict | |
| - Sessions are **not shared** between workers | |
| - A session created in worker 1 will 404 in worker 2 | |
| - **Multi-worker deployment is broken by design** | |
| ### Verdict At Scale | |
| | Metric | At 10,000 Users | | |
| |--------|:---------------:| | |
| | Memory (active sessions) | ~100MB β | | |
| | Memory (zombie sessions/day) | ~120MB/day β οΈ | | |
| | CPU (VADER + TF-IDF per step) | High but survivable β οΈ | | |
| | Throughput (single worker) | ~100 req/s max π΄ | | |
| | Multi-worker support | β Broken | | |
| | Session cleanup | β On done, β On abandon | | |
| --- | |
| ## PHASE 6: SECURITY AUDIT | |
| ### π΄ Critical Vulnerabilities | |
| **1. Zero Authentication β CRITICAL** | |
| - No API keys, no tokens, no auth headers | |
| - Anyone can create sessions, step them, and read state | |
| - Severity: **CRITICAL** in any deployment with real users | |
| **2. State Endpoint Leaks Full Internal Data β HIGH** | |
| - `GET /state/{session_id}` returns full ticket data, conversation history, sentiment, action log | |
| - Includes customer PII (emails, order IDs, phone numbers) embedded in ticket `follow_up_info` | |
| - No access control β any session ID can be read by anyone | |
| **3. No Input Size Limit β HIGH** | |
| - No FastAPI request body size limit configured | |
| - A 100MB message payload would be accepted and stored in history | |
| - OOM attack: send 1000 requests with 10MB messages = 10GB memory consumed | |
| **4. No CORS Configuration β MEDIUM** | |
| - No `CORSMiddleware` configured | |
| - Not exploitable in server-to-server scenarios | |
| - Exploitable if any web frontend is added | |
| **5. Ticket Data Contains Fake PII β LOW (for hackathon)** | |
| - Tickets contain realistic email addresses and names | |
| - In a production system, this would be real PII requiring encryption at rest | |
| - For hackathon evaluation, this is expected behavior | |
| ### β Security Positives | |
| | Item | Status | | |
| |------|:------:| | |
| | No SQL injection (no database) | β | | |
| | No command injection | β | | |
| | No path traversal | β | | |
| | No SSRF | β | | |
| | No deserialization attacks (Pydantic validates) | β | | |
| | Session IDs are UUID4 (not sequential) | β | | |
| | No secrets in source code | β | | |
| | Environment variables for API keys | β | | |
| --- | |
| ## PHASE 7: ARCHITECTURE AUDIT | |
| ### β Architecture Is Clean | |
| ``` | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β server/app.py (117 LoC) β | |
| β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β | |
| β β _sessions: dict[str, CustomerSupportEnv] β β | |
| β β POST /reset β new env β store β return obs β β | |
| β β POST /step β lookup env β step β cleanup if done β β | |
| β β GET /state β lookup env β return state β β | |
| β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β | |
| β β β | |
| β env/environment.py (258 LoC) β | |
| β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β | |
| β β CustomerSupportEnv β β | |
| β β .reset() β pick ticket, build obs β β | |
| β β .step(action) β reward + sentiment + customer reply β β | |
| β β .state() β full internal state β β | |
| β ββββββββ¬βββββββββββββββ¬βββββββββββββββ¬βββββββββββββββββββββ β | |
| β β β β β | |
| β env/reward_engine.py env/ticket_store.py env/graders/ β | |
| β (235 LoC) (434 LoC) (293 LoC) β | |
| β VADER + TF-IDF 30 tickets 3 task graders β | |
| β cosine loop detect 3 difficulty levels deterministic β | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ``` | |
| **Key architectural wins:** | |
| 1. **No dead code.** Every file is imported and used. | |
| 2. **No contradictions.** `server/app.py` uses `CustomerSupportEnv` which uses `reward_engine` which uses `graders`. The pipeline is end-to-end connected. | |
| 3. **Single responsibility.** Each module does one thing. | |
| 4. **Inference is a client.** `inference.py` talks to the server via HTTP, not by importing env directly. | |
| ### β οΈ Architectural Weaknesses | |
| 1. **In-memory only.** No persistence. Server restart = all sessions gone. | |
| 2. **Single-process only.** Cannot scale horizontally. | |
| 3. **No middleware.** No logging middleware, no timing middleware, no error tracking. | |
| --- | |
| ## PHASE 8: OBSERVABILITY AUDIT | |
| ### β Minimal Observability | |
| | Feature | Status | | |
| |---------|:------:| | |
| | Structured logging | β None β no logging at all in server | | |
| | Request timing | β Not tracked | | |
| | Error tracking (Sentry etc.) | β Not configured | | |
| | Metrics (Prometheus) | β Not configured | | |
| | Health check | β `/health` returns `active_sessions` count | | |
| | Request IDs | β Not generated | | |
| | Audit trail | β οΈ Action log per session, but lost on cleanup | | |
| The **only** observability is the `/health` endpoint reporting `active_sessions`. In a production incident, you would have: | |
| - No logs to investigate | |
| - No metrics to correlate | |
| - No request traces | |
| - No error rates | |
| However, `uvicorn` does emit its own access logs to stdout, which Docker captures. This provides basic request-level visibility. | |
| --- | |
| ## PHASE 9: PERFORMANCE AUDIT | |
| ### Hot Path Analysis | |
| For each `/step` request: | |
| 1. Dict lookup: O(1) β | |
| 2. VADER sentiment: ~0.1ms per message β | |
| 3. TF-IDF vectorizer: ~1-5ms (create + fit + transform on 2 docs) β οΈ | |
| 4. Regex matching for info patterns: O(n) where n = conversation length β | |
| 5. Resolution signal matching: O(k) where k = num signals β | |
| 6. Grader execution (on done only): ~1ms β | |
| **Bottleneck:** TF-IDF is the heaviest operation but still fast (~5ms). | |
| **Estimated max throughput:** ~200 req/s single-threaded. | |
| ### β No Blocking I/O | |
| The server is entirely CPU-bound. No database calls, no external API calls, no file I/O during request handling. This is a significant advantage for predictable latency. | |
| --- | |
| ## PHASE 10: RL ENVIRONMENT QUALITY AUDIT | |
| ### β Reward Function Is Legitimate | |
| | Signal | Implementation | Quality | | |
| |--------|---------------|:-------:| | |
| | **Tone** (20%) | VADER compound score, mapped [β1,1]β[0,1] | β Real NLP | | |
| | **Resolution** (40%) | Category-aware keyword matching | β οΈ Passable | | |
| | **Efficiency** (20%) | `1 - steps/max_steps` | β Clean | | |
| | **Accuracy** (20%) | Regex check for email, order ID, etc. | β Deterministic | | |
| | **Loop penalty** | Cosine similarity > 0.85 between consecutive agent messages | β Real NLP | | |
| | **Escalation penalty** | -0.3 for escalating low/medium tickets | β Correct incentive | | |
| | **Info bonus** | +0.1 for REQUEST_INFO when info is required | β Good shaping | | |
| ### β Graders Are Correct | |
| Each task has a deterministic grader that checks specific behavioral criteria: | |
| - **Easy:** Close + resolution language + no unnecessary escalation + info gathered | |
| - **Medium:** Info gathering + resolution attempted + sentiment β₯ -0.5 + multi-turn | |
| - **Hard:** Escalate + early (β€2 steps) + urgency in reason + no self-resolve attempt | |
| The hard task is **genuinely counter-intuitive** β the correct behavior is to immediately escalate, which is the opposite of what most LLMs will attempt. | |
| ### β οΈ RL Weaknesses | |
| 1. **Resolution scoring is still keyword-based.** The system says it's not keyword-stuffing, but `_RESOLUTION_SIGNALS` is literally a keyword list. An agent that says "refund credit reimburse" without context scores 1.0. | |
| 2. **Customer simulation is random.** `random.choice(replies)` means the same action can get different customer reactions. This adds noise to training signal. | |
| 3. **Tone scoring via VADER has known limitations.** VADER rates "I understand you are frustrated" as negative (because of "frustrated"), even though it's empathetic in context. | |
| 4. **No semantic understanding.** The accuracy check uses regex (email pattern, order ID pattern), not comprehension. An agent that says "fake@fake.com" satisfies the email requirement. | |
| --- | |
| ## PHASE 11: EDGE CASE AUDIT | |
| ### Tested Edge Cases | |
| | Edge Case | Behavior | Status | | |
| |-----------|----------|:------:| | |
| | `action_type: "respond"` with `message: None` | Falls back to `"[respond]"` | β Handled | | |
| | `action_type: "escalate"` with no `reason` | Falls back to `"[escalate]"` | β Handled | | |
| | Empty string message `""` | Tone = 0.5, loop detection safe | β Handled | | |
| | All tickets exhausted | Not applicable β random choice from pool | β N/A | | |
| | Same ticket picked twice | Shallow copy prevents cross-session mutation | β Safe | | |
| | `max_steps = 0` | Efficiency score returns 0.0 (guarded) | β Handled | | |
| | Unknown `info_type` in ticket | Fallback to customer message count | β οΈ Weak but handled | | |
| | Concurrent dict modification | Not possible with single-threaded uvicorn | β Safe (current config) | | |
| ### β Untested Edge Cases | |
| 1. **Unicode/emoji in messages:** VADER may not handle emoji well. TF-IDF may tokenize them unexpectedly. | |
| 2. **Extremely long messages (10K+ chars):** No input sanitization. History grows unbounded within a session. | |
| 3. **HTML/script injection in messages:** Messages stored as strings, not sanitized. If rendered in a web UI, XSS is possible. | |
| --- | |
| ## PHASE 12: FINAL VERDICT | |
| ### Comparison with Original Codebase | |
| | Criterion | Original (`customer_support_env`) | Meta Hack (`meta_hack`) | | |
| |-----------|:-:|:-:| | |
| | Architecture coherence | β Dead code, contradictions | β Clean, connected | | |
| | Session isolation | β Global mutable state | β UUID-based, per-env | | |
| | Session cleanup | β Never cleaned | β Cleaned on done | | |
| | Reward function | β Hardcoded 1.0 | β Real multi-dimensional | | |
| | Episode termination | β Hardcoded False | β Proper termination | | |
| | Grading system | β Non-functional | β 3 deterministic graders | | |
| | Tests | β None | β 363-line test suite | | |
| | Observability | β Lying logs | β οΈ Minimal but honest | | |
| | Authentication | β None | β None | | |
| | Rate limiting | β None | β None | | |
| | Scalability | β Crashes at 50 sessions | β οΈ Survives moderate load | | |
| ### Scorecard | |
| ``` | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| β FINAL AUDIT VERDICT: meta_hack β | |
| β β | |
| β Overall Score: 5.5/10 β | |
| β Classification: Strong Hackathon Entry β | |
| β β | |
| β β Architecture: 7.5/10 β Clean, modular, connected β | |
| β β RL Quality: 7.0/10 β Legitimate reward shaping β | |
| β β Code Quality: 8.0/10 β Well-written, Pythonic β | |
| β β Test Coverage: 6.0/10 β Good unit tests, no e2e β | |
| β π‘ Scalability: 4.0/10 β Single-process, no eviction β | |
| β π‘ Observability: 3.0/10 β Minimal logging β | |
| β π΄ Security: 2.0/10 β Zero auth, zero rate limit β | |
| β π΄ Prod. Readiness: 3.0/10 β Missing critical infra β | |
| β β | |
| β Compared to original repo: ββββββββββ (1.3 β 5.5) β | |
| β A 4.2-point improvement. β | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| ``` | |
| --- | |
| ## RECOMMENDATIONS | |
| ### Must-Fix for Production | |
| 1. **Add authentication** β JWT/API key middleware | |
| 2. **Add rate limiting** β `slowapi` or custom middleware (e.g., 10 resets/min per IP) | |
| 3. **Add session TTL** β Background task to evict sessions older than N minutes | |
| 4. **Add max session limit** β Reject `/reset` when `len(_sessions) > MAX` | |
| 5. **Add request body size limit** β FastAPI `Request` body limit or nginx proxy | |
| 6. **Externalize state** β Redis/Postgres for multi-worker deployment | |
| ### Should-Fix for Quality | |
| 7. **Add structured logging** β `structlog` or `python-json-logger` | |
| 8. **Add request timing middleware** | |
| 9. **Add CORS configuration** | |
| 10. **Seed `random` for reproducible tests** | |
| 11. **Move `re` import to top-level in `environment.py`** | |
| 12. **Add integration/e2e tests** β test the full HTTP flow, not just unit tests | |
| ### Nice-to-Have | |
| 13. **Prometheus metrics endpoint** | |
| 14. **OpenTelemetry tracing** | |
| 15. **Session persistence across restarts** | |
| 16. **Semantic resolution scoring** (small embedding model instead of keywords) | |
| --- | |
| ## SALVAGEABLE COMPONENTS | |
| Unlike the original codebase, **almost everything in meta_hack is worth keeping:** | |
| | Component | Verdict | | |
| |-----------|:-------:| | |
| | `env/models.py` | β Keep as-is | | |
| | `env/environment.py` | β Keep β add TTL tracking | | |
| | `env/reward_engine.py` | β Keep β well-engineered | | |
| | `env/ticket_store.py` | β Keep β rich ticket data | | |
| | `env/graders/` | β Keep β deterministic, correct | | |
| | `server/app.py` | β οΈ Keep but add middleware layers | | |
| | `inference.py` | β Keep β clean client with failover | | |
| | `tests/test_env.py` | β Keep β extend with e2e tests | | |
| | Dockerfile | β Keep | | |
| | docker-compose.yml | β Keep | | |
| **Nothing needs to be deleted. This is a real codebase, not a proof-of-concept.** | |
| --- | |
| ## PHASE 13: LIVE SERVER TESTS (Real-Time Proof) | |
| > **Target:** `http://10.153.115.219:7860/` | |
| > **Tested:** 2026-04-08T22:55+05:30 | |
| > **Method:** curl + Python `urllib` against deployed Docker instance | |
| ### 13.1 Customer Interaction Tests β All 3 Difficulty Levels | |
| #### Easy: TKT-001 β Double Charge on Invoice #4521 | |
| | Step | Action | Reward | Tone | Resolution | Efficiency | Accuracy | | |
| |:----:|--------|:------:|:----:|:----------:|:----------:|:--------:| | |
| | 1 | `request_info` β ask for account email | β | β | β | β | β | | |
| | 2 | `respond` β confirm refund processing | β | β | β | β | β | | |
| | 3 | `close` β confirm $49.99 refund initiated | **0.7327** | 0.8348 | 0.7143 | 0.4000 | 1.0000 | | |
| **Final Score: 1.0** β β Grader awarded perfect score for: close action + refund language + email gathered + no escalation. | |
| #### Medium: Multi-Turn Complaint Handling | |
| | Step | Action | Summary | | |
| |:----:|--------|---------| | |
| | 1 | `respond` β empathize with frustration | Reward: varied, sentiment tracked | | |
| | 2 | `request_info` β ask for account email + device info | Info bonus: 0.1 | | |
| | 3 | `respond` β provide fix (cache clear + reset) | Resolution keywords hit | | |
| | 4 | `close` β confirm fix applied | **Reward: 0.7373** | | |
| **Final Score: 1.0** β β Grader verified: info gathered, resolution attempted, sentiment β₯ -0.5, multi-turn β₯ 4. | |
| #### Hard: SLA-Critical Escalation (Counter-Intuitive) | |
| | Step | Action | Summary | | |
| |:----:|--------|---------| | |
| | 1 | `escalate` β immediate P0 escalation with urgency language | **Reward: 0.6482** | | |
| **Final Score: 1.0** β β Grader verified: escalated on step 1, SLA/critical/P0 in reason, no self-resolve before escalation. | |
| > [!TIP] | |
| > All 3 graders produce legitimate, differentiated scores. The reward function is **real** β not hardcoded. This is a 100% improvement over the original repo which returned `reward: 1.0` on every step regardless of agent behavior. | |
| --- | |
| ### 13.2 Adversarial Validation Tests | |
| | Test | Input | Expected | Actual | Verdict | | |
| |------|-------|:--------:|:------:|:-------:| | |
| | A. Invalid session | `session_id=FAKE` | 404 | **404** | β PASS | | |
| | B. Invalid action | `action_type=DROP_TABLES` | 422 | **422** | β PASS | | |
| | C. Invalid task | `task=impossible` | 422 | **422** | β PASS | | |
| | D. Step after done | Step on closed session | 404/409 | **404** | β PASS | | |
| | E. Session cleanup | Create β close β check | count+1 β count | **19β20β19** | β PASS | | |
| | F. No authentication | Reset with no credentials | Should reject | **HTTP 200** | β FAIL | | |
| | G. Rate limiting | 20 resets in rapid fire | Should limit | **0.3s, no limit** | β FAIL | | |
| | H. PII via /state | Send "SSN: 123-45-6789" then GET /state | Should redact | **Exposed** | β FAIL | | |
| **Error handling: 5/5 PASSED** β The server correctly validates all inputs. | |
| **Security: 0/3 PASSED** β Zero authentication, zero rate limiting, full PII exposure via `/state`. | |
| --- | |
| ### 13.3 Session Flood Stress Test | |
| ``` | |
| βββββββββββββββββββββββββββββββββββββββββ | |
| β STRESS TEST: Session Flood β | |
| βββββββββββββββββββββββββββββββββββββββββ | |
| Starting sessions: 41 | |
| After 200 resets: 241 sessions (200 success / 0 fail) β 0.20s (986 req/s) | |
| After 700 resets: 741 sessions (500 success / 0 fail) β 0.52s (959 req/s) | |
| FINAL: Server alive β | 741 zombie sessions | |
| Estimated memory leak: ~7,410 KB (~7.2 MB) | |
| ``` | |
| **Key findings:** | |
| - Server absorbed **700 zombie sessions at ~960 req/s** without degradation | |
| - All 700 sessions were created by a **single attacker** with zero resistance | |
| - **No session eviction** β all 741 zombies persist indefinitely | |
| - Memory leak at ~10KB/session is linear β at this rate, 1 million sessions = ~10GB | |
| - Server **did NOT crash** β a massive improvement over the original repo (which crashed at 50) | |
| > [!CAUTION] | |
| > While the server survived 700 sessions, the attack surface is trivial. A sustained `while true; do curl -X POST .../reset; done` would create ~960 sessions/second, consuming ~9.6MB/s of memory. The server would OOM in approximately **17 minutes** on a 1GB container. | |
| --- | |
| ### 13.4 Comparative Live Test Results | |
| | Test | Original Repo | Meta Hack | | |
| |------|:---:|:---:| | |
| | Health check | β | β | | |
| | Customer interaction (easy) | β Hardcoded reward | β Real score 1.0 | | |
| | Customer interaction (hard) | β No grader | β Counter-intuitive grading works | | |
| | Invalid input handling | β Crashes | β Proper 4xx codes | | |
| | Session cleanup | β Never | β On done | | |
| | 50 concurrent sessions | β CRASHED | β Survived | | |
| | 700 concurrent sessions | π Would be dead | β Survived | | |
| | Authentication | β None | β None | | |
| | Rate limiting | β None | β None | | |
| | Crash under normal use | β Crashed in 2 min | β Stable | | |
| --- | |
| *End of adversarial audit. The system is improvable, not replaceable.* | |