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:
parent
05aea9530b
commit
cad9ef1b23
36
skill.md
36
skill.md
|
|
@ -38,28 +38,28 @@ curl "http://localhost:8787/random?bytes=64&hex=true"
|
||||||
curl "http://localhost:8787/random?bytes=1024" -o random.bin
|
curl "http://localhost:8787/random?bytes=1024" -o random.bin
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Get Raw Data (`/raw`)
|
### 2. Stream Random Bytes (`/stream`)
|
||||||
|
|
||||||
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`)
|
|
||||||
|
|
||||||
Stream continuous random bytes using Server-Sent Events (SSE) format. Useful for high-throughput applications.
|
Stream continuous random bytes using Server-Sent Events (SSE) format. Useful for high-throughput applications.
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
- `bytes` (integer, optional): Total bytes to stream (omit for unlimited)
|
- `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:**
|
**Examples:**
|
||||||
```bash
|
```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
|
# Stream unlimited random bytes
|
||||||
curl -N "http://localhost:8787/stream"
|
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"
|
curl -N "http://localhost:8787/stream?hex=true"
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. List Cameras (`/cameras`)
|
### 3. List Cameras (`/cameras`)
|
||||||
|
|
||||||
List available camera devices on the system.
|
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.
|
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:
|
The following tools are exposed via MCP:
|
||||||
|
|
||||||
1. **get-random**: Generate random bytes (cryptographically secure)
|
1. **get-random**: Generate random bytes (cryptographically secure)
|
||||||
2. **get-raw**: Extract raw sensor data
|
|
||||||
3. **get-stream**: Stream random bytes continuously
|
3. **get-stream**: Stream random bytes continuously
|
||||||
4. **list-cameras**: Discover available cameras
|
4. **list-cameras**: Discover available cameras
|
||||||
5. **health-check**: Verify server status
|
5. **health-check**: Verify server status
|
||||||
|
|
@ -171,7 +170,7 @@ curl -N "http://localhost:8787/stream"
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
- Maximum 1MB per request (`/random` and `/raw`)
|
- Maximum 1MB per request (`/random`)
|
||||||
- Maximum 4 concurrent requests
|
- Maximum 4 concurrent requests
|
||||||
- Requires a camera device with covered lens
|
- Requires a camera device with covered lens
|
||||||
- Performance depends on camera frame rate
|
- Performance depends on camera frame rate
|
||||||
|
|
@ -180,7 +179,6 @@ curl -N "http://localhost:8787/stream"
|
||||||
|
|
||||||
- No authentication required (intended for local/trusted networks)
|
- No authentication required (intended for local/trusted networks)
|
||||||
- Random data is cryptographically secure when using `/random` endpoint
|
- Random data is cryptographically secure when using `/random` endpoint
|
||||||
- Raw endpoint (`/raw`) provides unprocessed sensor data
|
|
||||||
- Consider rate limiting in production deployments
|
- Consider rate limiting in production deployments
|
||||||
|
|
||||||
## See Also
|
## See Also
|
||||||
|
|
|
||||||
53
src/main.rs
53
src/main.rs
|
|
@ -10,7 +10,7 @@ use axum::{
|
||||||
routing::get,
|
routing::get,
|
||||||
Router,
|
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 bytes::Bytes;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
@ -32,12 +32,6 @@ struct RandomQuery {
|
||||||
|
|
||||||
fn default_bytes() -> usize { 32 }
|
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)]
|
#[derive(serde::Deserialize)]
|
||||||
struct StreamQuery {
|
struct StreamQuery {
|
||||||
|
|
@ -74,10 +68,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.route("/", get(index))
|
.route("/", get(index))
|
||||||
.route("/cameras", get(get_cameras))
|
.route("/cameras", get(get_cameras))
|
||||||
.route("/random", get(get_random))
|
.route("/random", get(get_random))
|
||||||
.route("/raw", get(get_raw))
|
|
||||||
.route("/stream", get(get_stream))
|
.route("/stream", get(get_stream))
|
||||||
.route("/health", get(health))
|
.route("/health", get(health))
|
||||||
.route("/.well-known/mcp.json", get(mcp_wellknown))
|
.route("/.well-known/mcp.json", get(mcp_wellknown))
|
||||||
|
.route("/.well-known/skill.md", get(get_skill_md))
|
||||||
.route("/docs", get(get_docs))
|
.route("/docs", get(get_docs))
|
||||||
.route("/docs/skill.md", get(get_skill_md))
|
.route("/docs/skill.md", get(get_skill_md))
|
||||||
.route("/docs/mcp.json", get(mcp_wellknown));
|
.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);
|
let origin = format!("{}://{}", scheme, host);
|
||||||
|
|
||||||
// Build URL templates as strings first
|
// Build URL templates as strings first
|
||||||
let random_url = format!("{}/random?bytes={{bytes}}&hex={{hex}}", origin);
|
let random_url_example1 = format!("{}/random?bytes=32&hex=true", origin);
|
||||||
let raw_url = format!("{}/raw?bytes={{bytes}}", origin);
|
let random_url_example2 = format!("{}/random?bytes=64&hex=false", origin);
|
||||||
let stream_url = format!("{}/stream?bytes={{bytes}}&hex={{hex}}", 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 cameras_url = format!("{}/cameras", origin);
|
||||||
let health_url = format!("{}/health", origin);
|
let health_url = format!("{}/health", origin);
|
||||||
let mcp_url = format!("{}/.well-known/mcp.json", 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",
|
"name": "get-random",
|
||||||
"description": "Get cryptographically secure random bytes from camera sensor entropy",
|
"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"],
|
"capabilities": ["random-generation", "entropy-source", "quantum"],
|
||||||
"auth": { "type": "none" },
|
"auth": { "type": "none" },
|
||||||
"parameters": {
|
"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" }
|
"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",
|
"name": "get-stream",
|
||||||
"description": "Stream continuous random bytes (SSE format). Use ?bytes=N to limit total bytes, ?hex=true for hex output",
|
"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"],
|
"capabilities": ["random-generation", "entropy-source", "quantum", "streaming"],
|
||||||
"auth": { "type": "none" },
|
"auth": { "type": "none" },
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|
@ -314,7 +301,7 @@ async fn mcp_wellknown(headers: HeaderMap, _uri: Uri) -> Json<serde_json::Value>
|
||||||
{
|
{
|
||||||
"name": "list-cameras",
|
"name": "list-cameras",
|
||||||
"description": "List available camera devices",
|
"description": "List available camera devices",
|
||||||
"url_template": cameras_url,
|
"url": cameras_url,
|
||||||
"capabilities": ["device-discovery"],
|
"capabilities": ["device-discovery"],
|
||||||
"auth": { "type": "none" },
|
"auth": { "type": "none" },
|
||||||
"parameters": {}
|
"parameters": {}
|
||||||
|
|
@ -322,7 +309,7 @@ async fn mcp_wellknown(headers: HeaderMap, _uri: Uri) -> Json<serde_json::Value>
|
||||||
{
|
{
|
||||||
"name": "health-check",
|
"name": "health-check",
|
||||||
"description": "Check if the TRNG server is running",
|
"description": "Check if the TRNG server is running",
|
||||||
"url_template": health_url,
|
"url": health_url,
|
||||||
"capabilities": ["health"],
|
"capabilities": ["health"],
|
||||||
"auth": { "type": "none" },
|
"auth": { "type": "none" },
|
||||||
"parameters": {}
|
"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.
|
/// Cryptographically sound continuous random. GET /stream or /stream?bytes=N.
|
||||||
/// Multiple streams get different data (each chunk goes to one consumer).
|
/// Multiple streams get different data (each chunk goes to one consumer).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue