@@ -4,6 +4,10 @@ import {createMetricsMiddleware} from '@fluxer/hono/src/middleware/Metrics';
44import { Hono } from 'hono' ;
55import { describe , expect , test } from 'vitest' ;
66
7+ function requestMetrics ( app : Hono , remoteAddress = '127.0.0.1' ) {
8+ return app . request ( '/_metrics' , undefined , { incoming : { socket : { remoteAddress} } } ) ;
9+ }
10+
711function createTestApp ( ) {
812 const { middleware, metricsHandler, state} = createMetricsMiddleware ( 'test' ) ;
913 const app = new Hono ( ) ;
@@ -24,7 +28,7 @@ describe('Metrics Middleware', () => {
2428 const { app} = createTestApp ( ) ;
2529 await app . request ( '/users' ) ;
2630 await app . request ( '/users' ) ;
27- const res = await app . request ( '/_metrics' ) ;
31+ const res = await requestMetrics ( app ) ;
2832 const body = await res . text ( ) ;
2933 expect ( body ) . toContain ( 'fluxer_test_http_requests_total{method="GET",status="2xx"} 2' ) ;
3034 } ) ;
@@ -33,7 +37,7 @@ describe('Metrics Middleware', () => {
3337 const { app} = createTestApp ( ) ;
3438 await app . request ( '/users' ) ;
3539 await app . request ( '/users' , { method : 'POST' } ) ;
36- const res = await app . request ( '/_metrics' ) ;
40+ const res = await requestMetrics ( app ) ;
3741 const body = await res . text ( ) ;
3842 expect ( body ) . toContain ( 'fluxer_test_http_requests_total{method="GET",status="2xx"} 1' ) ;
3943 expect ( body ) . toContain ( 'fluxer_test_http_requests_total{method="POST",status="2xx"} 1' ) ;
@@ -44,7 +48,7 @@ describe('Metrics Middleware', () => {
4448 await app . request ( '/users' ) ;
4549 await app . request ( '/bad' ) ;
4650 await app . request ( '/error' ) ;
47- const res = await app . request ( '/_metrics' ) ;
51+ const res = await requestMetrics ( app ) ;
4852 const body = await res . text ( ) ;
4953 expect ( body ) . toContain ( 'status="2xx"' ) ;
5054 expect ( body ) . toContain ( 'status="4xx"' ) ;
@@ -57,15 +61,15 @@ describe('Metrics Middleware', () => {
5761 const { app} = createTestApp ( ) ;
5862 await app . request ( '/error' ) ;
5963 await app . request ( '/error' ) ;
60- const res = await app . request ( '/_metrics' ) ;
64+ const res = await requestMetrics ( app ) ;
6165 const body = await res . text ( ) ;
6266 expect ( body ) . toContain ( 'fluxer_test_http_errors_total{method="GET"} 2' ) ;
6367 } ) ;
6468
6569 test ( 'does not count 4xx as errors' , async ( ) => {
6670 const { app} = createTestApp ( ) ;
6771 await app . request ( '/bad' ) ;
68- const res = await app . request ( '/_metrics' ) ;
72+ const res = await requestMetrics ( app ) ;
6973 const body = await res . text ( ) ;
7074 expect ( body ) . not . toContain ( 'fluxer_test_http_errors_total{method="GET"}' ) ;
7175 } ) ;
@@ -75,7 +79,7 @@ describe('Metrics Middleware', () => {
7579 test ( 'records request duration' , async ( ) => {
7680 const { app} = createTestApp ( ) ;
7781 await app . request ( '/users' ) ;
78- const res = await app . request ( '/_metrics' ) ;
82+ const res = await requestMetrics ( app ) ;
7983 const body = await res . text ( ) ;
8084 expect ( body ) . toContain ( 'fluxer_test_http_request_duration_seconds_count 1' ) ;
8185 expect ( body ) . toContain ( 'fluxer_test_http_request_duration_seconds_sum' ) ;
@@ -88,7 +92,7 @@ describe('Metrics Middleware', () => {
8892 await app . request ( '/users' ) ;
8993 await app . request ( '/users' ) ;
9094 await app . request ( '/users' ) ;
91- const res = await app . request ( '/_metrics' ) ;
95+ const res = await requestMetrics ( app ) ;
9296 const body = await res . text ( ) ;
9397 expect ( body ) . toContain ( 'fluxer_test_http_request_duration_seconds_count 3' ) ;
9498 } ) ;
@@ -97,7 +101,7 @@ describe('Metrics Middleware', () => {
97101 describe ( 'uptime gauge' , ( ) => {
98102 test ( 'reports uptime in seconds' , async ( ) => {
99103 const { app} = createTestApp ( ) ;
100- const res = await app . request ( '/_metrics' ) ;
104+ const res = await requestMetrics ( app ) ;
101105 const body = await res . text ( ) ;
102106 expect ( body ) . toContain ( '# TYPE fluxer_test_uptime_seconds gauge' ) ;
103107 expect ( body ) . toMatch ( / f l u x e r _ t e s t _ u p t i m e _ s e c o n d s \d / ) ;
@@ -109,30 +113,30 @@ describe('Metrics Middleware', () => {
109113 const { app} = createTestApp ( ) ;
110114 await app . request ( '/_health' ) ;
111115 await app . request ( '/_health' ) ;
112- const res = await app . request ( '/_metrics' ) ;
116+ const res = await requestMetrics ( app ) ;
113117 const body = await res . text ( ) ;
114118 expect ( body ) . not . toContain ( 'method="GET",status="2xx"' ) ;
115119 } ) ;
116120
117121 test ( 'skips /_healthz requests' , async ( ) => {
118122 const { app} = createTestApp ( ) ;
119123 await app . request ( '/_healthz' ) ;
120- const res = await app . request ( '/_metrics' ) ;
124+ const res = await requestMetrics ( app ) ;
121125 const body = await res . text ( ) ;
122126 expect ( body ) . not . toContain ( 'method="GET",status="2xx"' ) ;
123127 } ) ;
124128
125129 test ( 'skips /_metrics requests' , async ( ) => {
126130 const { app} = createTestApp ( ) ;
127- const res = await app . request ( '/_metrics' ) ;
131+ const res = await requestMetrics ( app ) ;
128132 const body = await res . text ( ) ;
129133 expect ( body ) . not . toContain ( 'method="GET",status="2xx"' ) ;
130134 } ) ;
131135
132136 test ( 'does not skip normal paths' , async ( ) => {
133137 const { app} = createTestApp ( ) ;
134138 await app . request ( '/users' ) ;
135- const res = await app . request ( '/_metrics' ) ;
139+ const res = await requestMetrics ( app ) ;
136140 const body = await res . text ( ) ;
137141 expect ( body ) . toContain ( 'method="GET",status="2xx"' ) ;
138142 } ) ;
@@ -141,19 +145,19 @@ describe('Metrics Middleware', () => {
141145 describe ( 'metrics endpoint' , ( ) => {
142146 test ( 'returns correct content type' , async ( ) => {
143147 const { app} = createTestApp ( ) ;
144- const res = await app . request ( '/_metrics' ) ;
148+ const res = await requestMetrics ( app ) ;
145149 expect ( res . headers . get ( 'Content-Type' ) ) . toBe ( 'text/plain; version=0.0.4; charset=utf-8' ) ;
146150 } ) ;
147151
148152 test ( 'returns 200 status' , async ( ) => {
149153 const { app} = createTestApp ( ) ;
150- const res = await app . request ( '/_metrics' ) ;
154+ const res = await requestMetrics ( app ) ;
151155 expect ( res . status ) . toBe ( 200 ) ;
152156 } ) ;
153157
154158 test ( 'includes HELP and TYPE annotations' , async ( ) => {
155159 const { app} = createTestApp ( ) ;
156- const res = await app . request ( '/_metrics' ) ;
160+ const res = await requestMetrics ( app ) ;
157161 const body = await res . text ( ) ;
158162 expect ( body ) . toContain ( '# HELP fluxer_test_http_requests_total Total HTTP requests' ) ;
159163 expect ( body ) . toContain ( '# TYPE fluxer_test_http_requests_total counter' ) ;
@@ -167,13 +171,74 @@ describe('Metrics Middleware', () => {
167171
168172 test ( 'renders default counter value when no requests made' , async ( ) => {
169173 const { app} = createTestApp ( ) ;
170- const res = await app . request ( '/_metrics' ) ;
174+ const res = await requestMetrics ( app ) ;
171175 const body = await res . text ( ) ;
172176 expect ( body ) . toContain ( 'fluxer_test_http_requests_total 0' ) ;
173177 expect ( body ) . toContain ( 'fluxer_test_http_errors_total 0' ) ;
174178 } ) ;
175179 } ) ;
176180
181+ describe ( 'loopback restriction' , ( ) => {
182+ test ( 'serves metrics to an IPv4 loopback peer' , async ( ) => {
183+ const { app} = createTestApp ( ) ;
184+ const res = await requestMetrics ( app , '127.0.0.1' ) ;
185+ expect ( res . status ) . toBe ( 200 ) ;
186+ } ) ;
187+
188+ test ( 'serves metrics to any 127.0.0.0/8 peer' , async ( ) => {
189+ const { app} = createTestApp ( ) ;
190+ const res = await requestMetrics ( app , '127.0.0.2' ) ;
191+ expect ( res . status ) . toBe ( 200 ) ;
192+ } ) ;
193+
194+ test ( 'serves metrics to an IPv6 loopback peer' , async ( ) => {
195+ const { app} = createTestApp ( ) ;
196+ const res = await requestMetrics ( app , '::1' ) ;
197+ expect ( res . status ) . toBe ( 200 ) ;
198+ } ) ;
199+
200+ test ( 'serves metrics to an IPv4-mapped loopback peer' , async ( ) => {
201+ const { app} = createTestApp ( ) ;
202+ const res = await requestMetrics ( app , '::ffff:127.0.0.1' ) ;
203+ expect ( res . status ) . toBe ( 200 ) ;
204+ } ) ;
205+
206+ test ( 'rejects a public peer' , async ( ) => {
207+ const { app} = createTestApp ( ) ;
208+ const res = await requestMetrics ( app , '8.8.8.8' ) ;
209+ expect ( res . status ) . toBe ( 403 ) ;
210+ expect ( await res . text ( ) ) . toBe ( 'FORBIDDEN' ) ;
211+ } ) ;
212+
213+ test ( 'rejects a container network peer' , async ( ) => {
214+ const { app} = createTestApp ( ) ;
215+ const res = await requestMetrics ( app , '172.18.0.4' ) ;
216+ expect ( res . status ) . toBe ( 403 ) ;
217+ } ) ;
218+
219+ test ( 'rejects a request with no peer address' , async ( ) => {
220+ const { app} = createTestApp ( ) ;
221+ const res = await app . request ( '/_metrics' , undefined , { incoming : { socket : { } } } ) ;
222+ expect ( res . status ) . toBe ( 403 ) ;
223+ } ) ;
224+
225+ test ( 'rejects a request with no node bindings' , async ( ) => {
226+ const { app} = createTestApp ( ) ;
227+ const res = await app . request ( '/_metrics' , undefined , { } ) ;
228+ expect ( res . status ) . toBe ( 403 ) ;
229+ } ) ;
230+
231+ test ( 'ignores a forged x-forwarded-for header' , async ( ) => {
232+ const { app} = createTestApp ( ) ;
233+ const res = await app . request (
234+ '/_metrics' ,
235+ { headers : { 'x-forwarded-for' : '127.0.0.1' } } ,
236+ { incoming : { socket : { remoteAddress : '203.0.113.9' } } } ,
237+ ) ;
238+ expect ( res . status ) . toBe ( 403 ) ;
239+ } ) ;
240+ } ) ;
241+
177242 describe ( 'service name prefix' , ( ) => {
178243 test ( 'uses provided service name in metric names' , async ( ) => {
179244 const { middleware, metricsHandler} = createMetricsMiddleware ( 'gateway' ) ;
@@ -182,7 +247,7 @@ describe('Metrics Middleware', () => {
182247 app . get ( '/_metrics' , metricsHandler ) ;
183248 app . get ( '/test' , ( c ) => c . json ( { ok : true } ) ) ;
184249 await app . request ( '/test' ) ;
185- const res = await app . request ( '/_metrics' ) ;
250+ const res = await requestMetrics ( app ) ;
186251 const body = await res . text ( ) ;
187252 expect ( body ) . toContain ( 'fluxer_gateway_http_requests_total' ) ;
188253 expect ( body ) . toContain ( 'fluxer_gateway_http_request_duration_seconds' ) ;
0 commit comments