@stonyx/rest-server 0.2.1-beta.90 → 0.2.1-beta.91
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 +106 -55
- package/config/environment.js +32 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -3
- package/dist/main.js.map +1 -1
- package/dist/request.d.ts.map +1 -1
- package/dist/request.js +8 -4
- package/dist/request.js.map +1 -1
- package/dist/route-matching.d.ts +56 -1
- package/dist/route-matching.d.ts.map +1 -1
- package/dist/route-matching.js +58 -1
- package/dist/route-matching.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -79,59 +79,82 @@ Configuration is read from `stonyx/config` under `restServer`:
|
|
|
79
79
|
| `origin` | **String \| Array** | `'*'` | CORS origin(s) allowed |
|
|
80
80
|
| `methods` | **String** | `'GET,POST,PATCH,PUT,DELETE'` | CORS allowed methods |
|
|
81
81
|
| `enableHealthCheck` | **Boolean** | `true` | Register `GET /health` endpoint (disable via `REST_HEALTH_CHECK_DISABLE=true`) |
|
|
82
|
-
| `caseSensitiveRoutes` | **Boolean** | `true` | Match route paths case-sensitively. Disable via `REST_CASE_SENSITIVE_ROUTES=false`. See [
|
|
82
|
+
| `caseSensitiveRoutes` | **Boolean** | `true` | Match route paths case-sensitively. Disable via `REST_CASE_SENSITIVE_ROUTES=false`. See [Route Matching Strictness](#route-matching-strictness) — **disabling this re-opens a security hole**. |
|
|
83
|
+
| `strictRoutes` | **Boolean** | `true` | Match route paths strictly, so a trailing slash does not match a route registered without one. Disable via `REST_STRICT_ROUTES=false`. See [Route Matching Strictness](#route-matching-strictness) — **disabling this re-opens a security hole**, and note `GET /health/` now 404s. |
|
|
83
84
|
| `trustProxy` | **Boolean** | `false` | Trust reverse proxy headers (e.g. `X-Forwarded-Proto`). Enable via `REST_TRUST_PROXY=true` when running behind a load balancer such as AWS ALB/ELB to ensure correct protocol detection. |
|
|
84
85
|
| `statusMap` | **Object** | `{}` | Optional mapping of HTTP status codes to custom messages |
|
|
85
86
|
|
|
86
|
-
###
|
|
87
|
+
### Route Matching Strictness
|
|
87
88
|
|
|
88
|
-
Routes match **case-sensitively by default**.
|
|
89
|
-
|
|
90
|
-
`GET /users/Success` does not reach a `/success` handler registered inside it.
|
|
89
|
+
Routes match **case-sensitively and strictly by default**. Two settings, both
|
|
90
|
+
on, both applied at both express construction sites:
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
| axis | setting | example that no longer matches |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| casing | `case sensitive routing` | `GET /users/Success` -> does not reach `/success` |
|
|
95
|
+
| trailing slash | `strict routing` | `GET /users/success/` -> does not reach `/success` |
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
97
|
+
Read [What this does not do](#what-this-does-not-do) and
|
|
98
|
+
[Upgrading](#upgrading-behaviour-changes) before you rely on that. Two things
|
|
99
|
+
the table does not say: "does not reach the handler" is not the same as "404",
|
|
100
|
+
and one edge of the trailing-slash axis is **not** closed and cannot be.
|
|
101
|
+
|
|
102
|
+
This is deliberate and security-relevant. Express matches both case-insensitively
|
|
103
|
+
and slash-insensitively by default, which means any authorization written
|
|
104
|
+
against the request URL can be walked past by changing the case of the request,
|
|
105
|
+
or by appending one character:
|
|
100
106
|
|
|
101
107
|
```
|
|
102
|
-
GET /owners/angela
|
|
103
|
-
GET /OwNeRs/angela
|
|
104
|
-
|
|
108
|
+
GET /owners/angela -> 404 (correctly filtered)
|
|
109
|
+
GET /OwNeRs/angela -> 200 (full record) <- closed by case sensitive routing
|
|
110
|
+
GET /owners/angela/ -> 200 (full record) <- closed by strict routing
|
|
111
|
+
DELETE /ANIMALS/22 -> 204 (record destroyed)
|
|
112
|
+
DELETE /animals/22/ -> 204 (record destroyed)
|
|
105
113
|
```
|
|
106
114
|
|
|
107
115
|
The consumer's predicate is stricter than the router that dispatched the
|
|
108
116
|
request, so the router hands the handler a request the predicate would have
|
|
109
|
-
rejected.
|
|
110
|
-
the path a handler sees can only ever be the exact registered casing.
|
|
111
|
-
|
|
112
|
-
It does not close the asymmetry itself. Express exposes `case sensitive
|
|
113
|
-
routing` and `strict routing` as a pair of loose-by-default router settings and
|
|
114
|
-
this change sets only the first, so the identical bypass is still reachable by
|
|
115
|
-
appending a slash. Measured on this release against this repo's own fixture:
|
|
117
|
+
rejected. Measured against this repo's own fixture, before and after:
|
|
116
118
|
|
|
117
119
|
```
|
|
118
|
-
|
|
119
|
-
GET /private/failure
|
|
120
|
+
before after
|
|
121
|
+
GET /private/failure 505 505 (auth hook fires, request blocked)
|
|
122
|
+
GET /private/failure/ 200 404 (auth hook never fired; now a miss)
|
|
123
|
+
GET /private/FAILURE 200 200 (absorbed by /:id — see below)
|
|
120
124
|
```
|
|
121
125
|
|
|
122
|
-
|
|
123
|
-
the
|
|
124
|
-
|
|
125
|
-
[#50](https://github.com/abofs/stonyx-rest-server/issues/50)
|
|
126
|
-
here; it is a second consumer-visible behaviour change that needs its own flag
|
|
127
|
-
and its own release note.
|
|
128
|
-
|
|
129
|
-
**So do not drop a URL-normalizing defence you already have on the strength of
|
|
130
|
-
this section.** If your authorization compares `req.path` or `req.originalUrl`,
|
|
131
|
-
keep whatever normalization you have until #50 ships.
|
|
126
|
+
For a handler that authorizes on `req.path`, the path it sees can now only ever
|
|
127
|
+
be the exact registered spelling, in the exact registered casing, with no
|
|
128
|
+
trailing slash. That closes [#47](https://github.com/abofs/stonyx-rest-server/issues/47)
|
|
129
|
+
and [#50](https://github.com/abofs/stonyx-rest-server/issues/50).
|
|
132
130
|
|
|
133
131
|
#### What this does not do
|
|
134
132
|
|
|
133
|
+
**It does not close the trailing slash on a mount root, and no setting can.**
|
|
134
|
+
This is the one edge that remains open, so do not read the section above as
|
|
135
|
+
closing the class outright:
|
|
136
|
+
|
|
137
|
+
```
|
|
138
|
+
GET /public -> req.path '/' req.originalUrl '/public'
|
|
139
|
+
GET /public/ -> req.path '/' req.originalUrl '/public/'
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Express's router applies mount-prefix matching with `strict: false`
|
|
143
|
+
unconditionally (`router@2.2.0`; the file-and-line citation is in
|
|
144
|
+
[`docs/project-structure.md`](docs/project-structure.md) § *Strict routing
|
|
145
|
+
(#50)*), so both forms reach the mounted route class and both arrive with
|
|
146
|
+
`req.path === '/'`. A hook authorizing on `req.path` cannot tell them apart, so
|
|
147
|
+
for that hook there is no asymmetry to exploit. **A hook comparing
|
|
148
|
+
`req.originalUrl` still sees two different strings, and `strictRoutes` does not
|
|
149
|
+
change that.** If your authorization compares `req.originalUrl` rather than
|
|
150
|
+
`req.path`, keep whatever URL normalization you have.
|
|
151
|
+
|
|
152
|
+
No *setting* closes this, but the module can:
|
|
153
|
+
[#54](https://github.com/abofs/stonyx-rest-server/issues/54) tracks closing it
|
|
154
|
+
with a canonical-path check ahead of the `auth` hook. Until that ships, an
|
|
155
|
+
`originalUrl` hook is bypassed by one character — `GET /admin` denied,
|
|
156
|
+
`GET /admin/` reaching the route class's index handler.
|
|
157
|
+
|
|
135
158
|
**It does not normalize path *parameter values*.** If your `auth()` hook rejects
|
|
136
159
|
`params.id === 'restricted'`, then `GET /private/RESTRICTED` still reaches the
|
|
137
160
|
handler — the router matched the route correctly, and `restricted` and
|
|
@@ -148,36 +171,64 @@ repo's AC5 asserts exactly that. A class exposing `/orders/summary` alongside
|
|
|
148
171
|
database lookup. The param route's own `auth()` hook still runs, so this is an
|
|
149
172
|
expectation defect rather than a bypass — but plan for a reroute, not a 404.
|
|
150
173
|
|
|
151
|
-
|
|
152
|
-
|
|
174
|
+
Note the two axes differ here. A *trailing slash* is not absorbed by `/:id`,
|
|
175
|
+
because `/:id` is equally strict: `GET /private/failure/` misses `/failure` and
|
|
176
|
+
misses `/:id`, and is a true 404.
|
|
177
|
+
|
|
178
|
+
**It does not redirect or rewrite** mixed-case or trailing-slash requests to
|
|
179
|
+
their canonical form. Whether `/Users` is a typo to forgive or an attack to
|
|
180
|
+
reject is an application policy decision, and encoding it here would mint
|
|
181
|
+
another variant of the bug above.
|
|
153
182
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
183
|
+
#### Upgrading: behaviour changes
|
|
184
|
+
|
|
185
|
+
Both settings change which requests match, so both are consumer-visible.
|
|
186
|
+
|
|
187
|
+
**`GET /health/` now returns 404.** `GET /health` is unaffected. This is the
|
|
188
|
+
change most likely to page someone, and it is an **availability** problem rather
|
|
189
|
+
than a 404 you will read about in a log: if a Kubernetes liveness probe, an ELB
|
|
190
|
+
target-group health check or an uptime monitor is pointed at the trailing-slash
|
|
191
|
+
form, it starts failing and the deployment gets marked unhealthy and cycled.
|
|
192
|
+
This module emits no request logging, so the only symptom is the probe going
|
|
193
|
+
red. **Check your probe URLs before upgrading.**
|
|
194
|
+
|
|
195
|
+
Also affected:
|
|
196
|
+
|
|
197
|
+
- **Param routes.** `/resource/:id/` no longer matches. Any client calling
|
|
198
|
+
`/private/restricted/` gets a 404 where it previously got the param route.
|
|
199
|
+
- **Trailing-slash-normalizing proxies.** nginx `try_files`/`rewrite`, Apache
|
|
200
|
+
`DirectorySlash On` and some CDN edge rules append a slash; behind one of
|
|
201
|
+
those, every route stops matching at once.
|
|
202
|
+
- **Mount paths from filenames.** With `camelCaseRoutes` truthy, `phone-number.ts`
|
|
203
|
+
mounts at `/phoneNumber`, so `GET /phonenumber` returns 404; with it falsy,
|
|
204
|
+
`Users.ts` mounts at `/Users`, so `GET /users` returns 404.
|
|
205
|
+
|
|
206
|
+
A request that stops matching returns express's default `404 Cannot GET /x` with
|
|
207
|
+
no log line and no stack, so it looks like a deploy that dropped a route.
|
|
158
208
|
|
|
159
209
|
#### Opting out
|
|
160
210
|
|
|
211
|
+
Two separate flags, one per axis:
|
|
212
|
+
|
|
161
213
|
```bash
|
|
162
|
-
REST_CASE_SENSITIVE_ROUTES=false
|
|
214
|
+
REST_CASE_SENSITIVE_ROUTES=false # restores case-insensitive matching (#47)
|
|
215
|
+
REST_STRICT_ROUTES=false # restores trailing-slash tolerance (#50)
|
|
163
216
|
```
|
|
164
217
|
|
|
165
|
-
|
|
166
|
-
authorization in your application becomes bypassable by changing case. It
|
|
167
|
-
exists as a one-line remediation for an existing deployment, not as a
|
|
168
|
-
configuration to run on.
|
|
218
|
+
or equivalently `restServer: { caseSensitiveRoutes: false, strictRoutes: false }`.
|
|
169
219
|
|
|
170
|
-
|
|
171
|
-
|
|
220
|
+
**They are deliberately separate keys, and neither implies the other.** Slash
|
|
221
|
+
tolerance is a legitimate need — a health-check URL you cannot change today is
|
|
222
|
+
the common case. Casing tolerance almost never is. Folding them into one flag
|
|
223
|
+
would force anyone who needs the first to accept the second, which is why a
|
|
224
|
+
consumer who took the `#47` opt-out still has to set `REST_STRICT_ROUTES=false`
|
|
225
|
+
separately to keep trailing slashes working.
|
|
172
226
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
A request that stops matching returns express's default `404 Cannot GET /x` with
|
|
179
|
-
no log line and no stack, so it looks like a deploy that dropped a route. Set
|
|
180
|
-
the flag to restore service, then fix the client's casing and remove the flag.
|
|
227
|
+
**Each flag restores the corresponding vulnerability described above** — the
|
|
228
|
+
URL-based authorization in your application becomes bypassable along that axis
|
|
229
|
+
again. They exist as one-line remediations for an existing deployment, not as a
|
|
230
|
+
configuration to run on. Set the flag to restore service, then fix the client
|
|
231
|
+
and remove the flag.
|
|
181
232
|
|
|
182
233
|
### Running Behind a Load Balancer
|
|
183
234
|
|
package/config/environment.js
CHANGED
|
@@ -5,6 +5,7 @@ const {
|
|
|
5
5
|
REST_HEALTH_CHECK_DISABLE,
|
|
6
6
|
REST_PORT,
|
|
7
7
|
REST_REQUEST_PATH,
|
|
8
|
+
REST_STRICT_ROUTES,
|
|
8
9
|
REST_TRUST_PROXY
|
|
9
10
|
} = process.env;
|
|
10
11
|
|
|
@@ -31,6 +32,37 @@ const config = {
|
|
|
31
32
|
// the subprocess-based env isolation this repo does not yet have; any fix
|
|
32
33
|
// must keep a live assertion on this default.
|
|
33
34
|
caseSensitiveRoutes: REST_CASE_SENSITIVE_ROUTES !== 'false',
|
|
35
|
+
|
|
36
|
+
// Secure by default, same polarity and same reasoning as caseSensitiveRoutes
|
|
37
|
+
// above: routes match strictly, so a trailing slash cannot walk past a
|
|
38
|
+
// consumer's URL-based authorization (abofs/stonyx-rest-server#50).
|
|
39
|
+
//
|
|
40
|
+
// BEHAVIOUR CHANGE for consumers upgrading: `/health/` now returns 404, and
|
|
41
|
+
// param routes like `/resource/:id/` no longer match. Opt out with
|
|
42
|
+
// REST_STRICT_ROUTES=false only as a temporary remediation. It is a separate
|
|
43
|
+
// key from REST_CASE_SENSITIVE_ROUTES on purpose -- opting out of slash
|
|
44
|
+
// strictness must not silently re-open #47's case bypass.
|
|
45
|
+
//
|
|
46
|
+
// DELIBERATELY NOT PINNED in test/config/environment.ts -- do not "fix" this
|
|
47
|
+
// as part of abofs/stonyx-rest-server#43. Same trap as the key above, and now
|
|
48
|
+
// measured for both: pin `strictRoutes: true` in test/config/environment.ts
|
|
49
|
+
// AND invert this line to `=== 'true'`, and the suite reports 31 pass /
|
|
50
|
+
// 0 fail. A naive pin makes an insecure published default completely
|
|
51
|
+
// invisible to a green suite.
|
|
52
|
+
//
|
|
53
|
+
// Unpinned, inverting this line alone turns #50's AC1 and AC2 red (29/2).
|
|
54
|
+
// AC3 stays GREEN under that mutation, because AC3 sets `strictRoutes` on the
|
|
55
|
+
// config object directly and so guards src/route-matching.ts's READ rather
|
|
56
|
+
// than this default -- the two assertions cover different halves and neither
|
|
57
|
+
// subsumes the other.
|
|
58
|
+
//
|
|
59
|
+
// The cost is that the suite is ambient-sensitive here
|
|
60
|
+
// (`REST_STRICT_ROUTES=false pnpm test` => 29 pass / 2 fail), but it fails
|
|
61
|
+
// LOUDLY, so there is no false green. Closing #43 for either key needs
|
|
62
|
+
// subprocess-based env isolation this repo does not have; any fix must keep a
|
|
63
|
+
// live assertion on this default.
|
|
64
|
+
strictRoutes: REST_STRICT_ROUTES !== 'false',
|
|
65
|
+
|
|
34
66
|
enableHealthCheck: REST_HEALTH_CHECK_DISABLE !== 'true',
|
|
35
67
|
origin: REST_CORS_ORIGIN ?? '*',
|
|
36
68
|
methods: REST_CORS_METHODS ?? 'GET,POST,PATCH,PUT,DELETE',
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAiBA,OAAgB,EAAE,KAAK,OAAO,EAAoE,MAAM,SAAS,CAAC;AAKlH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC;IAE5B,GAAG,EAAG,OAAO,CAAC;IACd,MAAM,EAAG,MAAM,CAAC;;
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAiBA,OAAgB,EAAE,KAAK,OAAO,EAAoE,MAAM,SAAS,CAAC;AAKlH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAEnC,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,MAAM,CAAC,QAAQ,EAAE,UAAU,CAAC;IAE5B,GAAG,EAAG,OAAO,CAAC;IACd,MAAM,EAAG,MAAM,CAAC;;IAwBhB,MAAM,CAAC,KAAK,IAAI,IAAI;IAQd,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAerB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAelC,qBAAqB,IAAI,IAAI;IAW7B,UAAU,CAAC,iBAAiB,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI;CAarG"}
|
package/dist/main.js
CHANGED
|
@@ -29,11 +29,19 @@ export default class RestServer {
|
|
|
29
29
|
return RestServer.instance;
|
|
30
30
|
RestServer.instance = this;
|
|
31
31
|
this.api = express();
|
|
32
|
-
//
|
|
32
|
+
// Applies BOTH route-matching settings: case sensitive routing
|
|
33
|
+
// (abofs/stonyx-rest-server#47) and strict routing (#50). The two do not
|
|
34
|
+
// have the same role at this site:
|
|
35
|
+
// - #47: this call closes the mount segment (/PUBLIC/...). The matching
|
|
36
|
+
// call in Request's constructor closes sub-paths; both are required.
|
|
37
|
+
// - #50: this call closes exactly /health/, the only route registered
|
|
38
|
+
// directly on this app. It has NO security role for #50 -- do not
|
|
39
|
+
// describe it as having one. Router.prototype.use hardcodes
|
|
40
|
+
// `strict: false`, so mount segments are strict-immune, and the call in
|
|
41
|
+
// Request's constructor closes the trailing-slash bypass on its own.
|
|
33
42
|
// Must stay in the constructor: the router is materialized lazily on first
|
|
34
43
|
// route registration, so applying this after setupRouter() is silently
|
|
35
|
-
// ineffective.
|
|
36
|
-
// sub-paths -- see src/route-matching.ts for why both are required.
|
|
44
|
+
// ineffective. See src/route-matching.ts for the per-site split.
|
|
37
45
|
applyRouteMatching(this.api);
|
|
38
46
|
}
|
|
39
47
|
static close() {
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,OAA2F,MAAM,SAAS,CAAC;AAClH,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,GAAG,MAAM,YAAY,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,kBAAkB,MAAM,qBAAqB,CAAC;AAGrD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,CAAC,OAAO,OAAO,UAAU;IAC7B,MAAM,CAAC,QAAQ,CAAa;IAE5B,GAAG,CAAW;IACd,MAAM,CAAU;IAEhB;QACE,IAAI,UAAU,CAAC,QAAQ;YAAE,OAAO,UAAU,CAAC,QAAQ,CAAC;QACpD,UAAU,CAAC,QAAQ,GAAG,IAAI,CAAC;QAE3B,IAAI,CAAC,GAAG,GAAG,OAAO,EAAE,CAAC;QAErB,0EAA0E;QAC1E,2EAA2E;QAC3E,uEAAuE;QACvE,
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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"}
|
package/dist/request.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,OAAO,IAAI,cAAc,EAAE,KAAK,QAAQ,IAAI,eAAe,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAOlH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACnD,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AACtG,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;AAC3F,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC;AAE9F,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,SAAS,SAAmB;IAEnC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY;IASlD,MAAM,CAAC,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAWrE,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAG,aAAa,CAAC;IACjB,IAAI,CAAC,EAAE,WAAW,CAAC;;
|
|
1
|
+
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,OAAO,IAAI,cAAc,EAAE,KAAK,QAAQ,IAAI,eAAe,EAAE,KAAK,OAAO,EAAE,MAAM,SAAS,CAAC;AAOlH,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AACnD,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AACtG,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;AAC3F,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,GAAG,cAAc,EAAE,CAAC,CAAC,CAAC;AAE9F,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,SAAS,SAAmB;IAEnC,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,cAAc,GAAG,YAAY;IASlD,MAAM,CAAC,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAWrE,eAAe,EAAE,OAAO,CAAC;IACzB,QAAQ,EAAG,aAAa,CAAC;IACjB,IAAI,CAAC,EAAE,WAAW,CAAC;;IAmB3B,aAAa,IAAI,IAAI;CAqDtB"}
|
package/dist/request.js
CHANGED
|
@@ -28,10 +28,14 @@ export default class Request {
|
|
|
28
28
|
constructor() {
|
|
29
29
|
const api = express();
|
|
30
30
|
api.disable('x-powered-by');
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
31
|
+
// Applies BOTH route-matching settings: case sensitive routing
|
|
32
|
+
// (abofs/stonyx-rest-server#47) and strict routing (#50). For #47 this
|
|
33
|
+
// call closes sub-paths (/public/SUCCESS) and the parent's call closes the
|
|
34
|
+
// mount segment; for #50 THIS call closes the entire trailing-slash
|
|
35
|
+
// authorization bypass on its own, and the parent's call has no security
|
|
36
|
+
// role. Must stay in the constructor: registerCalls() materializes this
|
|
37
|
+
// router, and a set applied afterwards has no effect. The parent app's
|
|
38
|
+
// setting does not reach here -- see src/route-matching.ts.
|
|
35
39
|
applyRouteMatching(api);
|
|
36
40
|
this.expressInstance = api;
|
|
37
41
|
}
|
package/dist/request.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"AAAA,OAAO,OAA2F,MAAM,SAAS,CAAC;AAClH,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,kBAAkB,MAAM,qBAAqB,CAAC;AAErD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AAOnE,MAAM,CAAC,OAAO,OAAO,OAAO;IAC1B,MAAM,CAAC,SAAS,GAAG,eAAe,CAAC;IAEnC,MAAM,CAAC,QAAQ,CAAC,GAAmB;QACjC,MAAM,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC;QAC9B,MAAM,MAAM,GAAG,GAAyC,CAAC;QACzD,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,SAAS,CAAiB,CAAC;QAE9E,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QACvB,OAAO,MAAM,CAAC,SAAS,CAAiB,CAAC;IAC3C,CAAC;IAED,MAAM,CAAC,kBAAkB,CAAC,GAAoB,EAAE,MAAc;QAC5D,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,EAAE,SAAS,IAAI,EAAE,CAAC;QACrD,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QAExC,IAAI,OAAO,EAAE,CAAC;YACZ,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACnC,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,eAAe,CAAU;IACzB,QAAQ,CAAiB;IAGzB;QACE,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;QACtB,GAAG,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC;QAE5B,
|
|
1
|
+
{"version":3,"file":"request.js","sourceRoot":"","sources":["../src/request.ts"],"names":[],"mappings":"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"}
|
package/dist/route-matching.d.ts
CHANGED
|
@@ -29,7 +29,62 @@ import type { Express } from 'express';
|
|
|
29
29
|
*
|
|
30
30
|
* Note `express({ caseSensitive: true })` does NOT work: express 5's
|
|
31
31
|
* `createApplication()` takes zero arguments and forwards nothing. The app
|
|
32
|
-
* setting is the only mechanism.
|
|
32
|
+
* setting is the only mechanism. The same is true of `strict`.
|
|
33
|
+
*
|
|
34
|
+
* ---
|
|
35
|
+
*
|
|
36
|
+
* `strict routing` (abofs/stonyx-rest-server#50) closes the same class of
|
|
37
|
+
* authorization bypass for a TRAILING SLASH: `GET /private/failure` was denied
|
|
38
|
+
* by a consumer's auth hook while `GET /private/failure/` reached the guarded
|
|
39
|
+
* handler, because the hook compares `req.path` against `/failure`.
|
|
40
|
+
*
|
|
41
|
+
* It is a SEPARATE key from `caseSensitiveRoutes`, not a rename and not a
|
|
42
|
+
* reuse. Coupling them would force any consumer who legitimately needs
|
|
43
|
+
* trailing-slash tolerance -- a load balancer probing `/health/`, a
|
|
44
|
+
* slash-normalizing proxy -- to re-open #47's case bypass to get it. Case
|
|
45
|
+
* insensitivity is almost never intentional; slash tolerance frequently is.
|
|
46
|
+
*
|
|
47
|
+
* The two settings do NOT share the #47 split above, and this is the one thing
|
|
48
|
+
* not to carry across from that fix. For `strict routing`:
|
|
49
|
+
*
|
|
50
|
+
* - The CHILD site (Request's constructor) closes the entire security
|
|
51
|
+
* defect on its own. The parent site does nothing for it.
|
|
52
|
+
* - The PARENT site (RestServer's constructor) closes exactly one thing in
|
|
53
|
+
* this repo: `/health/`, the only route registered directly on the parent
|
|
54
|
+
* app. It has no security role here; do not describe it as having one.
|
|
55
|
+
*
|
|
56
|
+
* The cause is concrete: `Router.prototype.use` hardcodes `strict: false` (and
|
|
57
|
+
* `end: false`), so mount segments are structurally strict-immune. That is the
|
|
58
|
+
* OPPOSITE of `sensitive`, which `use()` DOES forward, and which is why #47's
|
|
59
|
+
* parent site closed `/PUBLIC/...`. The version-pinned file-and-line citation
|
|
60
|
+
* for that upstream behaviour is deliberately kept in ONE place --
|
|
61
|
+
* `docs/project-structure.md`, section "Strict routing (#50)" -- so a router
|
|
62
|
+
* upgrade invalidates one line rather than five. Both sites still get both
|
|
63
|
+
* settings (they share this function), but the justification differs and the
|
|
64
|
+
* tests are built on the measured split, not on the analogy.
|
|
65
|
+
*
|
|
66
|
+
* Consequence worth stating so nobody expects this setting to cover it: the
|
|
67
|
+
* mount-segment trailing slash (`/public/`) cannot be closed by an express
|
|
68
|
+
* SETTING. For both `/public` and `/public/` the mounted sub-app receives
|
|
69
|
+
* `req.path === '/'`, so a `req.path` auth hook sees no difference, and a test
|
|
70
|
+
* asserting `/public/` -> 404 could never pass.
|
|
71
|
+
*
|
|
72
|
+
* That is NOT the same as saying the edge is harmless or unclosable.
|
|
73
|
+
* `req.originalUrl` does differ, and a hook authorizing on it is bypassed by
|
|
74
|
+
* one character -- measured: `GET /admin` -> 401, `GET /admin/` -> 200 with the
|
|
75
|
+
* guarded handler running unauthenticated. That is a live bypass of the same
|
|
76
|
+
* class as #47 and #50, and it IS closable by this module: a canonical-path
|
|
77
|
+
* check ahead of the `auth` call in `Request.registerCalls()`, or opt-in
|
|
78
|
+
* normalizing middleware. It is tracked as abofs/stonyx-rest-server#54. Do not
|
|
79
|
+
* close it here, and do not read this note as saying it cannot be closed.
|
|
80
|
+
*
|
|
81
|
+
* Both guards are `!== false` for the same reason: these flags default to the
|
|
82
|
+
* truthy direction, so a truthy check fails OPEN for a consumer whose shipped
|
|
83
|
+
* config predates the key. Both are also asserted at the unit tier for BOTH
|
|
84
|
+
* failure shapes -- key present-and-`undefined` and key absent as an own
|
|
85
|
+
* property -- in `test/unit/request-test.ts` (#47's AC6, #50's AC3). The
|
|
86
|
+
* integration tier cannot see either: with the shipped default `true`, a
|
|
87
|
+
* fail-open guard leaves every integration assertion green.
|
|
33
88
|
*/
|
|
34
89
|
export default function applyRouteMatching(api: Express): void;
|
|
35
90
|
//# sourceMappingURL=route-matching.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-matching.d.ts","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC
|
|
1
|
+
{"version":3,"file":"route-matching.d.ts","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGvC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAG7D"}
|
package/dist/route-matching.js
CHANGED
|
@@ -29,10 +29,67 @@ import config from 'stonyx/config';
|
|
|
29
29
|
*
|
|
30
30
|
* Note `express({ caseSensitive: true })` does NOT work: express 5's
|
|
31
31
|
* `createApplication()` takes zero arguments and forwards nothing. The app
|
|
32
|
-
* setting is the only mechanism.
|
|
32
|
+
* setting is the only mechanism. The same is true of `strict`.
|
|
33
|
+
*
|
|
34
|
+
* ---
|
|
35
|
+
*
|
|
36
|
+
* `strict routing` (abofs/stonyx-rest-server#50) closes the same class of
|
|
37
|
+
* authorization bypass for a TRAILING SLASH: `GET /private/failure` was denied
|
|
38
|
+
* by a consumer's auth hook while `GET /private/failure/` reached the guarded
|
|
39
|
+
* handler, because the hook compares `req.path` against `/failure`.
|
|
40
|
+
*
|
|
41
|
+
* It is a SEPARATE key from `caseSensitiveRoutes`, not a rename and not a
|
|
42
|
+
* reuse. Coupling them would force any consumer who legitimately needs
|
|
43
|
+
* trailing-slash tolerance -- a load balancer probing `/health/`, a
|
|
44
|
+
* slash-normalizing proxy -- to re-open #47's case bypass to get it. Case
|
|
45
|
+
* insensitivity is almost never intentional; slash tolerance frequently is.
|
|
46
|
+
*
|
|
47
|
+
* The two settings do NOT share the #47 split above, and this is the one thing
|
|
48
|
+
* not to carry across from that fix. For `strict routing`:
|
|
49
|
+
*
|
|
50
|
+
* - The CHILD site (Request's constructor) closes the entire security
|
|
51
|
+
* defect on its own. The parent site does nothing for it.
|
|
52
|
+
* - The PARENT site (RestServer's constructor) closes exactly one thing in
|
|
53
|
+
* this repo: `/health/`, the only route registered directly on the parent
|
|
54
|
+
* app. It has no security role here; do not describe it as having one.
|
|
55
|
+
*
|
|
56
|
+
* The cause is concrete: `Router.prototype.use` hardcodes `strict: false` (and
|
|
57
|
+
* `end: false`), so mount segments are structurally strict-immune. That is the
|
|
58
|
+
* OPPOSITE of `sensitive`, which `use()` DOES forward, and which is why #47's
|
|
59
|
+
* parent site closed `/PUBLIC/...`. The version-pinned file-and-line citation
|
|
60
|
+
* for that upstream behaviour is deliberately kept in ONE place --
|
|
61
|
+
* `docs/project-structure.md`, section "Strict routing (#50)" -- so a router
|
|
62
|
+
* upgrade invalidates one line rather than five. Both sites still get both
|
|
63
|
+
* settings (they share this function), but the justification differs and the
|
|
64
|
+
* tests are built on the measured split, not on the analogy.
|
|
65
|
+
*
|
|
66
|
+
* Consequence worth stating so nobody expects this setting to cover it: the
|
|
67
|
+
* mount-segment trailing slash (`/public/`) cannot be closed by an express
|
|
68
|
+
* SETTING. For both `/public` and `/public/` the mounted sub-app receives
|
|
69
|
+
* `req.path === '/'`, so a `req.path` auth hook sees no difference, and a test
|
|
70
|
+
* asserting `/public/` -> 404 could never pass.
|
|
71
|
+
*
|
|
72
|
+
* That is NOT the same as saying the edge is harmless or unclosable.
|
|
73
|
+
* `req.originalUrl` does differ, and a hook authorizing on it is bypassed by
|
|
74
|
+
* one character -- measured: `GET /admin` -> 401, `GET /admin/` -> 200 with the
|
|
75
|
+
* guarded handler running unauthenticated. That is a live bypass of the same
|
|
76
|
+
* class as #47 and #50, and it IS closable by this module: a canonical-path
|
|
77
|
+
* check ahead of the `auth` call in `Request.registerCalls()`, or opt-in
|
|
78
|
+
* normalizing middleware. It is tracked as abofs/stonyx-rest-server#54. Do not
|
|
79
|
+
* close it here, and do not read this note as saying it cannot be closed.
|
|
80
|
+
*
|
|
81
|
+
* Both guards are `!== false` for the same reason: these flags default to the
|
|
82
|
+
* truthy direction, so a truthy check fails OPEN for a consumer whose shipped
|
|
83
|
+
* config predates the key. Both are also asserted at the unit tier for BOTH
|
|
84
|
+
* failure shapes -- key present-and-`undefined` and key absent as an own
|
|
85
|
+
* property -- in `test/unit/request-test.ts` (#47's AC6, #50's AC3). The
|
|
86
|
+
* integration tier cannot see either: with the shipped default `true`, a
|
|
87
|
+
* fail-open guard leaves every integration assertion green.
|
|
33
88
|
*/
|
|
34
89
|
export default function applyRouteMatching(api) {
|
|
35
90
|
if (config.restServer?.caseSensitiveRoutes !== false)
|
|
36
91
|
api.set('case sensitive routing', true);
|
|
92
|
+
if (config.restServer?.strictRoutes !== false)
|
|
93
|
+
api.set('strict routing', true);
|
|
37
94
|
}
|
|
38
95
|
//# sourceMappingURL=route-matching.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-matching.js","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,eAAe,CAAC;AAEnC
|
|
1
|
+
{"version":3,"file":"route-matching.js","sourceRoot":"","sources":["../src/route-matching.ts"],"names":[],"mappings":"AACA,OAAO,MAAM,MAAM,eAAe,CAAC;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsFG;AACH,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,GAAY;IACrD,IAAI,MAAM,CAAC,UAAU,EAAE,mBAAmB,KAAK,KAAK;QAAE,GAAG,CAAC,GAAG,CAAC,wBAAwB,EAAE,IAAI,CAAC,CAAC;IAC9F,IAAI,MAAM,CAAC,UAAU,EAAE,YAAY,KAAK,KAAK;QAAE,GAAG,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC,CAAC;AACjF,CAAC"}
|