Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
72b6f6b
fix(rs-dapi-client): rotate instead of ban on ResourceExhausted rate-…
lklimek Jun 22, 2026
fadedd4
Merge branch 'v3.1-dev' into fix/rs-dapi-client-rate-limit-rotate
lklimek Jun 22, 2026
396865f
fix(rs-dapi-client): exclude throttled node + backoff/jitter on rate-…
lklimek Jun 22, 2026
b8a8c32
fix(rs-dapi-client): clamp rate-limit backoff shift + symmetric invar…
lklimek Jun 22, 2026
8a41c92
fix(rs-dapi-client): replace rotate-on-rate-limit with Envoy-driven b…
lklimek Jun 23, 2026
c905b16
refactor(rs-dapi-client): drive rate-limit ban from Envoy reset heade…
lklimek Jun 23, 2026
dade457
test(rs-dapi-client): apply QA-001..005 doc-accuracy and test-honesty…
lklimek Jun 23, 2026
2c76a14
Merge branch 'v3.1-dev' into fix/rs-dapi-client-rate-limit-rotate
lklimek Jun 23, 2026
47f4160
fix(rs-dapi-client): apply PR-3951 review fixes — ban_for max-semanti…
lklimek Jun 24, 2026
be6ae84
test(rs-dapi-client): restore genuine window-expiry coverage in ban_f…
lklimek Jun 24, 2026
41cc076
docs(rs-dapi-client): QA-006/007/008 — ban_with_reason scope note + n…
lklimek Jun 24, 2026
ddfe16c
docs(rs-dapi-client): tighten ban_for/ban_with_reason scope docs; har…
lklimek Jun 24, 2026
caa025e
feat(dashmate): add platform.gateway.rateLimiter.responseHeaders.enab…
lklimek Jun 24, 2026
c49adb2
fix(dashmate): key responseHeaders migration at 4.0.0 not released rc.2
lklimek Jun 24, 2026
458a28e
test(rs-dapi-client): prove ban_for via DapiClient::execute end-to-en…
lklimek Jun 24, 2026
b23008e
fix(dashmate): key responseHeaders migration at next release 4.0.0-rc…
lklimek Jun 24, 2026
3a9cf45
feat(dashmate): reorder gateway filters (cors,grpc_web before ratelim…
lklimek Jun 24, 2026
6818f3e
fix(dashmate): make grpc-web over-limit a trailers-only ResourceExhau…
lklimek Jun 24, 2026
5d45556
chore: gitignore .env.*.bak to prevent committing backup env files
lklimek Jun 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/dashmate/configs/defaults/getBaseConfigFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ export default function getBaseConfigFactory() {
blacklist: [],
whitelist: [],
enabled: true,
responseHeaders: {
enabled: true,
},
},
ssl: {
enabled: false,
Expand Down
19 changes: 19 additions & 0 deletions packages/dashmate/configs/getConfigFileMigrationsFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -1520,6 +1520,25 @@ export default function getConfigFileMigrationsFactory(homeDir, defaultConfigs)

return configFile;
},
'4.0.0-rc.3': (configFile) => {
Object.entries(configFile.configs)
.forEach(([, options]) => {
// Add responseHeaders toggle to rate limiter (default true so existing
// deployments keep emitting RateLimit-* headers; rs-dapi-client depends
// on RateLimit-Reset to apply precise ban windows instead of the
// exponential health-ban ladder).
// Keyed at the next release (4.0.0-rc.3), not the already-released
// rc.2: the runner skips fromVersion===toVersion, so a key equal to
// an operator's current version never fires. Backfill runs once the
// package bumps to rc.3 (mirrors the 3.1.0 migration added at 3.1.0-dev.1).
if (options.platform?.gateway?.rateLimiter
&& typeof options.platform.gateway.rateLimiter.responseHeaders === 'undefined') {
options.platform.gateway.rateLimiter.responseHeaders = base.get('platform.gateway.rateLimiter.responseHeaders');
}
});

return configFile;
},
Comment thread
lklimek marked this conversation as resolved.
Comment thread
lklimek marked this conversation as resolved.
};
}

Expand Down
5 changes: 5 additions & 0 deletions packages/dashmate/docker-compose.rate_limiter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ services:
- GRPC_MAX_CONNECTION_AGE=1h
- GRPC_MAX_CONNECTION_AGE_GRACE=10m
- GRPC_PORT=8081
# Emit RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset response
# headers so rs-dapi-client can read the exact reset window and ban the
# node for that duration instead of the exponential health-ban ladder.
# Controlled by platform.gateway.rateLimiter.responseHeaders.enabled.
- LIMIT_RESPONSE_HEADERS_ENABLED=${PLATFORM_GATEWAY_RATE_LIMITER_RESPONSE_HEADERS_ENABLED:?err}
expose:
- 8081
profiles:
Expand Down
1 change: 1 addition & 0 deletions packages/dashmate/docs/config/gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ The rate limiter protects the Platform from excessive requests:
| `platform.gateway.rateLimiter.unit` | Time unit for rate limiting | `minute` | `hour` |
| `platform.gateway.rateLimiter.whitelist` | IPs exempt from rate limiting | `[]` | `["192.168.1.1"]` |
| `platform.gateway.rateLimiter.blacklist` | IPs blocked from all requests | `[]` | `["10.0.0.1"]` |
| `platform.gateway.rateLimiter.responseHeaders.enabled` | Emit `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` response headers. `rs-dapi-client` reads the Reset header to apply a precise ban window instead of the exponential health-ban ladder. Disable only for privacy reasons. | `true` | `false` |

Available time units:
- `second`: Per-second rate limiting
Expand Down
16 changes: 15 additions & 1 deletion packages/dashmate/src/config/configJsonSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -690,8 +690,22 @@ export default {
enabled: {
type: 'boolean',
},
responseHeaders: {
type: 'object',
description: 'Control emission of RateLimit-* response headers (RateLimit-Limit, '
+ 'RateLimit-Remaining, RateLimit-Reset). When enabled, rs-dapi-client reads '
+ 'the Reset header to ban the node for the server-advertised window instead '
+ 'of the exponential health-ban ladder. Disable only for privacy reasons.',
properties: {
enabled: {
type: 'boolean',
},
},
additionalProperties: false,
required: ['enabled'],
},
},
required: ['docker', 'enabled', 'unit', 'requestsPerUnit', 'blacklist', 'whitelist', 'metrics'],
required: ['docker', 'enabled', 'unit', 'requestsPerUnit', 'blacklist', 'whitelist', 'metrics', 'responseHeaders'],
Comment thread
Claudius-Maginificent marked this conversation as resolved.
additionalProperties: false,
},
ssl: {
Expand Down
25 changes: 18 additions & 7 deletions packages/dashmate/templates/platform/gateway/envoy.yaml.dot
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@
{{?}}
http_filters:
# TODO: Introduce when stable https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/adaptive_concurrency_filter.html
# Filter order matters: cors and grpc_web MUST precede ratelimit so the
# over-limit RESOURCE_EXHAUSTED *local reply* gets CORS headers + grpc-web
# content-type framing on encode, letting browser (grpc-web) clients read
# RateLimit-Reset for the same node-backoff the native client does. A local
# reply only traverses encoder filters positioned ABOVE its generating
# filter (Envoy #11776), so ratelimit must come last (before router).
# TODO(verify on pinned Envoy build): the grpc_web encode behavior on a
# trailers-only over-limit local reply (keeps grpc-status+ratelimit-reset in
# HTTP headers vs reframing into a body trailer) is inferred from the
# gRPC-Web spec, not confirmed against dashmate's pinned image. Smoke-test
# before relying on browser parity (see PR #3951).
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
{{? it.platform.gateway.rateLimiter.enabled}}
- name: envoy.filters.http.ratelimit
typed_config:
Expand All @@ -96,12 +113,6 @@
timeout: 0.5s
transport_api_version: V3
{{?}}
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
Expand Down Expand Up @@ -198,7 +209,7 @@
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,grpc-status,grpc-message,code,drive-error-data-bin,dash-serialized-consensus-error-bin,stack-bin
expose_headers: custom-header-1,grpc-status,grpc-message,code,drive-error-data-bin,dash-serialized-consensus-error-bin,stack-bin,ratelimit-reset,ratelimit-limit,ratelimit-remaining

static_resources:
listeners:
Expand Down
Loading
Loading