Skip to content

Commit 10a34fd

Browse files
author
ChromeOS Developer
committed
Update documentation and apply security hardening
Documentation Updates: - README.md: Enhanced feature descriptions and setup instructions - ARCHITECTURE.md: Refined system design diagrams - API.md: Added comprehensive endpoint examples - DEMO_MODE.md: Expanded demo scenario walkthroughs - EVALUATION.md: Improved testing procedures - MODEL_USAGE.md: Clarified Voxtral/Mistral integration details - THREAT_MODEL.md: Updated security analysis and mitigations - DEPLOY.md: Refined Render deployment instructions - QUICKSTART.md: Streamlined getting started guide Hardening Improvements: - Added input validation across all endpoints - Enhanced error handling for edge cases - Improved resource cleanup in WebSocket handlers - Fixed race conditions in state management - Strengthened API key and config security
1 parent 7741d16 commit 10a34fd

13 files changed

Lines changed: 382 additions & 123 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ See [`docs/API.md`](docs/API.md) for the full API reference — request/response
371371
| Artifact | Description |
372372
|----------|-------------|
373373
| [`backend/tests/`](backend/tests/) | 172 unit/integration tests (scoring, formatting, streaming) |
374+
| [`scripts/run_evaluation.py`](scripts/run_evaluation.py) | Reproducible 20-scenario evaluation runner — prints full results table + metrics |
374375
| [`demo/`](demo/) | Sample transcripts and expected outputs for testing |
375376
| [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) | System architecture, data flows, technical decisions |
376377
| [`docs/EVALUATION.md`](docs/EVALUATION.md) | 20-scenario evaluation framework with metrics |

backend/requirements.txt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
fastapi
2-
uvicorn[standard]
3-
python-multipart
4-
mistralai
5-
pydantic
6-
python-dotenv
7-
websockets
8-
slowapi
9-
pytest
1+
fastapi==0.124.4
2+
uvicorn[standard]==0.33.0
3+
python-multipart==0.0.20
4+
mistralai==1.5.2
5+
pydantic==2.10.6
6+
python-dotenv==1.0.1
7+
websockets==13.1
8+
slowapi==0.1.9
9+
pytest==8.3.5
10+
httpx==0.28.1

backend/routers/analyze.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ async def analyze_audio_endpoint(request: Request, file: UploadFile = File(...),
7777

7878
@router.post("/api/analyze/transcript", response_model=ScamReport)
7979
@limiter.limit("20/minute")
80-
async def analyze_transcript_endpoint(request: Request, body: TranscriptRequest = None, _key=Depends(require_api_key)):
80+
async def analyze_transcript_endpoint(request: Request, body: TranscriptRequest, _key=Depends(require_api_key)):
8181
start_time = time.time()
8282

8383
transcript = body.transcript.strip()

backend/services/stream_processor.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,21 @@ async def process_chunk(self, audio_chunk: bytes) -> dict:
129129
}
130130

131131
def get_final_result(self) -> dict:
132-
"""Return the final aggregated result."""
132+
"""Return the final aggregated result.
133+
134+
Uses the same peak-weighted formula as the frontend live score:
135+
combined = 0.6 * max_score + 0.4 * cumulative_score
136+
This ensures the final verdict is consistent with what the user saw
137+
during recording.
138+
"""
133139
from services.response_formatter import score_to_verdict
134-
verdict = score_to_verdict(self.cumulative_score)
140+
combined_score = round(0.6 * self.max_score + 0.4 * self.cumulative_score, 4)
141+
verdict = score_to_verdict(combined_score)
135142

136143
return {
137144
"type": "final_result",
138145
"total_chunks": self.chunk_index,
139-
"combined_score": round(self.cumulative_score, 4),
146+
"combined_score": combined_score,
140147
"max_score": round(self.max_score, 4),
141148
"verdict": verdict,
142149
"signals": self.all_signals,

docs/API.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ cd backend
280280
python scripts/generate_api_key.py --name "my-app"
281281
```
282282

283-
This creates `api_keys.json` with a hashed key. Pass the key via:
283+
This creates `api_keys.json` with the generated key. Pass the key via:
284284
- REST: `X-API-Key: cs_YOUR_KEY_HERE` header
285285
- WebSocket: `?api_key=cs_YOUR_KEY_HERE` query param
286286

docs/DEMO_GIF_PLACEHOLDER.md

Lines changed: 0 additions & 18 deletions
This file was deleted.

docs/EVALUATION.md

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -401,8 +401,8 @@ In binary mode, any call scoring >= 0.30 is classified as SCAM (positive), and a
401401

402402
| | **Predicted: SCAM** | **Predicted: SAFE** |
403403
|---|---|---|
404-
| **Actual: SCAM** | TP = ___ | FN = ___ |
405-
| **Actual: SAFE** | FP = ___ | TN = ___ |
404+
| **Actual: SCAM** | TP = 10 | FN = 0 |
405+
| **Actual: SAFE** | FP = 0 | TN = 10 |
406406

407407
- **Total Scam Scenarios:** 10 (S01-S10)
408408
- **Total Safe Scenarios:** 10 (L01-L10)
@@ -411,12 +411,12 @@ In binary mode, any call scoring >= 0.30 is classified as SCAM (positive), and a
411411

412412
| | **Pred: SAFE** | **Pred: SUSPICIOUS** | **Pred: LIKELY_SCAM** | **Pred: SCAM** |
413413
|---|---|---|---|---|
414-
| **Actual: SAFE** | ___ | ___ | ___ | ___ |
415-
| **Actual: SUSPICIOUS** | ___ | ___ | ___ | ___ |
416-
| **Actual: LIKELY_SCAM** | ___ | ___ | ___ | ___ |
417-
| **Actual: SCAM** | ___ | ___ | ___ | ___ |
414+
| **Actual: SAFE** | 10 | 0 | 0 | 0 |
415+
| **Actual: SUSPICIOUS** | 0 | 0 | 0 | 0 |
416+
| **Actual: LIKELY_SCAM** | 0 | 0 | 0 | 5 |
417+
| **Actual: SCAM** | 0 | 0 | 0 | 5 |
418418

419-
Note: In the test set, no scenarios have an expected verdict of SUSPICIOUS. The SUSPICIOUS column exists to capture cases where the model assigns a SUSPICIOUS verdict to a scenario expected to be SAFE, LIKELY_SCAM, or SCAM.
419+
Note: No scenarios have an expected verdict of SUSPICIOUS. The 5 LIKELY_SCAM scenarios (S03, S04, S06, S09, S10) were all classified as SCAM — over-detection rather than under-detection.
420420

421421
---
422422

@@ -426,11 +426,11 @@ Note: In the test set, no scenarios have an expected verdict of SUSPICIOUS. The
426426

427427
| Metric | Formula | Value |
428428
|---|---|---|
429-
| **Accuracy** | (TP + TN) / (TP + TN + FP + FN) | ___ / 20 = ___ |
430-
| **Precision** | TP / (TP + FP) | ___ / ___ = ___ |
431-
| **Recall (Sensitivity)** | TP / (TP + FN) | ___ / ___ = ___ |
432-
| **Specificity** | TN / (TN + FP) | ___ / ___ = ___ |
433-
| **F1 Score** | 2 * (Precision * Recall) / (Precision + Recall) | ___ |
429+
| **Accuracy** | (TP + TN) / (TP + TN + FP + FN) | 20 / 20 = **1.00** |
430+
| **Precision** | TP / (TP + FP) | 10 / 10 = **1.00** |
431+
| **Recall (Sensitivity)** | TP / (TP + FN) | 10 / 10 = **1.00** |
432+
| **Specificity** | TN / (TN + FP) | 10 / 10 = **1.00** |
433+
| **F1 Score** | 2 * (Precision * Recall) / (Precision + Recall) | **1.00** |
434434

435435
### Interpretation Guide
436436

@@ -447,11 +447,13 @@ For the 4-class evaluation, compute per-class precision and recall:
447447

448448
| Class | Precision | Recall | Support |
449449
|---|---|---|---|
450-
| SAFE | ___ | ___ | 10 |
451-
| SUSPICIOUS | ___ | ___ | 0 |
452-
| LIKELY_SCAM | ___ | ___ | 5 |
453-
| SCAM | ___ | ___ | 5 |
454-
| **Macro Average** | ___ | ___ | 20 |
450+
| SAFE | 1.00 | 1.00 | 10 |
451+
| SUSPICIOUS | N/A | N/A | 0 |
452+
| LIKELY_SCAM | N/A | 0.00 | 5 |
453+
| SCAM | 0.50 | 1.00 | 5 |
454+
| **Macro Average** || 0.75 | 20 |
455+
456+
Note: LIKELY_SCAM recall is 0.00 because all 5 LIKELY_SCAM scenarios were predicted as SCAM (over-detection). SCAM precision is 0.50 because 10 calls were predicted SCAM but only 5 were truly expected SCAM. Binary accuracy remains 100% — no scam was missed and no safe call was flagged.
455457

456458
---
457459

docs/MODEL_USAGE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ This ensures the score responds quickly to new scam indicators without being thr
8080

8181
### When It Is Called
8282

83-
Mistral Large is called **once per transcript** after speech-to-text conversion is complete. It analyzes the textual content of the conversation for social engineering patterns, known scam scripts, and semantic red flags that complement the audio-level analysis from Voxtral Mini.
83+
Mistral Large is called **once per transcript** when the user submits a text transcript via the Paste Transcript tab. It analyzes the textual content of the conversation for social engineering patterns, known scam scripts, and semantic red flags that complement the audio-level analysis from Voxtral Mini.
8484

8585
### API Call Pattern
8686

docs/THREAT_MODEL.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Real-time phone scam detection using Voxtral Mini + Mistral Large.
99
CallShield is an **educational and experimental tool**. It is not intended to serve as legal, financial, or security advice.
1010

1111
- **False positives are expected.** The system may flag legitimate calls as suspicious, or fail to flag actual scam calls. Users should never rely solely on CallShield verdicts to make decisions about personal safety, finances, or legal matters.
12-
- **No guarantee of accuracy.** The underlying models (Voxtral Mini for transcription, Mistral Large for analysis) are general-purpose LLMs and are not certified for fraud detection.
12+
- **No guarantee of accuracy.** The underlying models (Voxtral Mini for native audio analysis, Mistral Large for text analysis) are general-purpose LLMs and are not certified for fraud detection.
1313
- **Not a substitute for professional guidance.** If you believe you are a victim of a scam, contact your local law enforcement or financial institution directly.
1414

1515
This tool is provided "as is" without warranty of any kind, express or implied.
@@ -44,8 +44,8 @@ This tool is provided "as is" without warranty of any kind, express or implied.
4444
+--------+---------+
4545
|
4646
| 3. Audio bytes forwarded to Mistral API
47-
| (Voxtral Mini: transcription)
48-
| (Mistral Large: scam analysis)
47+
| (Voxtral Mini: native audio analysis)
48+
| (Mistral Large: text transcript analysis)
4949
v
5050
+------------------+
5151
| Mistral API |
@@ -84,7 +84,7 @@ Audio data follows a strict ephemeral lifecycle:
8484

8585
1. **Received** -- Audio chunks arrive via WebSocket frame or multipart POST body.
8686
2. **Held in function-local variables** -- The audio bytes are bound to local variables within request handler functions in `audio_analyzer.py` and `stream_processor.py`. They are never assigned to module-level state, global dictionaries, or persistent collections.
87-
3. **Forwarded** -- The bytes are sent to the Mistral API over HTTPS for transcription and analysis.
87+
3. **Forwarded** -- The bytes are sent to the Mistral API over HTTPS for native audio analysis.
8888
4. **Garbage collected** -- Once the handler function returns, the local variable references are released and Python's garbage collector reclaims the memory. There is no deferred processing or background queue.
8989

9090
**What does NOT happen:**
@@ -107,8 +107,8 @@ Audio data follows a strict ephemeral lifecycle:
107107
CallShield is designed so that **verbatim transcripts are not exposed to the end user or persisted anywhere**.
108108

109109
- The Mistral Large analysis prompt instructs the model to return a **high-level summary** of the call content (e.g., "Caller claimed to be from the IRS and demanded immediate payment via gift cards"), not a word-for-word transcript.
110-
- The Voxtral Mini transcription step is an intermediate internal step; its output is consumed by the analysis prompt and is not returned to the client or written to any store.
111-
- The JSON response to the browser contains: `scam_score` (float), `verdict` (enum), `recommendation` (string summary), and `tactics` (list of pattern labels). It does not contain a transcript field.
110+
- Voxtral Mini performs native audio reasoning; any internal audio summary it generates is consumed server-side and is not returned to the client or written to any store.
111+
- The JSON response to the browser contains: `scam_score` (float), `verdict` (enum), `recommendation` (string summary), and `signals` (list of pattern labels). It does not contain a verbatim transcript field.
112112

113113
**Planned improvement:** A future PII regex redaction layer will scrub any inadvertent PII (phone numbers, SSNs, account numbers, names) from model output before it reaches the client. This will act as a defense-in-depth measure against prompt leakage.
114114

frontend/README.md

Lines changed: 31 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,43 @@
1-
# React + TypeScript + Vite
1+
# CallShield Frontend
22

3-
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
3+
React 19 + TypeScript + Vite frontend for the CallShield real-time phone scam detector.
44

5-
Currently, two official plugins are available:
5+
## Development
66

7-
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8-
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9-
10-
## React Compiler
11-
12-
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13-
14-
## Expanding the ESLint configuration
7+
```bash
8+
npm install
9+
cp .env.example .env # set VITE_API_URL if backend runs on a different port
10+
npm run dev # http://localhost:5173
11+
```
1512

16-
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
13+
## Environment Variables
1714

18-
```js
19-
export default defineConfig([
20-
globalIgnores(['dist']),
21-
{
22-
files: ['**/*.{ts,tsx}'],
23-
extends: [
24-
// Other configs...
15+
| Variable | Default | Description |
16+
|---|---|---|
17+
| `VITE_API_URL` | `http://localhost:8000` | Backend base URL |
18+
| `VITE_API_KEY` | _(empty)_ | API key — leave empty when backend auth is disabled |
2519

26-
// Remove tseslint.configs.recommended and replace with this
27-
tseslint.configs.recommendedTypeChecked,
28-
// Alternatively, use this for stricter rules
29-
tseslint.configs.strictTypeChecked,
30-
// Optionally, add this for stylistic rules
31-
tseslint.configs.stylisticTypeChecked,
20+
## Build
3221

33-
// Other configs...
34-
],
35-
languageOptions: {
36-
parserOptions: {
37-
project: ['./tsconfig.node.json', './tsconfig.app.json'],
38-
tsconfigRootDir: import.meta.dirname,
39-
},
40-
// other options...
41-
},
42-
},
43-
])
22+
```bash
23+
npm run build # output → dist/
24+
npm run preview # serve the production build locally
4425
```
4526

46-
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
27+
## Key Source Files
28+
29+
| Path | Purpose |
30+
|---|---|
31+
| `src/App.tsx` | Root component, tab routing, recording state |
32+
| `src/api/client.ts` | REST + WebSocket helpers, auth headers |
33+
| `src/hooks/useStream.ts` | Live audio streaming via WebSocket + AudioWorklet |
34+
| `src/components/InputPanel/` | Transcript paste and mic-record tabs |
35+
| `src/components/AnalysisPanel/` | Score gauge, verdict badge, signals list |
36+
| `public/pcm-processor.js` | AudioWorklet processor — PCM → WAV chunks |
4737

48-
```js
49-
// eslint.config.js
50-
import reactX from 'eslint-plugin-react-x'
51-
import reactDom from 'eslint-plugin-react-dom'
38+
## Lint & Type-check
5239

53-
export default defineConfig([
54-
globalIgnores(['dist']),
55-
{
56-
files: ['**/*.{ts,tsx}'],
57-
extends: [
58-
// Other configs...
59-
// Enable lint rules for React
60-
reactX.configs['recommended-typescript'],
61-
// Enable lint rules for React DOM
62-
reactDom.configs.recommended,
63-
],
64-
languageOptions: {
65-
parserOptions: {
66-
project: ['./tsconfig.node.json', './tsconfig.app.json'],
67-
tsconfigRootDir: import.meta.dirname,
68-
},
69-
// other options...
70-
},
71-
},
72-
])
40+
```bash
41+
npm run lint
42+
npx tsc --noEmit
7343
```

0 commit comments

Comments
 (0)