observability-toolkit 1.8.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -5
- package/dist/backends/index.d.ts +163 -0
- package/dist/backends/index.d.ts.map +1 -1
- package/dist/backends/index.js +57 -0
- package/dist/backends/index.js.map +1 -1
- package/dist/backends/index.test.js +55 -1
- package/dist/backends/index.test.js.map +1 -1
- package/dist/backends/local-jsonl-boolean-search.test.js +8 -8
- package/dist/backends/local-jsonl-boolean-search.test.js.map +1 -1
- package/dist/backends/local-jsonl-cache.test.d.ts +2 -0
- package/dist/backends/local-jsonl-cache.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-cache.test.js +295 -0
- package/dist/backends/local-jsonl-cache.test.js.map +1 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.d.ts +2 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.js +180 -0
- package/dist/backends/local-jsonl-circuit-breaker.test.js.map +1 -0
- package/dist/backends/local-jsonl-export.test.d.ts +2 -0
- package/dist/backends/local-jsonl-export.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-export.test.js +704 -0
- package/dist/backends/local-jsonl-export.test.js.map +1 -0
- package/dist/backends/local-jsonl-index.test.d.ts +2 -0
- package/dist/backends/local-jsonl-index.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-index.test.js +554 -0
- package/dist/backends/local-jsonl-index.test.js.map +1 -0
- package/dist/backends/local-jsonl-logs.test.js +52 -43
- package/dist/backends/local-jsonl-logs.test.js.map +1 -1
- package/dist/backends/local-jsonl-metrics.test.d.ts +2 -0
- package/dist/backends/local-jsonl-metrics.test.d.ts.map +1 -0
- package/dist/backends/local-jsonl-metrics.test.js +876 -0
- package/dist/backends/local-jsonl-metrics.test.js.map +1 -0
- package/dist/backends/local-jsonl-traces.test.js +89 -83
- package/dist/backends/local-jsonl-traces.test.js.map +1 -1
- package/dist/backends/local-jsonl.d.ts +39 -0
- package/dist/backends/local-jsonl.d.ts.map +1 -1
- package/dist/backends/local-jsonl.js +975 -492
- package/dist/backends/local-jsonl.js.map +1 -1
- package/dist/backends/signoz-api-circuit-breaker.test.d.ts +6 -0
- package/dist/backends/signoz-api-circuit-breaker.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-circuit-breaker.test.js +548 -0
- package/dist/backends/signoz-api-circuit-breaker.test.js.map +1 -0
- package/dist/backends/signoz-api-rate-limiter.test.d.ts +6 -0
- package/dist/backends/signoz-api-rate-limiter.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-rate-limiter.test.js +390 -0
- package/dist/backends/signoz-api-rate-limiter.test.js.map +1 -0
- package/dist/backends/signoz-api-ssrf.test.d.ts +6 -0
- package/dist/backends/signoz-api-ssrf.test.d.ts.map +1 -0
- package/dist/backends/signoz-api-ssrf.test.js +216 -0
- package/dist/backends/signoz-api-ssrf.test.js.map +1 -0
- package/dist/backends/signoz-api-test-helpers.d.ts +80 -0
- package/dist/backends/signoz-api-test-helpers.d.ts.map +1 -0
- package/dist/backends/signoz-api-test-helpers.js +79 -0
- package/dist/backends/signoz-api-test-helpers.js.map +1 -0
- package/dist/backends/signoz-api.d.ts +31 -1
- package/dist/backends/signoz-api.d.ts.map +1 -1
- package/dist/backends/signoz-api.js +717 -539
- package/dist/backends/signoz-api.js.map +1 -1
- package/dist/backends/signoz-api.test.d.ts +9 -0
- package/dist/backends/signoz-api.test.d.ts.map +1 -1
- package/dist/backends/signoz-api.test.js +20 -1032
- package/dist/backends/signoz-api.test.js.map +1 -1
- package/dist/lib/agent-as-judge.d.ts +388 -0
- package/dist/lib/agent-as-judge.d.ts.map +1 -0
- package/dist/lib/agent-as-judge.js +740 -0
- package/dist/lib/agent-as-judge.js.map +1 -0
- package/dist/lib/agent-as-judge.test.d.ts +5 -0
- package/dist/lib/agent-as-judge.test.d.ts.map +1 -0
- package/dist/lib/agent-as-judge.test.js +816 -0
- package/dist/lib/agent-as-judge.test.js.map +1 -0
- package/dist/lib/cache.d.ts +61 -2
- package/dist/lib/cache.d.ts.map +1 -1
- package/dist/lib/cache.js +54 -3
- package/dist/lib/cache.js.map +1 -1
- package/dist/lib/circuit-breaker.d.ts +101 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.js +158 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/circuit-breaker.test.d.ts +2 -0
- package/dist/lib/circuit-breaker.test.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.test.js +263 -0
- package/dist/lib/circuit-breaker.test.js.map +1 -0
- package/dist/lib/confident-export.d.ts +101 -0
- package/dist/lib/confident-export.d.ts.map +1 -0
- package/dist/lib/confident-export.js +393 -0
- package/dist/lib/confident-export.js.map +1 -0
- package/dist/lib/confident-export.test.d.ts +7 -0
- package/dist/lib/confident-export.test.d.ts.map +1 -0
- package/dist/lib/confident-export.test.js +835 -0
- package/dist/lib/confident-export.test.js.map +1 -0
- package/dist/lib/constants-symlink.test.d.ts +12 -0
- package/dist/lib/constants-symlink.test.d.ts.map +1 -0
- package/dist/lib/constants-symlink.test.js +357 -0
- package/dist/lib/constants-symlink.test.js.map +1 -0
- package/dist/lib/constants.d.ts +75 -0
- package/dist/lib/constants.d.ts.map +1 -1
- package/dist/lib/constants.js +104 -1
- package/dist/lib/constants.js.map +1 -1
- package/dist/lib/datadog-export.d.ts +156 -0
- package/dist/lib/datadog-export.d.ts.map +1 -0
- package/dist/lib/datadog-export.js +464 -0
- package/dist/lib/datadog-export.js.map +1 -0
- package/dist/lib/datadog-export.test.d.ts +14 -0
- package/dist/lib/datadog-export.test.d.ts.map +1 -0
- package/dist/lib/datadog-export.test.js +890 -0
- package/dist/lib/datadog-export.test.js.map +1 -0
- package/dist/lib/edge-cases.test.js +17 -17
- package/dist/lib/edge-cases.test.js.map +1 -1
- package/dist/lib/error-sanitizer.d.ts.map +1 -1
- package/dist/lib/error-sanitizer.js +29 -3
- package/dist/lib/error-sanitizer.js.map +1 -1
- package/dist/lib/error-sanitizer.test.js +159 -0
- package/dist/lib/error-sanitizer.test.js.map +1 -1
- package/dist/lib/error-types.d.ts +54 -0
- package/dist/lib/error-types.d.ts.map +1 -0
- package/dist/lib/error-types.js +154 -0
- package/dist/lib/error-types.js.map +1 -0
- package/dist/lib/error-types.test.d.ts +2 -0
- package/dist/lib/error-types.test.d.ts.map +1 -0
- package/dist/lib/error-types.test.js +196 -0
- package/dist/lib/error-types.test.js.map +1 -0
- package/dist/lib/evaluation-hooks.d.ts +49 -0
- package/dist/lib/evaluation-hooks.d.ts.map +1 -0
- package/dist/lib/evaluation-hooks.js +488 -0
- package/dist/lib/evaluation-hooks.js.map +1 -0
- package/dist/lib/evaluation-hooks.test.d.ts +8 -0
- package/dist/lib/evaluation-hooks.test.d.ts.map +1 -0
- package/dist/lib/evaluation-hooks.test.js +624 -0
- package/dist/lib/evaluation-hooks.test.js.map +1 -0
- package/dist/lib/export-utils.d.ts +99 -0
- package/dist/lib/export-utils.d.ts.map +1 -0
- package/dist/lib/export-utils.js +238 -0
- package/dist/lib/export-utils.js.map +1 -0
- package/dist/lib/export-utils.test.d.ts +5 -0
- package/dist/lib/export-utils.test.d.ts.map +1 -0
- package/dist/lib/export-utils.test.js +193 -0
- package/dist/lib/export-utils.test.js.map +1 -0
- package/dist/lib/file-utils.d.ts +17 -2
- package/dist/lib/file-utils.d.ts.map +1 -1
- package/dist/lib/file-utils.js +24 -5
- package/dist/lib/file-utils.js.map +1 -1
- package/dist/lib/file-utils.test.js +30 -0
- package/dist/lib/file-utils.test.js.map +1 -1
- package/dist/lib/histogram.d.ts +119 -0
- package/dist/lib/histogram.d.ts.map +1 -0
- package/dist/lib/histogram.js +202 -0
- package/dist/lib/histogram.js.map +1 -0
- package/dist/lib/histogram.test.d.ts +5 -0
- package/dist/lib/histogram.test.d.ts.map +1 -0
- package/dist/lib/histogram.test.js +381 -0
- package/dist/lib/histogram.test.js.map +1 -0
- package/dist/lib/indexer.test.js +27 -27
- package/dist/lib/indexer.test.js.map +1 -1
- package/dist/lib/input-validator.d.ts +12 -0
- package/dist/lib/input-validator.d.ts.map +1 -1
- package/dist/lib/input-validator.fuzz.test.d.ts +12 -0
- package/dist/lib/input-validator.fuzz.test.d.ts.map +1 -0
- package/dist/lib/input-validator.fuzz.test.js +290 -0
- package/dist/lib/input-validator.fuzz.test.js.map +1 -0
- package/dist/lib/input-validator.js +57 -3
- package/dist/lib/input-validator.js.map +1 -1
- package/dist/lib/input-validator.test.js +129 -1
- package/dist/lib/input-validator.test.js.map +1 -1
- package/dist/lib/instrumentation.d.ts +153 -0
- package/dist/lib/instrumentation.d.ts.map +1 -0
- package/dist/lib/instrumentation.integration.test.d.ts +2 -0
- package/dist/lib/instrumentation.integration.test.d.ts.map +1 -0
- package/dist/lib/instrumentation.integration.test.js +589 -0
- package/dist/lib/instrumentation.integration.test.js.map +1 -0
- package/dist/lib/instrumentation.js +520 -0
- package/dist/lib/instrumentation.js.map +1 -0
- package/dist/lib/instrumentation.test.d.ts +2 -0
- package/dist/lib/instrumentation.test.d.ts.map +1 -0
- package/dist/lib/instrumentation.test.js +821 -0
- package/dist/lib/instrumentation.test.js.map +1 -0
- package/dist/lib/langfuse-export.d.ts +125 -0
- package/dist/lib/langfuse-export.d.ts.map +1 -0
- package/dist/lib/langfuse-export.js +367 -0
- package/dist/lib/langfuse-export.js.map +1 -0
- package/dist/lib/langfuse-export.test.d.ts +7 -0
- package/dist/lib/langfuse-export.test.d.ts.map +1 -0
- package/dist/lib/langfuse-export.test.js +1007 -0
- package/dist/lib/langfuse-export.test.js.map +1 -0
- package/dist/lib/llm-as-judge.d.ts +657 -0
- package/dist/lib/llm-as-judge.d.ts.map +1 -0
- package/dist/lib/llm-as-judge.js +1397 -0
- package/dist/lib/llm-as-judge.js.map +1 -0
- package/dist/lib/llm-as-judge.test.d.ts +2 -0
- package/dist/lib/llm-as-judge.test.d.ts.map +1 -0
- package/dist/lib/llm-as-judge.test.js +2409 -0
- package/dist/lib/llm-as-judge.test.js.map +1 -0
- package/dist/lib/logger.d.ts +46 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +81 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/logger.test.d.ts +2 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/logger.test.js +122 -0
- package/dist/lib/logger.test.js.map +1 -0
- package/dist/lib/metrics.d.ts +62 -0
- package/dist/lib/metrics.d.ts.map +1 -0
- package/dist/lib/metrics.js +166 -0
- package/dist/lib/metrics.js.map +1 -0
- package/dist/lib/metrics.test.d.ts +5 -0
- package/dist/lib/metrics.test.d.ts.map +1 -0
- package/dist/lib/metrics.test.js +189 -0
- package/dist/lib/metrics.test.js.map +1 -0
- package/dist/lib/parse-stats.d.ts +119 -0
- package/dist/lib/parse-stats.d.ts.map +1 -0
- package/dist/lib/parse-stats.js +206 -0
- package/dist/lib/parse-stats.js.map +1 -0
- package/dist/lib/parse-stats.test.d.ts +5 -0
- package/dist/lib/parse-stats.test.d.ts.map +1 -0
- package/dist/lib/parse-stats.test.js +283 -0
- package/dist/lib/parse-stats.test.js.map +1 -0
- package/dist/lib/phoenix-export.d.ts +109 -0
- package/dist/lib/phoenix-export.d.ts.map +1 -0
- package/dist/lib/phoenix-export.js +429 -0
- package/dist/lib/phoenix-export.js.map +1 -0
- package/dist/lib/phoenix-export.test.d.ts +11 -0
- package/dist/lib/phoenix-export.test.d.ts.map +1 -0
- package/dist/lib/phoenix-export.test.js +725 -0
- package/dist/lib/phoenix-export.test.js.map +1 -0
- package/dist/lib/server-utils.d.ts +14 -1
- package/dist/lib/server-utils.d.ts.map +1 -1
- package/dist/lib/server-utils.js +43 -3
- package/dist/lib/server-utils.js.map +1 -1
- package/dist/lib/shared-schemas.d.ts +28 -0
- package/dist/lib/shared-schemas.d.ts.map +1 -1
- package/dist/lib/shared-schemas.js +33 -4
- package/dist/lib/shared-schemas.js.map +1 -1
- package/dist/lib/toon-encoder.d.ts +7 -2
- package/dist/lib/toon-encoder.d.ts.map +1 -1
- package/dist/lib/toon-encoder.js +21 -6
- package/dist/lib/toon-encoder.js.map +1 -1
- package/dist/lib/toon-encoder.test.d.ts +5 -0
- package/dist/lib/toon-encoder.test.d.ts.map +1 -0
- package/dist/lib/toon-encoder.test.js +85 -0
- package/dist/lib/toon-encoder.test.js.map +1 -0
- package/dist/lib/verification-events.d.ts +100 -0
- package/dist/lib/verification-events.d.ts.map +1 -0
- package/dist/lib/verification-events.js +162 -0
- package/dist/lib/verification-events.js.map +1 -0
- package/dist/lib/verification-events.test.d.ts +5 -0
- package/dist/lib/verification-events.test.d.ts.map +1 -0
- package/dist/lib/verification-events.test.js +193 -0
- package/dist/lib/verification-events.test.js.map +1 -0
- package/dist/server.d.ts +5 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +79 -21
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +30 -0
- package/dist/server.test.js.map +1 -1
- package/dist/test-helpers/env-utils.d.ts +22 -0
- package/dist/test-helpers/env-utils.d.ts.map +1 -1
- package/dist/test-helpers/env-utils.js +38 -0
- package/dist/test-helpers/env-utils.js.map +1 -1
- package/dist/test-helpers/fuzz-generators.d.ts +58 -0
- package/dist/test-helpers/fuzz-generators.d.ts.map +1 -0
- package/dist/test-helpers/fuzz-generators.js +216 -0
- package/dist/test-helpers/fuzz-generators.js.map +1 -0
- package/dist/test-helpers/index.d.ts +1 -0
- package/dist/test-helpers/index.d.ts.map +1 -1
- package/dist/test-helpers/index.js +2 -0
- package/dist/test-helpers/index.js.map +1 -1
- package/dist/test-helpers/memfs-utils.d.ts +181 -0
- package/dist/test-helpers/memfs-utils.d.ts.map +1 -0
- package/dist/test-helpers/memfs-utils.js +292 -0
- package/dist/test-helpers/memfs-utils.js.map +1 -0
- package/dist/test-helpers/memfs-utils.test.d.ts +5 -0
- package/dist/test-helpers/memfs-utils.test.d.ts.map +1 -0
- package/dist/test-helpers/memfs-utils.test.js +338 -0
- package/dist/test-helpers/memfs-utils.test.js.map +1 -0
- package/dist/test-helpers/race-condition-helpers.d.ts +85 -0
- package/dist/test-helpers/race-condition-helpers.d.ts.map +1 -0
- package/dist/test-helpers/race-condition-helpers.js +279 -0
- package/dist/test-helpers/race-condition-helpers.js.map +1 -0
- package/dist/test-helpers/test-data-builders.d.ts +40 -3
- package/dist/test-helpers/test-data-builders.d.ts.map +1 -1
- package/dist/test-helpers/test-data-builders.js +54 -5
- package/dist/test-helpers/test-data-builders.js.map +1 -1
- package/dist/test-helpers/tool-validators.d.ts.map +1 -1
- package/dist/test-helpers/tool-validators.js +16 -1
- package/dist/test-helpers/tool-validators.js.map +1 -1
- package/dist/tools/context-stats.d.ts.map +1 -1
- package/dist/tools/context-stats.js +6 -8
- package/dist/tools/context-stats.js.map +1 -1
- package/dist/tools/export-confident.d.ts +145 -0
- package/dist/tools/export-confident.d.ts.map +1 -0
- package/dist/tools/export-confident.js +134 -0
- package/dist/tools/export-confident.js.map +1 -0
- package/dist/tools/export-confident.test.d.ts +7 -0
- package/dist/tools/export-confident.test.d.ts.map +1 -0
- package/dist/tools/export-confident.test.js +332 -0
- package/dist/tools/export-confident.test.js.map +1 -0
- package/dist/tools/export-datadog.d.ts +160 -0
- package/dist/tools/export-datadog.d.ts.map +1 -0
- package/dist/tools/export-datadog.js +160 -0
- package/dist/tools/export-datadog.js.map +1 -0
- package/dist/tools/export-datadog.test.d.ts +8 -0
- package/dist/tools/export-datadog.test.d.ts.map +1 -0
- package/dist/tools/export-datadog.test.js +419 -0
- package/dist/tools/export-datadog.test.js.map +1 -0
- package/dist/tools/export-langfuse.d.ts +137 -0
- package/dist/tools/export-langfuse.d.ts.map +1 -0
- package/dist/tools/export-langfuse.js +131 -0
- package/dist/tools/export-langfuse.js.map +1 -0
- package/dist/tools/export-langfuse.test.d.ts +7 -0
- package/dist/tools/export-langfuse.test.d.ts.map +1 -0
- package/dist/tools/export-langfuse.test.js +303 -0
- package/dist/tools/export-langfuse.test.js.map +1 -0
- package/dist/tools/export-phoenix.d.ts +145 -0
- package/dist/tools/export-phoenix.d.ts.map +1 -0
- package/dist/tools/export-phoenix.js +135 -0
- package/dist/tools/export-phoenix.js.map +1 -0
- package/dist/tools/export-phoenix.test.d.ts +7 -0
- package/dist/tools/export-phoenix.test.d.ts.map +1 -0
- package/dist/tools/export-phoenix.test.js +316 -0
- package/dist/tools/export-phoenix.test.js.map +1 -0
- package/dist/tools/health-check.d.ts +26 -0
- package/dist/tools/health-check.d.ts.map +1 -1
- package/dist/tools/health-check.js +36 -7
- package/dist/tools/health-check.js.map +1 -1
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +6 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inject-evaluations.d.ts +1315 -0
- package/dist/tools/inject-evaluations.d.ts.map +1 -0
- package/dist/tools/inject-evaluations.js +121 -0
- package/dist/tools/inject-evaluations.js.map +1 -0
- package/dist/tools/inject-evaluations.test.d.ts +5 -0
- package/dist/tools/inject-evaluations.test.d.ts.map +1 -0
- package/dist/tools/inject-evaluations.test.js +359 -0
- package/dist/tools/inject-evaluations.test.js.map +1 -0
- package/dist/tools/query-evaluations.d.ts +25 -4
- package/dist/tools/query-evaluations.d.ts.map +1 -1
- package/dist/tools/query-evaluations.js +26 -2
- package/dist/tools/query-evaluations.js.map +1 -1
- package/dist/tools/query-evaluations.test.js +53 -46
- package/dist/tools/query-evaluations.test.js.map +1 -1
- package/dist/tools/query-llm-events.js +2 -2
- package/dist/tools/query-llm-events.js.map +1 -1
- package/dist/tools/query-llm-events.test.js +6 -3
- package/dist/tools/query-llm-events.test.js.map +1 -1
- package/dist/tools/query-logs.d.ts +8 -8
- package/dist/tools/query-logs.js +3 -3
- package/dist/tools/query-logs.js.map +1 -1
- package/dist/tools/query-metrics.d.ts +4 -4
- package/dist/tools/query-metrics.js +2 -2
- package/dist/tools/query-metrics.js.map +1 -1
- package/dist/tools/query-traces.d.ts +8 -8
- package/dist/tools/query-verifications.d.ts +111 -0
- package/dist/tools/query-verifications.d.ts.map +1 -0
- package/dist/tools/query-verifications.js +101 -0
- package/dist/tools/query-verifications.js.map +1 -0
- package/dist/tools/query-verifications.test.d.ts +5 -0
- package/dist/tools/query-verifications.test.d.ts.map +1 -0
- package/dist/tools/query-verifications.test.js +156 -0
- package/dist/tools/query-verifications.test.js.map +1 -0
- package/dist/types/evaluation-hooks.d.ts +176 -0
- package/dist/types/evaluation-hooks.d.ts.map +1 -0
- package/dist/types/evaluation-hooks.js +49 -0
- package/dist/types/evaluation-hooks.js.map +1 -0
- package/package.json +11 -2
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limiter tests for SigNoz API backend
|
|
3
|
+
* L-STYLE-2: Extracted from signoz-api.test.ts for maintainability
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import assert from 'node:assert';
|
|
7
|
+
import { SigNozApiBackend, TokenBucketRateLimiter } from './signoz-api.js';
|
|
8
|
+
import { HttpStatus } from '../lib/constants.js';
|
|
9
|
+
import { setupMock, createV5TraceResponse } from './signoz-api-test-helpers.js';
|
|
10
|
+
describe('SigNozApiBackend rate limiter', () => {
|
|
11
|
+
it('should allow requests up to max tokens', async () => {
|
|
12
|
+
let callCount = 0;
|
|
13
|
+
globalThis.fetch = setupMock(async () => {
|
|
14
|
+
callCount++;
|
|
15
|
+
return {
|
|
16
|
+
ok: true,
|
|
17
|
+
json: async () => createV5TraceResponse([]),
|
|
18
|
+
text: async () => '',
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
// Create backend - default is 60 tokens
|
|
22
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
23
|
+
// Make multiple rapid requests - should all succeed within token limit
|
|
24
|
+
for (let i = 0; i < 10; i++) {
|
|
25
|
+
await backend.queryTraces({});
|
|
26
|
+
}
|
|
27
|
+
assert.strictEqual(callCount, 10, 'All requests within limit should succeed');
|
|
28
|
+
});
|
|
29
|
+
it('should block requests when rate limit exceeded', async () => {
|
|
30
|
+
const originalDateNow = Date.now;
|
|
31
|
+
let currentTime = 1000000;
|
|
32
|
+
Date.now = () => currentTime;
|
|
33
|
+
// Create limiter with very low rate limit (3 tokens) for testing
|
|
34
|
+
const limiter = new TokenBucketRateLimiter(3, 1); // 3 tokens, 1/sec refill
|
|
35
|
+
// Use all tokens
|
|
36
|
+
assert.strictEqual(limiter.tryConsume(), true, '1st token should be available');
|
|
37
|
+
assert.strictEqual(limiter.tryConsume(), true, '2nd token should be available');
|
|
38
|
+
assert.strictEqual(limiter.tryConsume(), true, '3rd token should be available');
|
|
39
|
+
assert.strictEqual(limiter.tryConsume(), false, '4th token should be blocked');
|
|
40
|
+
Date.now = originalDateNow;
|
|
41
|
+
});
|
|
42
|
+
it('should refill tokens over time', async () => {
|
|
43
|
+
const originalDateNow = Date.now;
|
|
44
|
+
let currentTime = 1000000;
|
|
45
|
+
Date.now = () => currentTime;
|
|
46
|
+
const limiter = new TokenBucketRateLimiter(3, 1); // 3 tokens, 1/sec refill
|
|
47
|
+
// Use all tokens
|
|
48
|
+
limiter.tryConsume();
|
|
49
|
+
limiter.tryConsume();
|
|
50
|
+
limiter.tryConsume();
|
|
51
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0, 'All tokens should be used');
|
|
52
|
+
// Advance time by 2 seconds
|
|
53
|
+
currentTime += 2000;
|
|
54
|
+
// Should have refilled 2 tokens
|
|
55
|
+
assert.strictEqual(limiter.getAvailableTokens(), 2, 'Should have refilled 2 tokens');
|
|
56
|
+
// Advance time by 5 more seconds (past max)
|
|
57
|
+
currentTime += 5000;
|
|
58
|
+
// Should cap at max tokens
|
|
59
|
+
assert.strictEqual(limiter.getAvailableTokens(), 3, 'Should cap at max tokens');
|
|
60
|
+
Date.now = originalDateNow;
|
|
61
|
+
});
|
|
62
|
+
it('should return empty array when rate limited', async () => {
|
|
63
|
+
const originalDateNow = Date.now;
|
|
64
|
+
let currentTime = 1000000;
|
|
65
|
+
Date.now = () => currentTime;
|
|
66
|
+
let callCount = 0;
|
|
67
|
+
globalThis.fetch = setupMock(async () => {
|
|
68
|
+
callCount++;
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
json: async () => createV5TraceResponse([]),
|
|
72
|
+
text: async () => '',
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
// We can't easily set custom rate limits on the backend, so we'll test
|
|
76
|
+
// the behavior by making 60+ requests (default limit)
|
|
77
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
78
|
+
// Make 60 requests (default token limit)
|
|
79
|
+
for (let i = 0; i < 60; i++) {
|
|
80
|
+
await backend.queryTraces({});
|
|
81
|
+
}
|
|
82
|
+
assert.strictEqual(callCount, 60);
|
|
83
|
+
// 61st request should be rate limited
|
|
84
|
+
const result = await backend.queryTraces({});
|
|
85
|
+
assert.deepStrictEqual(result, [], 'Should return empty array when rate limited');
|
|
86
|
+
assert.strictEqual(callCount, 60, 'Should not have made fetch call when rate limited');
|
|
87
|
+
Date.now = originalDateNow;
|
|
88
|
+
});
|
|
89
|
+
it('should log warning when rate limit exceeded', async () => {
|
|
90
|
+
const originalDateNow = Date.now;
|
|
91
|
+
let currentTime = 1000000;
|
|
92
|
+
Date.now = () => currentTime;
|
|
93
|
+
const warnLogs = [];
|
|
94
|
+
const originalWarn = console.warn;
|
|
95
|
+
console.warn = (msg) => { warnLogs.push(msg); };
|
|
96
|
+
const limiter = new TokenBucketRateLimiter(2, 1);
|
|
97
|
+
// Use all tokens
|
|
98
|
+
limiter.tryConsume();
|
|
99
|
+
limiter.tryConsume();
|
|
100
|
+
// This should trigger the warning
|
|
101
|
+
limiter.tryConsume();
|
|
102
|
+
console.warn = originalWarn;
|
|
103
|
+
Date.now = originalDateNow;
|
|
104
|
+
assert(warnLogs.some(log => log.includes('[obs-toolkit] Rate limit exceeded')));
|
|
105
|
+
});
|
|
106
|
+
it('should report rate limit status in health check', async () => {
|
|
107
|
+
const originalDateNow = Date.now;
|
|
108
|
+
let currentTime = 1000000;
|
|
109
|
+
Date.now = () => currentTime;
|
|
110
|
+
globalThis.fetch = setupMock(async () => {
|
|
111
|
+
return {
|
|
112
|
+
ok: true,
|
|
113
|
+
json: async () => ({ status: 'success' }),
|
|
114
|
+
text: async () => '',
|
|
115
|
+
};
|
|
116
|
+
});
|
|
117
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
118
|
+
// Use all 60 tokens
|
|
119
|
+
for (let i = 0; i < 60; i++) {
|
|
120
|
+
await backend.queryTraces({});
|
|
121
|
+
}
|
|
122
|
+
// Health check should report rate limit status
|
|
123
|
+
const health = await backend.healthCheck();
|
|
124
|
+
assert.strictEqual(health.status, 'error');
|
|
125
|
+
assert(health.message?.includes('Rate limit'));
|
|
126
|
+
Date.now = originalDateNow;
|
|
127
|
+
});
|
|
128
|
+
it('should reset tokens with reset method', async () => {
|
|
129
|
+
const limiter = new TokenBucketRateLimiter(3, 1);
|
|
130
|
+
// Use all tokens
|
|
131
|
+
limiter.tryConsume();
|
|
132
|
+
limiter.tryConsume();
|
|
133
|
+
limiter.tryConsume();
|
|
134
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0);
|
|
135
|
+
// Reset
|
|
136
|
+
limiter.reset();
|
|
137
|
+
assert.strictEqual(limiter.getAvailableTokens(), 3, 'Should have all tokens after reset');
|
|
138
|
+
});
|
|
139
|
+
it('should handle logs query when rate limited', async () => {
|
|
140
|
+
const originalDateNow = Date.now;
|
|
141
|
+
let currentTime = 1000000;
|
|
142
|
+
Date.now = () => currentTime;
|
|
143
|
+
let callCount = 0;
|
|
144
|
+
globalThis.fetch = setupMock(async () => {
|
|
145
|
+
callCount++;
|
|
146
|
+
return {
|
|
147
|
+
ok: true,
|
|
148
|
+
json: async () => ({ data: { data: { results: [] } } }),
|
|
149
|
+
text: async () => '',
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
153
|
+
// Use all 60 tokens
|
|
154
|
+
for (let i = 0; i < 60; i++) {
|
|
155
|
+
await backend.queryTraces({});
|
|
156
|
+
}
|
|
157
|
+
assert.strictEqual(callCount, 60);
|
|
158
|
+
// Logs query should also be rate limited
|
|
159
|
+
const result = await backend.queryLogs({});
|
|
160
|
+
assert.deepStrictEqual(result, []);
|
|
161
|
+
assert.strictEqual(callCount, 60, 'Should not make fetch call for logs when rate limited');
|
|
162
|
+
Date.now = originalDateNow;
|
|
163
|
+
});
|
|
164
|
+
it('should handle metrics query when rate limited', async () => {
|
|
165
|
+
const originalDateNow = Date.now;
|
|
166
|
+
let currentTime = 1000000;
|
|
167
|
+
Date.now = () => currentTime;
|
|
168
|
+
let callCount = 0;
|
|
169
|
+
globalThis.fetch = setupMock(async () => {
|
|
170
|
+
callCount++;
|
|
171
|
+
return {
|
|
172
|
+
ok: true,
|
|
173
|
+
json: async () => ({ data: { data: { results: [] } } }),
|
|
174
|
+
text: async () => '',
|
|
175
|
+
};
|
|
176
|
+
});
|
|
177
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
178
|
+
// Use all 60 tokens
|
|
179
|
+
for (let i = 0; i < 60; i++) {
|
|
180
|
+
await backend.queryTraces({});
|
|
181
|
+
}
|
|
182
|
+
assert.strictEqual(callCount, 60);
|
|
183
|
+
// Metrics query should also be rate limited
|
|
184
|
+
const result = await backend.queryMetrics({ metricName: 'test' });
|
|
185
|
+
assert.deepStrictEqual(result, []);
|
|
186
|
+
assert.strictEqual(callCount, 60, 'Should not make fetch call for metrics when rate limited');
|
|
187
|
+
Date.now = originalDateNow;
|
|
188
|
+
});
|
|
189
|
+
it('should refund single token correctly', async () => {
|
|
190
|
+
const originalDateNow = Date.now;
|
|
191
|
+
let currentTime = 1000000;
|
|
192
|
+
Date.now = () => currentTime;
|
|
193
|
+
const limiter = new TokenBucketRateLimiter(3, 1); // 3 tokens, 1/sec refill
|
|
194
|
+
// Use all tokens
|
|
195
|
+
limiter.tryConsume();
|
|
196
|
+
limiter.tryConsume();
|
|
197
|
+
limiter.tryConsume();
|
|
198
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0, 'All tokens should be used');
|
|
199
|
+
// Refund one token
|
|
200
|
+
limiter.refund();
|
|
201
|
+
assert.strictEqual(limiter.getAvailableTokens(), 1, 'Should have 1 token after refund');
|
|
202
|
+
// Refund again
|
|
203
|
+
limiter.refund();
|
|
204
|
+
assert.strictEqual(limiter.getAvailableTokens(), 2, 'Should have 2 tokens after second refund');
|
|
205
|
+
// Refund at max should not exceed max
|
|
206
|
+
limiter.refund();
|
|
207
|
+
limiter.refund(); // This should cap at max
|
|
208
|
+
assert.strictEqual(limiter.getAvailableTokens(), 3, 'Should cap at max tokens');
|
|
209
|
+
Date.now = originalDateNow;
|
|
210
|
+
});
|
|
211
|
+
it('should refund token when circuit breaker rejects request', async () => {
|
|
212
|
+
const originalDateNow = Date.now;
|
|
213
|
+
let currentTime = 1000000;
|
|
214
|
+
Date.now = () => currentTime;
|
|
215
|
+
let callCount = 0;
|
|
216
|
+
globalThis.fetch = setupMock(async () => {
|
|
217
|
+
callCount++;
|
|
218
|
+
// Simulate failures to open circuit breaker
|
|
219
|
+
const response = {
|
|
220
|
+
ok: false,
|
|
221
|
+
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
|
222
|
+
json: async () => ({}),
|
|
223
|
+
text: async () => 'Internal Server Error',
|
|
224
|
+
};
|
|
225
|
+
return response;
|
|
226
|
+
});
|
|
227
|
+
const backend = new SigNozApiBackend('https://signoz.example.com', 'test-key');
|
|
228
|
+
// Cause circuit breaker to open (default is 3 failures per constants.ts)
|
|
229
|
+
for (let i = 0; i < 3; i++) {
|
|
230
|
+
try {
|
|
231
|
+
await backend.queryTraces({});
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Expected to throw
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
assert.strictEqual(callCount, 3, 'Should have made 3 failing requests');
|
|
238
|
+
// Get initial token count - 60 tokens - 3 consumed = 57
|
|
239
|
+
// Now circuit breaker is open, subsequent requests should refund tokens
|
|
240
|
+
const result1 = await backend.queryTraces({});
|
|
241
|
+
assert.deepStrictEqual(result1, [], 'Should return empty when circuit breaker open');
|
|
242
|
+
// This should not consume more tokens because the token was refunded
|
|
243
|
+
const result2 = await backend.queryTraces({});
|
|
244
|
+
assert.deepStrictEqual(result2, [], 'Should return empty when circuit breaker open');
|
|
245
|
+
// No additional fetch calls should have been made
|
|
246
|
+
assert.strictEqual(callCount, 3, 'Should not make more fetch calls when circuit breaker open');
|
|
247
|
+
Date.now = originalDateNow;
|
|
248
|
+
});
|
|
249
|
+
// A2 Category 1: Rate Limiter Boundary Tests
|
|
250
|
+
describe('rate limiter boundary tests (A2)', () => {
|
|
251
|
+
it('should allow exactly MAX_TOKENS requests', async () => {
|
|
252
|
+
const MAX_TOKENS = 60;
|
|
253
|
+
const limiter = new TokenBucketRateLimiter(MAX_TOKENS, 1);
|
|
254
|
+
// Consume exactly MAX_TOKENS
|
|
255
|
+
for (let i = 0; i < MAX_TOKENS; i++) {
|
|
256
|
+
const allowed = limiter.tryConsume();
|
|
257
|
+
assert.strictEqual(allowed, true, `Request ${i + 1} should be allowed`);
|
|
258
|
+
}
|
|
259
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0, '60th request should leave 0 tokens');
|
|
260
|
+
});
|
|
261
|
+
it('should block the 61st request', async () => {
|
|
262
|
+
const MAX_TOKENS = 60;
|
|
263
|
+
const limiter = new TokenBucketRateLimiter(MAX_TOKENS, 1);
|
|
264
|
+
// Consume all tokens
|
|
265
|
+
for (let i = 0; i < MAX_TOKENS; i++) {
|
|
266
|
+
limiter.tryConsume();
|
|
267
|
+
}
|
|
268
|
+
// 61st request should fail
|
|
269
|
+
const allowed = limiter.tryConsume();
|
|
270
|
+
assert.strictEqual(allowed, false, '61st request should be blocked');
|
|
271
|
+
});
|
|
272
|
+
it('should refill tokens after elapsed time', async () => {
|
|
273
|
+
const originalDateNow = Date.now;
|
|
274
|
+
let currentTime = 1000000;
|
|
275
|
+
Date.now = () => currentTime;
|
|
276
|
+
try {
|
|
277
|
+
const limiter = new TokenBucketRateLimiter(60, 1); // 1 token per second
|
|
278
|
+
// Use all tokens
|
|
279
|
+
for (let i = 0; i < 60; i++) {
|
|
280
|
+
limiter.tryConsume();
|
|
281
|
+
}
|
|
282
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0);
|
|
283
|
+
// Advance time by 1 second - should get 1 token back
|
|
284
|
+
currentTime += 1000;
|
|
285
|
+
assert.strictEqual(limiter.getAvailableTokens(), 1, 'Should have 1 token after 1 second');
|
|
286
|
+
// Advance time by 5 more seconds - should have 6 tokens
|
|
287
|
+
currentTime += 5000;
|
|
288
|
+
assert.strictEqual(limiter.getAvailableTokens(), 6, 'Should have 6 tokens after 6 seconds');
|
|
289
|
+
}
|
|
290
|
+
finally {
|
|
291
|
+
Date.now = originalDateNow;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
it('should not add partial tokens for fractional seconds', async () => {
|
|
295
|
+
const originalDateNow = Date.now;
|
|
296
|
+
let currentTime = 1000000;
|
|
297
|
+
Date.now = () => currentTime;
|
|
298
|
+
try {
|
|
299
|
+
const limiter = new TokenBucketRateLimiter(60, 1);
|
|
300
|
+
// Use all tokens
|
|
301
|
+
for (let i = 0; i < 60; i++) {
|
|
302
|
+
limiter.tryConsume();
|
|
303
|
+
}
|
|
304
|
+
// Advance by 500ms - should NOT add a partial token
|
|
305
|
+
currentTime += 500;
|
|
306
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0, 'Should not have partial tokens at 500ms');
|
|
307
|
+
// Advance to 999ms - still no token
|
|
308
|
+
currentTime += 499;
|
|
309
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0, 'Should not have token at 999ms');
|
|
310
|
+
// Advance to 1000ms - now should have 1 token
|
|
311
|
+
currentTime += 1;
|
|
312
|
+
assert.strictEqual(limiter.getAvailableTokens(), 1, 'Should have 1 token at 1000ms');
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
Date.now = originalDateNow;
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
it('should cap refilled tokens at MAX_TOKENS', async () => {
|
|
319
|
+
const originalDateNow = Date.now;
|
|
320
|
+
let currentTime = 1000000;
|
|
321
|
+
Date.now = () => currentTime;
|
|
322
|
+
try {
|
|
323
|
+
const limiter = new TokenBucketRateLimiter(60, 1);
|
|
324
|
+
// Use 30 tokens (leave 30)
|
|
325
|
+
for (let i = 0; i < 30; i++) {
|
|
326
|
+
limiter.tryConsume();
|
|
327
|
+
}
|
|
328
|
+
assert.strictEqual(limiter.getAvailableTokens(), 30);
|
|
329
|
+
// Advance by 60 seconds - should cap at 60, not 90
|
|
330
|
+
currentTime += 60000;
|
|
331
|
+
assert.strictEqual(limiter.getAvailableTokens(), 60, 'Should cap at MAX_TOKENS');
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
Date.now = originalDateNow;
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
it('should handle exact exhaustion correctly', async () => {
|
|
338
|
+
const limiter = new TokenBucketRateLimiter(3, 1);
|
|
339
|
+
limiter.tryConsume();
|
|
340
|
+
limiter.tryConsume();
|
|
341
|
+
assert.strictEqual(limiter.getAvailableTokens(), 1, 'Should have 1 token left');
|
|
342
|
+
limiter.tryConsume();
|
|
343
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0, 'Should have exactly 0 tokens');
|
|
344
|
+
const allowed = limiter.tryConsume();
|
|
345
|
+
assert.strictEqual(allowed, false, 'Next request should fail');
|
|
346
|
+
});
|
|
347
|
+
it('should not overflow on large refill periods', async () => {
|
|
348
|
+
const originalDateNow = Date.now;
|
|
349
|
+
let currentTime = 1000000;
|
|
350
|
+
Date.now = () => currentTime;
|
|
351
|
+
try {
|
|
352
|
+
const limiter = new TokenBucketRateLimiter(60, 1);
|
|
353
|
+
// Use all tokens
|
|
354
|
+
for (let i = 0; i < 60; i++) {
|
|
355
|
+
limiter.tryConsume();
|
|
356
|
+
}
|
|
357
|
+
// Advance by a very long time (1 day)
|
|
358
|
+
currentTime += 86400000;
|
|
359
|
+
const tokens = limiter.getAvailableTokens();
|
|
360
|
+
assert.strictEqual(tokens, 60, 'Should cap at MAX_TOKENS, not overflow');
|
|
361
|
+
}
|
|
362
|
+
finally {
|
|
363
|
+
Date.now = originalDateNow;
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
it('should cap elapsed time to prevent precision loss (H1)', async () => {
|
|
367
|
+
const originalDateNow = Date.now;
|
|
368
|
+
let currentTime = 1000000;
|
|
369
|
+
Date.now = () => currentTime;
|
|
370
|
+
try {
|
|
371
|
+
// 10 tokens, 1/sec refill = max useful elapsed is 10 seconds
|
|
372
|
+
const limiter = new TokenBucketRateLimiter(10, 1);
|
|
373
|
+
// Use all tokens
|
|
374
|
+
for (let i = 0; i < 10; i++) {
|
|
375
|
+
limiter.tryConsume();
|
|
376
|
+
}
|
|
377
|
+
assert.strictEqual(limiter.getAvailableTokens(), 0);
|
|
378
|
+
// Advance by 30 days (extreme case for long-running process)
|
|
379
|
+
currentTime += 30 * 24 * 60 * 60 * 1000;
|
|
380
|
+
// Should still correctly cap at maxTokens, not have precision issues
|
|
381
|
+
const tokens = limiter.getAvailableTokens();
|
|
382
|
+
assert.strictEqual(tokens, 10, 'Should handle extreme elapsed times without precision loss');
|
|
383
|
+
}
|
|
384
|
+
finally {
|
|
385
|
+
Date.now = originalDateNow;
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
});
|
|
390
|
+
//# sourceMappingURL=signoz-api-rate-limiter.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signoz-api-rate-limiter.test.js","sourceRoot":"","sources":["../../src/backends/signoz-api-rate-limiter.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signoz-api-ssrf.test.d.ts","sourceRoot":"","sources":["../../src/backends/signoz-api-ssrf.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSRF protection tests for SigNoz API backend
|
|
3
|
+
* L-STYLE-2: Extracted from signoz-api.test.ts for maintainability
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import assert from 'node:assert';
|
|
7
|
+
import { SigNozApiBackend } from './signoz-api.js';
|
|
8
|
+
describe('SigNozApiBackend SSRF protection', () => {
|
|
9
|
+
it('should block localhost URL', () => {
|
|
10
|
+
const backend = new SigNozApiBackend('https://localhost/api', 'test-key');
|
|
11
|
+
// Backend with blocked URL will have empty baseUrl
|
|
12
|
+
const url = backend.getTraceUrl('trace-123');
|
|
13
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should have empty base for blocked localhost');
|
|
14
|
+
});
|
|
15
|
+
it('should block 127.0.0.1', () => {
|
|
16
|
+
const backend = new SigNozApiBackend('https://127.0.0.1/api', 'test-key');
|
|
17
|
+
const url = backend.getTraceUrl('trace-123');
|
|
18
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should have empty base for blocked 127.0.0.1');
|
|
19
|
+
});
|
|
20
|
+
it('should block IPv6 localhost ::1', () => {
|
|
21
|
+
const backend = new SigNozApiBackend('https://[::1]/api', 'test-key');
|
|
22
|
+
const url = backend.getTraceUrl('trace-123');
|
|
23
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block IPv6 localhost');
|
|
24
|
+
});
|
|
25
|
+
it('should block long-form IPv6 localhost', () => {
|
|
26
|
+
const backend = new SigNozApiBackend('https://[0:0:0:0:0:0:0:1]/api', 'test-key');
|
|
27
|
+
const url = backend.getTraceUrl('trace-123');
|
|
28
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block long-form IPv6 localhost');
|
|
29
|
+
});
|
|
30
|
+
it('should block IPv4-mapped IPv6 localhost', () => {
|
|
31
|
+
const backend = new SigNozApiBackend('https://[::ffff:127.0.0.1]/api', 'test-key');
|
|
32
|
+
const url = backend.getTraceUrl('trace-123');
|
|
33
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block IPv4-mapped localhost');
|
|
34
|
+
});
|
|
35
|
+
it('should block .localhost TLD', () => {
|
|
36
|
+
const backend = new SigNozApiBackend('https://myapp.localhost/api', 'test-key');
|
|
37
|
+
const url = backend.getTraceUrl('trace-123');
|
|
38
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block .localhost TLD');
|
|
39
|
+
});
|
|
40
|
+
it('should block private 192.168.x.x ranges', () => {
|
|
41
|
+
const backend = new SigNozApiBackend('https://192.168.1.1/api', 'test-key');
|
|
42
|
+
const url = backend.getTraceUrl('trace-123');
|
|
43
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block 192.168.x.x');
|
|
44
|
+
});
|
|
45
|
+
it('should block private 10.x.x.x ranges', () => {
|
|
46
|
+
const backend = new SigNozApiBackend('https://10.0.0.1/api', 'test-key');
|
|
47
|
+
const url = backend.getTraceUrl('trace-123');
|
|
48
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block 10.x.x.x');
|
|
49
|
+
});
|
|
50
|
+
it('should block private 172.16-31.x.x ranges', () => {
|
|
51
|
+
const backend = new SigNozApiBackend('https://172.16.0.1/api', 'test-key');
|
|
52
|
+
const url = backend.getTraceUrl('trace-123');
|
|
53
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block 172.16.x.x');
|
|
54
|
+
});
|
|
55
|
+
it('should block IPv6 unique local addresses (fc00::/7)', () => {
|
|
56
|
+
const backend = new SigNozApiBackend('https://[fc00::1]/api', 'test-key');
|
|
57
|
+
const url = backend.getTraceUrl('trace-123');
|
|
58
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block fc00:: ULA');
|
|
59
|
+
});
|
|
60
|
+
it('should block IPv6 link-local addresses (fe80::)', () => {
|
|
61
|
+
const backend = new SigNozApiBackend('https://[fe80::1]/api', 'test-key');
|
|
62
|
+
const url = backend.getTraceUrl('trace-123');
|
|
63
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block fe80:: link-local');
|
|
64
|
+
});
|
|
65
|
+
it('should block .local domain', () => {
|
|
66
|
+
const backend = new SigNozApiBackend('https://myhost.local/api', 'test-key');
|
|
67
|
+
const url = backend.getTraceUrl('trace-123');
|
|
68
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block .local domain');
|
|
69
|
+
});
|
|
70
|
+
it('should block .internal domain', () => {
|
|
71
|
+
const backend = new SigNozApiBackend('https://signoz.internal/api', 'test-key');
|
|
72
|
+
const url = backend.getTraceUrl('trace-123');
|
|
73
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block .internal domain');
|
|
74
|
+
});
|
|
75
|
+
it('should block .home.arpa domain', () => {
|
|
76
|
+
const backend = new SigNozApiBackend('https://router.home.arpa/api', 'test-key');
|
|
77
|
+
const url = backend.getTraceUrl('trace-123');
|
|
78
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block .home.arpa domain');
|
|
79
|
+
});
|
|
80
|
+
it('should allow valid external HTTPS URL', () => {
|
|
81
|
+
const backend = new SigNozApiBackend('https://signoz.example.com/api/', 'test-key');
|
|
82
|
+
const url = backend.getTraceUrl('trace-123');
|
|
83
|
+
assert.ok(url.includes('signoz.example.com'), 'Should allow valid external URL');
|
|
84
|
+
});
|
|
85
|
+
it('should block HTTP protocol', () => {
|
|
86
|
+
const backend = new SigNozApiBackend('http://signoz.example.com/api', 'test-key');
|
|
87
|
+
const url = backend.getTraceUrl('trace-123');
|
|
88
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block HTTP protocol');
|
|
89
|
+
});
|
|
90
|
+
// A2 Category 6: SSRF Advanced Tests
|
|
91
|
+
describe('advanced SSRF protection (A2)', () => {
|
|
92
|
+
it('should block URL-encoded localhost', () => {
|
|
93
|
+
// %6c%6f%63%61%6c%68%6f%73%74 = localhost
|
|
94
|
+
const backend = new SigNozApiBackend('https://%6c%6f%63%61%6c%68%6f%73%74/api', 'test-key');
|
|
95
|
+
const url = backend.getTraceUrl('trace-123');
|
|
96
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block URL-encoded localhost');
|
|
97
|
+
});
|
|
98
|
+
it('should block double-encoded localhost', () => {
|
|
99
|
+
// %256c = %l (double-encoded l)
|
|
100
|
+
const backend = new SigNozApiBackend('https://%256c%256f%256c%256f%256c%2568%256f%2573%2574/api', 'test-key');
|
|
101
|
+
const url = backend.getTraceUrl('trace-123');
|
|
102
|
+
// URL constructor may fail or return blocked URL
|
|
103
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block double-encoded localhost');
|
|
104
|
+
});
|
|
105
|
+
it('should block IPv6 compressed format for localhost', () => {
|
|
106
|
+
const backend = new SigNozApiBackend('https://[::ffff:7f00:1]/api', 'test-key');
|
|
107
|
+
const url = backend.getTraceUrl('trace-123');
|
|
108
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block ::ffff:7f00:1');
|
|
109
|
+
});
|
|
110
|
+
it('should block IPv6 with zone ID', () => {
|
|
111
|
+
// fe80::1%eth0 - link-local with zone ID
|
|
112
|
+
const backend = new SigNozApiBackend('https://[fe80::1%25eth0]/api', 'test-key');
|
|
113
|
+
const url = backend.getTraceUrl('trace-123');
|
|
114
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block IPv6 with zone ID');
|
|
115
|
+
});
|
|
116
|
+
it('should block decimal IP representation of localhost', () => {
|
|
117
|
+
// 2130706433 = 127.0.0.1 in decimal
|
|
118
|
+
const backend = new SigNozApiBackend('https://2130706433/api', 'test-key');
|
|
119
|
+
const url = backend.getTraceUrl('trace-123');
|
|
120
|
+
// May be parsed as hostname (not blocked by URL constructor)
|
|
121
|
+
// But our SSRF check should catch it if it resolves
|
|
122
|
+
assert.strictEqual(typeof url, 'string');
|
|
123
|
+
});
|
|
124
|
+
it('should block octal IP representation', () => {
|
|
125
|
+
// 0177.0.0.1 = 127.0.0.1 in octal
|
|
126
|
+
const backend = new SigNozApiBackend('https://0177.0.0.1/api', 'test-key');
|
|
127
|
+
const url = backend.getTraceUrl('trace-123');
|
|
128
|
+
// Octal notation may or may not be parsed by URL
|
|
129
|
+
assert.strictEqual(typeof url, 'string');
|
|
130
|
+
});
|
|
131
|
+
it('should block AWS metadata endpoint (169.254.169.254)', () => {
|
|
132
|
+
// M-INFO-1: Cloud metadata endpoints now blocked
|
|
133
|
+
const backend = new SigNozApiBackend('https://169.254.169.254/api', 'test-key');
|
|
134
|
+
const url = backend.getTraceUrl('trace-123');
|
|
135
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block AWS metadata endpoint');
|
|
136
|
+
});
|
|
137
|
+
it('should block GCP metadata endpoint', () => {
|
|
138
|
+
// .internal TLD is blocked
|
|
139
|
+
const backend = new SigNozApiBackend('https://metadata.google.internal/api', 'test-key');
|
|
140
|
+
const url = backend.getTraceUrl('trace-123');
|
|
141
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block GCP metadata');
|
|
142
|
+
});
|
|
143
|
+
it('should handle IPv4-mapped private ranges', () => {
|
|
144
|
+
// ::ffff:192.168.1.1 gets normalized by URL constructor to ::ffff:c0a8:101
|
|
145
|
+
// Current SSRF check uses string pattern that doesn't match this format
|
|
146
|
+
// Security note: Consider enhancing IPv4-mapped detection
|
|
147
|
+
const backend = new SigNozApiBackend('https://[::ffff:192.168.1.1]/api', 'test-key');
|
|
148
|
+
const url = backend.getTraceUrl('trace-123');
|
|
149
|
+
// Document current behavior - may not be blocked due to hex normalization
|
|
150
|
+
assert.strictEqual(typeof url, 'string');
|
|
151
|
+
});
|
|
152
|
+
it('should block link-local IPv4 addresses (169.254.0.0/16)', () => {
|
|
153
|
+
// M-INFO-1: Full link-local range now blocked
|
|
154
|
+
const backend = new SigNozApiBackend('https://169.254.1.1/api', 'test-key');
|
|
155
|
+
const url = backend.getTraceUrl('trace-123');
|
|
156
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block link-local addresses');
|
|
157
|
+
});
|
|
158
|
+
it('should block fd00:: IPv6 unique local', () => {
|
|
159
|
+
const backend = new SigNozApiBackend('https://[fd00::1]/api', 'test-key');
|
|
160
|
+
const url = backend.getTraceUrl('trace-123');
|
|
161
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block fd00:: ULA');
|
|
162
|
+
});
|
|
163
|
+
it('should handle empty hostname', () => {
|
|
164
|
+
// Empty hostname is invalid
|
|
165
|
+
try {
|
|
166
|
+
const backend = new SigNozApiBackend('https:///api', 'test-key');
|
|
167
|
+
const url = backend.getTraceUrl('trace-123');
|
|
168
|
+
assert.strictEqual(url, '/trace/trace-123');
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// URL constructor may throw for invalid URL
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
it('should handle username:password@ in URL', () => {
|
|
175
|
+
// Credentials in URL should not bypass SSRF check
|
|
176
|
+
const backend = new SigNozApiBackend('https://user:pass@localhost/api', 'test-key');
|
|
177
|
+
const url = backend.getTraceUrl('trace-123');
|
|
178
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block localhost with credentials');
|
|
179
|
+
});
|
|
180
|
+
it('should handle localhost with port', () => {
|
|
181
|
+
const backend = new SigNozApiBackend('https://localhost:8080/api', 'test-key');
|
|
182
|
+
const url = backend.getTraceUrl('trace-123');
|
|
183
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block localhost with port');
|
|
184
|
+
});
|
|
185
|
+
it('should block full 127.0.0.0/8 loopback range', () => {
|
|
186
|
+
// M-INFO-1: Full loopback range now blocked
|
|
187
|
+
const addresses = ['127.0.0.2', '127.1.1.1', '127.255.255.254'];
|
|
188
|
+
for (const addr of addresses) {
|
|
189
|
+
const backend = new SigNozApiBackend(`https://${addr}/api`, 'test-key');
|
|
190
|
+
const url = backend.getTraceUrl('trace-123');
|
|
191
|
+
assert.strictEqual(url, '/trace/trace-123', `Should block ${addr}`);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
it('should handle 0.0.0.0', () => {
|
|
195
|
+
const backend = new SigNozApiBackend('https://0.0.0.0/api', 'test-key');
|
|
196
|
+
const url = backend.getTraceUrl('trace-123');
|
|
197
|
+
assert.strictEqual(url, '/trace/trace-123', 'Should block 0.0.0.0');
|
|
198
|
+
});
|
|
199
|
+
it('should block Kubernetes internal domains', () => {
|
|
200
|
+
const internalDomains = [
|
|
201
|
+
'https://kubernetes.default.svc/api',
|
|
202
|
+
'https://kubernetes.default/api',
|
|
203
|
+
'https://service.namespace.svc.cluster.local/api',
|
|
204
|
+
];
|
|
205
|
+
for (const domain of internalDomains) {
|
|
206
|
+
const backend = new SigNozApiBackend(domain, 'test-key');
|
|
207
|
+
const url = backend.getTraceUrl('trace-123');
|
|
208
|
+
// Should block internal/local domains
|
|
209
|
+
if (domain.includes('.local')) {
|
|
210
|
+
assert.strictEqual(url, '/trace/trace-123', `Should block ${domain}`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
//# sourceMappingURL=signoz-api-ssrf.test.js.map
|