swadhinbiswas

[email protected]
2026-07-26 17:09:48 +0600 committed NaN years ago
543fd17
commit 543fd17
4 files changed
+195 -0
▼CHANGELOG.md
+58-0
1
+# Changelog
2
+
3
+All notable changes to JustAPI will be documented in this file.
4
+
5
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+## [Unreleased]
9
+
10
+### Added
11
+- `PerRouteRateLimiter` middleware with prefix matching, method filtering, and optional fallback (`middleware.rs`)
12
+- `Server::add_per_route_rate_limit()` builder method
13
+- `CoreError` typed error enum with 12 variants, `thiserror`-derived (`error.rs`)
14
+- `CoreError::to_problem_json()` for RFC 9457 Problem Details rendering
15
+- `CoreError::into_response()` for direct HTTP response building
16
+- `CoreError::status_code()` for variant-to-HTTP-status mapping
17
+- `Server::with_shutdown_timeout(Duration)` builder method (configurable drain timeout, default 30s)
18
+- 15 new crash-prevention and feature tests
19
+
20
+### Changed
21
+- **BREAKING:** All error responses now follow RFC 9457 Problem Details format (`application/problem+json`) instead of `{"detail":"..."}` or `{"error":"..."}`. Clients matching on `detail` field will still work; clients matching on `error` field must update.
22
+- `error_response()` returns `{type, title, status, detail}` instead of `{detail}`
23
+- `service_unavailable_response()` returns `{type, title, status, detail}` with `application/problem+json` content type
24
+- `json_error()` in middleware returns `{type, title, status, detail}` instead of `{error}`
25
+- `Handler::Static` dispatch uses `error_response()` for non-2xx status codes
26
+- 40+ `unwrap()` calls on request hot paths replaced with safe error handling
27
+- Mutex locks in `metrics.rs` and `tracing_setup.rs` now recover from poisoning
28
+- Email format validator handles missing `@` without panicking
29
+- `openai.rs` status code conversion uses `safe_status()` fallback
30
+
31
+### Fixed
32
+- User-triggerable server crash via email validation without `@` character (`validate.rs:33`, `app.rs:244`)
33
+- Potential panic from `Bound::new()` failure on every request (`handlers.rs:176`)
34
+- Potential panic from `CString::new()` and `py.eval()` in `fast_dumps` (`handlers.rs:335-336`)
35
+- Mutex poisoning causing panic on `/metrics` endpoint (`metrics.rs:153,278`)
36
+- Clock skew panic in Redis rate limiter (`rate_limit.rs:78`)
37
+- Clock skew panic in mail verification tokens (`mail/otp.rs`, `mail/verify.rs`)
38
+
39
+## [2.0.7] - 2026-07-25
40
+
41
+### Added
42
+- Generator dependencies (FastAPI-style `yield` in `Depends`)
43
+- ORJSON as optional Rust-level serializer (`orjson` feature)
44
+- Production panic hotspot audit — fixed 6 categories of `unwrap()` panics
45
+- Scalar API Reference documentation page (156 pages total)
46
+
47
+### Changed
48
+- Demo shop stress test — all D1–D4 defects fixed, 29/29 assertions pass
49
+
50
+## [2.0.0] - 2026-07-03
51
+
52
+### Added
53
+- Initial release of JustAPI 2.0 "Singularity"
54
+- Rust core (`justapi-core`): HTTP/1.1+HTTP/2, routing, middleware, compression, WebSocket, SSE, TLS, database ORM, OpenAPI, GraphQL, gRPC, WASM middleware
55
+- Python bindings (`justapi-py`): FastAPI-compatible decorator API, DI, controllers, TestClient
56
+- CLI (`justapi-cli`): serve, db migrate/rollback, routes, doctor, gen, new, create, profile
57
+- Inference engine (`justapi-inference`): KV cache, RadixAttention, continuous batching, speculative decoding, OpenAI-compatible API
58
+- 493+ tests, 8 fuzz targets, CI with Miri
0
59
▼CONTRIBUTING.md
+32-0
78
78
79
79
---
80
80
81
+## Versioning & Deprecation Policy
82
+
83
+### Semantic Versioning
84
+
85
+JustAPI follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html):
86
+
87
+- **Major (X.0.0):** Breaking changes to public API. Requires migration guide.
88
+- **Minor (0.X.0):** New features, backward-compatible. May deprecate existing APIs.
89
+- **Patch (0.0.X):** Bug fixes, backward-compatible. No API changes.
90
+
91
+**Current status:** Pre-1.0. Minor versions may contain breaking changes (documented in CHANGELOG.md with migration notes).
92
+
93
+### Deprecation Process
94
+
95
+When a public API is deprecated:
96
+
97
+1. Add `#[deprecated(since = "X.Y.Z", note = "use new_api() instead")]` (Rust) or `warnings.warn(...)` (Python)
98
+2. Document the deprecation in CHANGELOG.md under `### Deprecated`
99
+3. Provide a migration example in the deprecation note
100
+4. Remove the deprecated API in the next **minor** version (not patch)
101
+
102
+### Breaking Change Requirements
103
+
104
+Before merging a breaking change:
105
+
106
+- [ ] CHANGELOG.md updated with `### Changed` or `### Removed` entry
107
+- [ ] Migration guide written (new section in docs or inline in CHANGELOG)
108
+- [ ] Deprecation period of at least one minor version (unless critical security fix)
109
+- [ ] All downstream examples and tests updated
110
+
111
+---
112
+
81
113
## Resuming Work after Context Reset
82
114
If you are an agent resuming work after a context reset:
83
115
1. Read [PLAN.md](PLAN.md) to locate the current phase and its status.
84
116
▼crates/justapi-core/src/server/mod.rs
+85-0
1263
1263
/// `Server::with_max_body_size`.
1264
1264
pub(crate) const DEFAULT_MAX_BODY_SIZE: usize = 50 * 1024 * 1024;
1265
1265
1266
+/// Maximum allowed URL path length in bytes. Paths longer than this are
1267
+/// rejected with 414 URI Too Long before any routing or processing.
1268
+const MAX_PATH_LENGTH: usize = 8192;
1269
+
1270
+/// Maximum allowed single header value length in bytes. Headers exceeding
1271
+/// this are rejected with 431 Request Header Fields Too Large.
1272
+const MAX_HEADER_VALUE_LENGTH: usize = 8192;
1273
+
1274
+/// Maximum allowed number of request headers. Requests with more headers
1275
+/// are rejected with 431 Request Header Fields Too Large.
1276
+const MAX_HEADER_COUNT: usize = 100;
1277
+
1278
+/// Maximum allowed query string length in bytes. Query strings longer than
1279
+/// this are rejected with 414 URI Too Long.
1280
+const MAX_QUERY_LENGTH: usize = 2048;
1281
+
1266
1282
/// Max wall-clock time a single request handler may run before we respond 504
1267
1283
/// and abort it. Guards against stuck/slow handlers and resource exhaustion.
1268
1284
/// Configurable via `JUSTAPI_REQUEST_TIMEOUT_SECS`.
1368
1384
let path = req.uri().path().to_string();
1369
1385
let method = req.method().clone();
1370
1386
let start = std::time::Instant::now();
1387
+
1388
+ // --- Input validation (security hardening, Phase 58) ---
1389
+ // Reject oversized paths before routing to prevent DoS via
1390
+ // extremely long URI strings that blow up memory in the router
1391
+ // or middleware chain.
1392
+ if path.len() > MAX_PATH_LENGTH {
1393
+ return Ok(error_response(
1394
+ StatusCode::URI_TOO_LONG,
1395
+ "request URI exceeds maximum length",
1396
+ ));
1397
+ }
1398
+ if let Some(query) = req.uri().query() {
1399
+ if query.len() > MAX_QUERY_LENGTH {
1400
+ return Ok(error_response(
1401
+ StatusCode::URI_TOO_LONG,
1402
+ "query string exceeds maximum length",
1403
+ ));
1404
+ }
1405
+ }
1406
+ // Reject requests with too many headers or oversized header values.
1407
+ // Excessively large headers waste memory and slow down parsing.
1408
+ let header_count = req.headers().len();
1409
+ if header_count > MAX_HEADER_COUNT {
1410
+ return Ok(error_response(
1411
+ StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
1412
+ "too many request headers",
1413
+ ));
1414
+ }
1415
+ for (name, value) in req.headers().iter() {
1416
+ if value.len() > MAX_HEADER_VALUE_LENGTH {
1417
+ return Ok(error_response(
1418
+ StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
1419
+ &format!("header '{}' exceeds maximum value length", name.as_str()),
1420
+ ));
1421
+ }
1422
+ }
1423
+
1371
1424
req.extensions_mut().insert(peer_addr);
1372
1425
1373
1426
let span = tracing::info_span!(
2055
2108
async move {
2056
2109
let path = req.uri().path().to_string();
2057
2110
let start = std::time::Instant::now();
2111
+
2112
+ // --- Input validation (security hardening, Phase 58) ---
2113
+ if path.len() > MAX_PATH_LENGTH {
2114
+ return Ok(error_response(
2115
+ StatusCode::URI_TOO_LONG,
2116
+ "request URI exceeds maximum length",
2117
+ ));
2118
+ }
2119
+ if let Some(query) = req.uri().query() {
2120
+ if query.len() > MAX_QUERY_LENGTH {
2121
+ return Ok(error_response(
2122
+ StatusCode::URI_TOO_LONG,
2123
+ "query string exceeds maximum length",
2124
+ ));
2125
+ }
2126
+ }
2127
+ let header_count = req.headers().len();
2128
+ if header_count > MAX_HEADER_COUNT {
2129
+ return Ok(error_response(
2130
+ StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
2131
+ "too many request headers",
2132
+ ));
2133
+ }
2134
+ for (name, value) in req.headers().iter() {
2135
+ if value.len() > MAX_HEADER_VALUE_LENGTH {
2136
+ return Ok(error_response(
2137
+ StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
2138
+ &format!("header '{}' exceeds maximum value length", name.as_str()),
2139
+ ));
2140
+ }
2141
+ }
2142
+
2058
2143
req.extensions_mut().insert(peer_addr);
2059
2144
2060
2145
metrics.record_request();
2061
2146
▼crates/justapi-core/tests/integration.rs
+20-0
525
525
let rows: Value = serde_json::from_slice(&body).unwrap();
526
526
assert!(rows.as_array().unwrap().is_empty());
527
527
}
528
+
529
+// --- Phase 58: Input validation security tests ---
530
+
531
+#[tokio::test]
532
+async fn test_oversized_path_returns_414() {
533
+ let addr = start_server().await;
534
+ // Path longer than MAX_PATH_LENGTH (8192)
535
+ let long_path = format!("/{}", "a".repeat(9000));
536
+ let (status, body) = send_request(addr, Method::GET, &long_path, None).await;
537
+ assert_eq!(status, StatusCode::URI_TOO_LONG);
538
+ let v: serde_json::Value = serde_json::from_slice(&body).unwrap();
539
+ assert_eq!(v["status"], 414);
540
+}
541
+
542
+#[tokio::test]
543
+async fn test_normal_path_still_works() {
544
+ let addr = start_server().await;
545
+ let (status, _) = send_request(addr, Method::GET, "/hello", None).await;
546
+ assert_eq!(status, StatusCode::OK);
547
+}
528
548