Repository navigation
Performance optimization - #1
Merged
Merged
Conversation
Core changes across request lifecycle, routing, and session handling: - request.dart: lazy query params, lazy Session object + sessionTouched flag, preloadSession() bypass, fast inline cookie extraction, sequential IDs (replaces UUID), isolate-unique ID prefix, cookie prefix security fix - response.dart: static JsonUtf8Encoder (fused encoder, avoids String intermediate), lazy headers map allocation - base_container.dart: session I/O gated on sessionTouched (skip load/save/ Set-Cookie for routes that never touch session), skip load() for new sessions, X-Request-Id echoed only when client sends it, zero-middleware fast path bypasses closure chain entirely - fletch.dart: requestTimeout is now Duration? — null disables per-request Timer allocation (biggest single gain ~7k RPS) - router_interface.dart: static const empty map in RouteMatch avoids per-request HashMap for no-param routes - radix_route.dart: static cached RegExp, skip path normalization on hot path (dart:io paths are already clean), removed per-call processed list, break after static segment match - route_entry.dart: tryMatch() does method check + regex + param extraction in one pass, replacing separate matches() + extractParams() calls Result: 43,794 RPS (134.8% CPU, 19.3 MB) vs serinus 39,125 / dart_io 48,652
Security fixes: - Redact internal error details by default (debug: false); expose only with debug: true — prevents leaking DB addresses, stack traces, etc. - Add session.regenerate() to prevent session fixation after login - MemorySessionStore: cap at maxSessions (default 10k) with oldest-first eviction to prevent OOM DoS - Cookie parser: split-on-semicolon extraction prevents prefix-confusion attacks (evilfletch.sid=x;fletch.sid=real now correctly resolves) - Add MultipartFileExtension.sanitizedFilename stripping path traversal - Document rate limiter proxy bypass with X-Forwarded-For example Performance: - Lazy session ID and request ID generation — Random.secure() tokens now generated only on first access; benchmark routes that skip sessions pay zero entropy cost (37.8k → 44.3k RPS, #1 among Dart frameworks) Tests (286 total, 94.9% coverage): - Security test suite: error redaction, session.regenerate(), sanitizedFilename, MemorySessionStore eviction, session ID entropy - TLS integration tests: listenSecure() IPv4 binding, v6Only default, requestClientCertificate default (closes mutation testing gap) - New test files: cors, error_handler, fletch_features, rate_limiter, list_router, response, coverage_gaps, coverage_extension CI: - ci.yml: analyze + test + 90% coverage enforcement + Codecov upload - mutation.yml: weekly dart_mutant run (50% sample, 75% score threshold) with HTML/JUnit/AI reports as artifacts
…ts, and update router implementation.
…e project configuration and CI.
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 28248803 | Triggered | Generic Private Key | 100decc | packages/fletch/test/integration/tls_test.dart | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secret safely. Learn here the best practices.
- Revoke and rotate this secret.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
- Remove duplicate debug field from Fletch (inherited from BaseContainer) - Use super.debug constructor param to satisfy use_super_parameters lint - Add ignore_for_file for constant_identifier_names on HTTP method constants - Exclude benchmark/ from analysis_options to prevent dartmark noise - Remove unused imports in cors_test, coverage_extension_test, coverage_gaps_test - Remove unused port variable in fletch_features_test - Replace hardcoded TLS cert/key in tls_test with runtime openssl generation - Add apps/fletch_bench/drafts/ to .gitignore for local notes - Remove OPTIMIZATION_CHANGELOG.md from tracked files (moved to drafts/)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Performance, Security & Quality — Full Overhaul
Benchmark
p50 latency dropped from 0.27 ms → 0.17 ms. Memory held steady at ~18.4 MB.
Performance Changes
Lazy ID generation (biggest single regression fix)
Session IDs and request IDs were previously generated eagerly on every request using
Random.secure()— 36 bytes of OS entropy consumed even for routes that never touch sessions. Benchmark routes paid ~2.9 µs per request for IDs that were immediately thrown away.Both are now generated lazily — only on first access. Routes that skip
req.sessionandreq.requestIdpay exactly zero.Nullable
requestTimeout— remove per-request TimerrequestTimeout: nulldisables the 30s timeout entirely. This removes aTimer+Future+ two closure allocations per request — the single largest throughput improvement (~7k RPS).Environments with external timeout enforcement (nginx, load balancers) should use
nullin production.Lazy session object +
sessionTouchedgateThe
Sessionobject, all store I/O, andSet-Cookieemission are now gated behind a singlesessionTouchedboolean. Routes like/healthand/api/echopay zero session overhead.Zero-middleware fast path
When no global or route-level middleware is registered,
wrapWithMiddlewareshort-circuits directly to the handler — no closure or index variable allocations.Fused JSON encoder
res.json()now uses a staticJsonUtf8Encoderthat encodes directly toUint8Listin one step, removing the intermediateStringallocation per JSON response.Lazy maps everywhere
req.query—Uri.queryParametersonly called on first accessres.headers—LinkedHashMaponly allocated when a custom header is setRouteMatch.pathParams— static routes share a singleconst {}mapRouter hot-path improvements
RegExpfields, normalization removed fromfindRoute,List processedallocation eliminatedtryMatch()does one regex pass instead of two; prefix boundary fix prevents/apimatching/apix/...Security Fixes
Error response leaks internal details
HIGHBefore: Any unhandled exception sent
error.toString()to the client — leaking DB connection strings, file paths, library internals.After: Generic
"Internal Server Error"by default. Opt in to full detail withFletch(debug: true)for local development only.Session fixation
HIGHBefore: No way to change session ID after login — attacker could fix a known ID before authentication.
After:
await req.session.regenerate()— destroys the old session record, generates a new cryptographically-random ID, and emits a newSet-Cookieautomatically.Session IDs were predictable counters
HIGHBefore:
ses_<microsecond-prefix>_<n>— sequential, enumerable.After:
ses_<32-char base64url>— 192 bits fromRandom.secure(), generated lazily.Cookie prefix-confusion attack
HIGHBefore:
indexOf('sessionId=')matchedevilSessionId=xand stopped at the first occurrence, missing the real cookie.After: Split-on-
;parser with exact name comparison — correctly skips prefixed cookies and finds the real session cookie regardless of order.MemorySessionStore— unbounded memoryMEDIUMBefore: No size cap — ~86,400 sessions/day at 1 req/sec with no eviction.
After:
maxSessionscap (default 10,000) with oldest-first eviction on insert.Rate limiter ineffective behind reverse proxies
MEDIUMDocumented clearly with a
keyGeneratorexample that readsX-Forwarded-Forsafely, including the trust warning.Multipart filename path traversal
LOWMultipartFile.filenameis attacker-controlled and may contain../../etc/passwd. NewsanitizedFilenameextension strips all path components:Tests
New test files
test/integration/tls_test.dartlistenSecure()IPv4 binding,v6Onlydefault, client cert defaulttest/integration/cors_test.darttest/integration/error_handler_test.darttest/integration/rate_limiter_test.darttest/integration/fletch_features_test.darttest/unit/list_router_test.dartListRouterbranches including isolated prefix boundarytest/unit/response_test.dartResponsemethodstest/unit/coverage_gaps_test.darttest/unit/coverage_extension_test.dartsanitizedFilename,regenerate()test/security/security_test.dartregenerate(), ID entropy,maxSessionseviction,sanitizedFilenameMutation testing
Ran dart_mutant against all security-critical paths. 96.7% of mutations killed on the targeted files — the only survivor was a default boolean in
listenSecure(), now covered by the new TLS integration tests.CI
ci.yml— runs on every push tomainand all PRsdart analyze --fatal-infosdart test --coveragemutation.yml— weekly scheduled job (Mondays 03:00 UTC)--threshold 75Breaking Changes
None. All public API is backwards-compatible.
debugparameter added toFletch()— defaults tofalsemaxSessionsparameter added toMemorySessionStore()— defaults to10000session.regenerate()is a new method — no existing code affectedMultipartFileExtension.sanitizedFilenameis additiveFiles Changed