Remove /raw endpoint completely - security hardening

- Removed /raw route from server
- Removed get_raw function and RawQuery struct
- Removed extract_raw_lsb import
- Removed all /raw references from skill.md documentation
- Replaced url_template placeholders with concrete example URLs in MCP JSON
- Users can no longer access raw sensor data

Security: Raw sensor data access completely removed to prevent potential
information leakage from camera sensor patterns.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leopere 2026-02-05 17:05:57 -05:00
parent 05aea9530b
commit cad9ef1b23
Signed by: colin
SSH Key Fingerprint: SHA256:nRPCQTeMFLdGytxRQmPVK9VXY3/ePKQ5lGRyJhT5DY8
2 changed files with 29 additions and 60 deletions

View File

@ -38,28 +38,28 @@ curl "http://localhost:8787/random?bytes=64&hex=true"
curl "http://localhost:8787/random?bytes=1024" -o random.bin
```
### 2. Get Raw Data (`/raw`)
Get raw LSB (least significant bit) data from camera sensor without cryptographic extraction. Useful for testing or custom post-processing.
**Parameters:**
- `bytes` (integer, default: 65536, max: 1048576): Number of raw bytes to extract
**Example:**
```bash
curl "http://localhost:8787/raw?bytes=1024" -o raw.bin
```
### 3. Stream Random Bytes (`/stream`)
### 2. Stream Random Bytes (`/stream`)
Stream continuous random bytes using Server-Sent Events (SSE) format. Useful for high-throughput applications.
**Parameters:**
- `bytes` (integer, optional): Total bytes to stream (omit for unlimited)
- `hex` (boolean, default: false): Stream as hexadecimal strings
- `hex` (boolean, default: false): Stream as hexadecimal strings instead of binary
**Examples:**
```bash
# Stream unlimited random bytes (binary)
curl -N "http://localhost:8787/stream"
# Stream exactly 1MB
curl -N "http://localhost:8787/stream?bytes=1048576"
# Stream as hexadecimal strings
curl -N "http://localhost:8787/stream?hex=true"
# Stream limited bytes as hex
curl -N "http://localhost:8787/stream?bytes=1024&hex=true"
```bash
# Stream unlimited random bytes
curl -N "http://localhost:8787/stream"
@ -70,7 +70,7 @@ curl -N "http://localhost:8787/stream?bytes=1048576"
curl -N "http://localhost:8787/stream?hex=true"
```
### 4. List Cameras (`/cameras`)
### 3. List Cameras (`/cameras`)
List available camera devices on the system.
@ -93,7 +93,7 @@ curl "http://localhost:8787/cameras"
}
```
### 5. Health Check (`/health`)
### 4. Health Check (`/health`)
Check if the TRNG server is running.
@ -113,7 +113,6 @@ The API implements the Model Context Protocol (MCP) specification, allowing AI a
The following tools are exposed via MCP:
1. **get-random**: Generate random bytes (cryptographically secure)
2. **get-raw**: Extract raw sensor data
3. **get-stream**: Stream random bytes continuously
4. **list-cameras**: Discover available cameras
5. **health-check**: Verify server status
@ -171,7 +170,7 @@ curl -N "http://localhost:8787/stream"
## Limitations
- Maximum 1MB per request (`/random` and `/raw`)
- Maximum 1MB per request (`/random`)
- Maximum 4 concurrent requests
- Requires a camera device with covered lens
- Performance depends on camera frame rate
@ -180,7 +179,6 @@ curl -N "http://localhost:8787/stream"
- No authentication required (intended for local/trusted networks)
- Random data is cryptographically secure when using `/random` endpoint
- Raw endpoint (`/raw`) provides unprocessed sensor data
- Consider rate limiting in production deployments
## See Also

View File

@ -10,7 +10,7 @@ use axum::{
routing::get,
Router,
};
use camera_trng::{extract_entropy, extract_raw_lsb, list_cameras, subscribe_entropy, unsubscribe_entropy, ensure_producer_running, test_camera, CameraConfig, CHUNK_SIZE};
use camera_trng::{extract_entropy, list_cameras, subscribe_entropy, unsubscribe_entropy, ensure_producer_running, test_camera, CameraConfig, CHUNK_SIZE};
use bytes::Bytes;
use std::sync::{Arc, Mutex};
use serde_json::json;
@ -32,12 +32,6 @@ struct RandomQuery {
fn default_bytes() -> usize { 32 }
#[derive(serde::Deserialize)]
struct RawQuery {
#[serde(default = "default_raw_bytes")]
bytes: usize,
}
fn default_raw_bytes() -> usize { 65536 }
#[derive(serde::Deserialize)]
struct StreamQuery {
@ -74,10 +68,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.route("/", get(index))
.route("/cameras", get(get_cameras))
.route("/random", get(get_random))
.route("/raw", get(get_raw))
.route("/stream", get(get_stream))
.route("/health", get(health))
.route("/.well-known/mcp.json", get(mcp_wellknown))
.route("/.well-known/skill.md", get(get_skill_md))
.route("/docs", get(get_docs))
.route("/docs/skill.md", get(get_skill_md))
.route("/docs/mcp.json", get(mcp_wellknown));
@ -266,9 +260,10 @@ async fn mcp_wellknown(headers: HeaderMap, _uri: Uri) -> Json<serde_json::Value>
let origin = format!("{}://{}", scheme, host);
// Build URL templates as strings first
let random_url = format!("{}/random?bytes={{bytes}}&hex={{hex}}", origin);
let raw_url = format!("{}/raw?bytes={{bytes}}", origin);
let stream_url = format!("{}/stream?bytes={{bytes}}&hex={{hex}}", origin);
let random_url_example1 = format!("{}/random?bytes=32&hex=true", origin);
let random_url_example2 = format!("{}/random?bytes=64&hex=false", origin);
let stream_url_example1 = format!("{}/stream?hex=true", origin);
let stream_url_example2 = format!("{}/stream?bytes=1024&hex=true", origin);
let cameras_url = format!("{}/cameras", origin);
let health_url = format!("{}/health", origin);
let mcp_url = format!("{}/.well-known/mcp.json", origin);
@ -282,7 +277,8 @@ async fn mcp_wellknown(headers: HeaderMap, _uri: Uri) -> Json<serde_json::Value>
{
"name": "get-random",
"description": "Get cryptographically secure random bytes from camera sensor entropy",
"url_template": random_url,
"url": random_url_example1,
"example": random_url_example2,
"capabilities": ["random-generation", "entropy-source", "quantum"],
"auth": { "type": "none" },
"parameters": {
@ -290,20 +286,11 @@ async fn mcp_wellknown(headers: HeaderMap, _uri: Uri) -> Json<serde_json::Value>
"hex": { "type": "boolean", "default": false, "description": "Return bytes as hexadecimal string instead of binary" }
}
},
{
"name": "get-raw",
"description": "Get raw LSB (least significant bit) data from camera sensor without cryptographic extraction",
"url_template": raw_url,
"capabilities": ["entropy-source", "raw-data"],
"auth": { "type": "none" },
"parameters": {
"bytes": { "type": "integer", "default": 65536, "min": 1, "max": 1048576, "description": "Number of raw bytes to extract (max 1MB)" }
}
},
{
"name": "get-stream",
"description": "Stream continuous random bytes (SSE format). Use ?bytes=N to limit total bytes, ?hex=true for hex output",
"url_template": stream_url,
"url": stream_url_example1,
"example": stream_url_example2,
"capabilities": ["random-generation", "entropy-source", "quantum", "streaming"],
"auth": { "type": "none" },
"parameters": {
@ -314,7 +301,7 @@ async fn mcp_wellknown(headers: HeaderMap, _uri: Uri) -> Json<serde_json::Value>
{
"name": "list-cameras",
"description": "List available camera devices",
"url_template": cameras_url,
"url": cameras_url,
"capabilities": ["device-discovery"],
"auth": { "type": "none" },
"parameters": {}
@ -322,7 +309,7 @@ async fn mcp_wellknown(headers: HeaderMap, _uri: Uri) -> Json<serde_json::Value>
{
"name": "health-check",
"description": "Check if the TRNG server is running",
"url_template": health_url,
"url": health_url,
"capabilities": ["health"],
"auth": { "type": "none" },
"parameters": {}
@ -379,22 +366,6 @@ async fn get_random(Query(params): Query<RandomQuery>) -> Response {
}
async fn get_raw(Query(params): Query<RawQuery>) -> Response {
let bytes = params.bytes.min(MAX_BYTES_PER_REQUEST);
if bytes == 0 {
return (StatusCode::BAD_REQUEST, "bytes must be > 0").into_response();
}
let config = CameraConfig::from_env();
match tokio::task::spawn_blocking(move || extract_raw_lsb(bytes, &config)).await {
Ok(Ok(data)) => Response::builder()
.header(header::CONTENT_TYPE, "application/octet-stream")
.header(header::CACHE_CONTROL, "no-store")
.body(Body::from(data))
.unwrap(),
Ok(Err(e)) => (StatusCode::INTERNAL_SERVER_ERROR, e).into_response(),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
/// Cryptographically sound continuous random. GET /stream or /stream?bytes=N.
/// Multiple streams get different data (each chunk goes to one consumer).