oidc-spa 8.5.1 → 8.5.3
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/core/createOidc.js +1 -1
- package/core/tokenExfiltrationDefense.js +87 -70
- package/core/tokenExfiltrationDefense.js.map +1 -1
- package/core/tokenPlaceholderSubstitution.d.ts +4 -1
- package/core/tokenPlaceholderSubstitution.js +14 -10
- package/core/tokenPlaceholderSubstitution.js.map +1 -1
- package/esm/core/createOidc.js +1 -1
- package/esm/core/tokenExfiltrationDefense.js +87 -70
- package/esm/core/tokenExfiltrationDefense.js.map +1 -1
- package/esm/core/tokenPlaceholderSubstitution.d.ts +4 -1
- package/esm/core/tokenPlaceholderSubstitution.js +14 -10
- package/esm/core/tokenPlaceholderSubstitution.js.map +1 -1
- package/package.json +1 -1
- package/src/core/tokenExfiltrationDefense.ts +100 -92
- package/src/core/tokenPlaceholderSubstitution.ts +19 -10
|
@@ -51,7 +51,10 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
51
51
|
|
|
52
52
|
const headers = new Headers();
|
|
53
53
|
request.headers.forEach((value, key) => {
|
|
54
|
-
const nextValue = substitutePlaceholderByRealToken(
|
|
54
|
+
const nextValue = substitutePlaceholderByRealToken({
|
|
55
|
+
text: value,
|
|
56
|
+
doEncodeUriComponent: false
|
|
57
|
+
});
|
|
55
58
|
|
|
56
59
|
if (nextValue !== value) {
|
|
57
60
|
didSubstitute = true;
|
|
@@ -77,7 +80,10 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
if (typeof init.body === "string") {
|
|
80
|
-
body = substitutePlaceholderByRealToken(
|
|
83
|
+
body = substitutePlaceholderByRealToken({
|
|
84
|
+
text: init.body,
|
|
85
|
+
doEncodeUriComponent: false
|
|
86
|
+
});
|
|
81
87
|
|
|
82
88
|
if (init.body !== body) {
|
|
83
89
|
didSubstitute = true;
|
|
@@ -91,7 +97,10 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
91
97
|
const next = new URLSearchParams();
|
|
92
98
|
|
|
93
99
|
init.body.forEach((value, key) => {
|
|
94
|
-
const nextValue = substitutePlaceholderByRealToken(
|
|
100
|
+
const nextValue = substitutePlaceholderByRealToken({
|
|
101
|
+
text: value,
|
|
102
|
+
doEncodeUriComponent: false
|
|
103
|
+
});
|
|
95
104
|
|
|
96
105
|
if (nextValue !== value) {
|
|
97
106
|
didUrlSearchParamsSubstitute = true;
|
|
@@ -115,7 +124,10 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
115
124
|
|
|
116
125
|
init.body.forEach((value, key) => {
|
|
117
126
|
if (typeof value === "string") {
|
|
118
|
-
const nextValue = substitutePlaceholderByRealToken(
|
|
127
|
+
const nextValue = substitutePlaceholderByRealToken({
|
|
128
|
+
text: value,
|
|
129
|
+
doEncodeUriComponent: false
|
|
130
|
+
});
|
|
119
131
|
|
|
120
132
|
if (nextValue !== value) {
|
|
121
133
|
didFormDataSubstitute = true;
|
|
@@ -188,7 +200,10 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
188
200
|
}
|
|
189
201
|
|
|
190
202
|
const bodyText = await request.clone().text();
|
|
191
|
-
const nextBodyText = substitutePlaceholderByRealToken(
|
|
203
|
+
const nextBodyText = substitutePlaceholderByRealToken({
|
|
204
|
+
text: bodyText,
|
|
205
|
+
doEncodeUriComponent: false
|
|
206
|
+
});
|
|
192
207
|
|
|
193
208
|
if (nextBodyText !== bodyText) {
|
|
194
209
|
didSubstitute = true;
|
|
@@ -197,6 +212,18 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
197
212
|
body = nextBodyText;
|
|
198
213
|
}
|
|
199
214
|
|
|
215
|
+
let url: string;
|
|
216
|
+
|
|
217
|
+
{
|
|
218
|
+
const url_before = request.url;
|
|
219
|
+
|
|
220
|
+
url = substitutePlaceholderByRealToken({ text: url_before, doEncodeUriComponent: true });
|
|
221
|
+
|
|
222
|
+
if (url !== url_before) {
|
|
223
|
+
didSubstitute = true;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
200
227
|
block_authed_request_to_unauthorized_hostnames: {
|
|
201
228
|
if (!didSubstitute) {
|
|
202
229
|
break block_authed_request_to_unauthorized_hostnames;
|
|
@@ -223,7 +250,7 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
223
250
|
);
|
|
224
251
|
}
|
|
225
252
|
|
|
226
|
-
|
|
253
|
+
const nextInit: RequestInit = {
|
|
227
254
|
method: request.method,
|
|
228
255
|
headers,
|
|
229
256
|
body,
|
|
@@ -236,7 +263,19 @@ function patchFetchApiToSubstituteTokenPlaceholder(params: {
|
|
|
236
263
|
integrity: request.integrity,
|
|
237
264
|
keepalive: request.keepalive,
|
|
238
265
|
signal: request.signal
|
|
239
|
-
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
{
|
|
269
|
+
//@ts-expect-error
|
|
270
|
+
const duplex = init?.duplex ?? (input instanceof Request ? input.duplex : undefined);
|
|
271
|
+
|
|
272
|
+
if (duplex !== undefined) {
|
|
273
|
+
//@ts-expect-error
|
|
274
|
+
nextInit.duplex = duplex;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return fetch_actual(url, nextInit);
|
|
240
279
|
};
|
|
241
280
|
}
|
|
242
281
|
|
|
@@ -249,29 +288,7 @@ function patchXMLHttpRequestApiToSubstituteTokenPlaceholder(params: {
|
|
|
249
288
|
const send_actual = XMLHttpRequest.prototype.send;
|
|
250
289
|
const setRequestHeader_actual = XMLHttpRequest.prototype.setRequestHeader;
|
|
251
290
|
|
|
252
|
-
|
|
253
|
-
url: string;
|
|
254
|
-
didSubstitute: boolean;
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
const xhrDataSymbol = Symbol("oidc-spa XMLHttpRequest data");
|
|
258
|
-
|
|
259
|
-
const getXhrData = (xhr: XMLHttpRequest): XhrData => {
|
|
260
|
-
const xhr_any = xhr as any;
|
|
261
|
-
|
|
262
|
-
if (xhr_any[xhrDataSymbol] !== undefined) {
|
|
263
|
-
return xhr_any[xhrDataSymbol];
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const data: XhrData = {
|
|
267
|
-
url: "",
|
|
268
|
-
didSubstitute: false
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
xhr_any[xhrDataSymbol] = data;
|
|
272
|
-
|
|
273
|
-
return data;
|
|
274
|
-
};
|
|
291
|
+
const stateByInstance = new WeakMap<XMLHttpRequest, { url: string; didSubstitute: boolean }>();
|
|
275
292
|
|
|
276
293
|
XMLHttpRequest.prototype.open = function open(
|
|
277
294
|
method: string,
|
|
@@ -280,10 +297,27 @@ function patchXMLHttpRequestApiToSubstituteTokenPlaceholder(params: {
|
|
|
280
297
|
username?: string | null,
|
|
281
298
|
password?: string | null
|
|
282
299
|
) {
|
|
283
|
-
const
|
|
300
|
+
const state = { url: "", didSubstitute: false };
|
|
301
|
+
|
|
302
|
+
{
|
|
303
|
+
const url_str = typeof url === "string" ? url : url.href;
|
|
304
|
+
state.url = substitutePlaceholderByRealToken({ text: url_str, doEncodeUriComponent: true });
|
|
305
|
+
if (url_str !== state.url) {
|
|
306
|
+
state.didSubstitute = true;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
stateByInstance.set(this, state);
|
|
311
|
+
|
|
312
|
+
prevent_fetching_of_hashed_js_assets: {
|
|
313
|
+
const { pathname } = new URL(state.url, window.location.href);
|
|
314
|
+
|
|
315
|
+
if (!viteHashedJsAssetPathRegExp.test(pathname)) {
|
|
316
|
+
break prevent_fetching_of_hashed_js_assets;
|
|
317
|
+
}
|
|
284
318
|
|
|
285
|
-
|
|
286
|
-
|
|
319
|
+
throw new Error("oidc-spa: Blocked request to hashed static asset.");
|
|
320
|
+
}
|
|
287
321
|
|
|
288
322
|
if (async === undefined) {
|
|
289
323
|
return open_actual.bind(this)(method, url);
|
|
@@ -293,47 +327,45 @@ function patchXMLHttpRequestApiToSubstituteTokenPlaceholder(params: {
|
|
|
293
327
|
};
|
|
294
328
|
|
|
295
329
|
XMLHttpRequest.prototype.setRequestHeader = function setRequestHeader(name, value) {
|
|
296
|
-
const
|
|
297
|
-
|
|
330
|
+
const state = stateByInstance.get(this);
|
|
331
|
+
|
|
332
|
+
assert(state !== undefined, "29440283");
|
|
333
|
+
|
|
334
|
+
const nextValue = substitutePlaceholderByRealToken({ text: value, doEncodeUriComponent: false });
|
|
298
335
|
|
|
299
336
|
if (nextValue !== value) {
|
|
300
|
-
|
|
337
|
+
state.didSubstitute = true;
|
|
301
338
|
}
|
|
302
339
|
|
|
303
340
|
return setRequestHeader_actual.call(this, name, nextValue);
|
|
304
341
|
};
|
|
305
342
|
|
|
306
343
|
XMLHttpRequest.prototype.send = function send(body) {
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
prevent_fetching_of_hashed_js_assets: {
|
|
310
|
-
const { pathname } = new URL(xhrData.url, window.location.href);
|
|
344
|
+
const state = stateByInstance.get(this);
|
|
311
345
|
|
|
312
|
-
|
|
313
|
-
break prevent_fetching_of_hashed_js_assets;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
throw new Error("oidc-spa: Blocked request to hashed static asset.");
|
|
317
|
-
}
|
|
346
|
+
assert(state !== undefined, "32323484");
|
|
318
347
|
|
|
319
348
|
let nextBody = body;
|
|
320
349
|
|
|
321
350
|
if (typeof body === "string") {
|
|
322
|
-
const nextBodyText = substitutePlaceholderByRealToken(
|
|
351
|
+
const nextBodyText = substitutePlaceholderByRealToken({
|
|
352
|
+
text: body,
|
|
353
|
+
doEncodeUriComponent: false
|
|
354
|
+
});
|
|
323
355
|
|
|
324
356
|
if (nextBodyText !== body) {
|
|
325
|
-
|
|
357
|
+
state.didSubstitute = true;
|
|
326
358
|
}
|
|
327
359
|
|
|
328
360
|
nextBody = nextBodyText;
|
|
329
361
|
}
|
|
330
362
|
|
|
331
363
|
block_authed_request_to_unauthorized_hostnames: {
|
|
332
|
-
if (!
|
|
364
|
+
if (!state.didSubstitute) {
|
|
333
365
|
break block_authed_request_to_unauthorized_hostnames;
|
|
334
366
|
}
|
|
335
367
|
|
|
336
|
-
const { hostname } = new URL(
|
|
368
|
+
const { hostname } = new URL(state.url, window.location.href);
|
|
337
369
|
|
|
338
370
|
if (
|
|
339
371
|
getIsHostnameAuthorized({
|
|
@@ -377,43 +409,9 @@ function patchWebSocketApiToSubstituteTokenPlaceholder(params: {
|
|
|
377
409
|
|
|
378
410
|
const WebSocketPatched = function WebSocket(url: string | URL, protocols?: string | string[]) {
|
|
379
411
|
const urlStr = typeof url === "string" ? url : url.href;
|
|
380
|
-
const nextUrl = substitutePlaceholderByRealToken(urlStr);
|
|
412
|
+
const nextUrl = substitutePlaceholderByRealToken({ text: urlStr, doEncodeUriComponent: true });
|
|
381
413
|
let didSubstitute = nextUrl !== urlStr;
|
|
382
414
|
|
|
383
|
-
const nextProtocols = (() => {
|
|
384
|
-
if (protocols === undefined) {
|
|
385
|
-
return protocols;
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
if (typeof protocols === "string") {
|
|
389
|
-
const next = substitutePlaceholderByRealToken(protocols);
|
|
390
|
-
|
|
391
|
-
if (next !== protocols) {
|
|
392
|
-
didSubstitute = true;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
return next;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
let didProtocolsSubstitute = false;
|
|
399
|
-
|
|
400
|
-
const next = protocols.map(protocol => {
|
|
401
|
-
const nextProtocol = substitutePlaceholderByRealToken(protocol);
|
|
402
|
-
|
|
403
|
-
if (nextProtocol !== protocol) {
|
|
404
|
-
didProtocolsSubstitute = true;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
return nextProtocol;
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
if (didProtocolsSubstitute) {
|
|
411
|
-
didSubstitute = true;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return next;
|
|
415
|
-
})();
|
|
416
|
-
|
|
417
415
|
const { hostname, pathname } = new URL(nextUrl, window.location.href);
|
|
418
416
|
|
|
419
417
|
block_authed_request_to_unauthorized_hostnames: {
|
|
@@ -440,7 +438,7 @@ function patchWebSocketApiToSubstituteTokenPlaceholder(params: {
|
|
|
440
438
|
);
|
|
441
439
|
}
|
|
442
440
|
|
|
443
|
-
const ws = new WebSocket_actual(nextUrl,
|
|
441
|
+
const ws = new WebSocket_actual(nextUrl, protocols as Parameters<typeof WebSocket>[1]);
|
|
444
442
|
|
|
445
443
|
wsDataByWs.set(ws, {
|
|
446
444
|
url: nextUrl,
|
|
@@ -476,7 +474,10 @@ function patchWebSocketApiToSubstituteTokenPlaceholder(params: {
|
|
|
476
474
|
let nextData = data;
|
|
477
475
|
|
|
478
476
|
if (typeof data === "string") {
|
|
479
|
-
const nextDataText = substitutePlaceholderByRealToken(
|
|
477
|
+
const nextDataText = substitutePlaceholderByRealToken({
|
|
478
|
+
text: data,
|
|
479
|
+
doEncodeUriComponent: false
|
|
480
|
+
});
|
|
480
481
|
|
|
481
482
|
if (nextDataText !== data) {
|
|
482
483
|
wsData.didSubstitute = true;
|
|
@@ -537,7 +538,7 @@ function patchEventSourceApiToSubstituteTokenPlaceholder(params: {
|
|
|
537
538
|
eventSourceInitDict?: EventSourceInit
|
|
538
539
|
) {
|
|
539
540
|
const urlStr = typeof url === "string" ? url : url.href;
|
|
540
|
-
const nextUrl = substitutePlaceholderByRealToken(urlStr);
|
|
541
|
+
const nextUrl = substitutePlaceholderByRealToken({ text: urlStr, doEncodeUriComponent: true });
|
|
541
542
|
const didSubstitute = nextUrl !== urlStr;
|
|
542
543
|
|
|
543
544
|
const { hostname } = new URL(nextUrl, window.location.href);
|
|
@@ -598,7 +599,7 @@ function patchNavigatorSendBeaconApiToSubstituteTokenPlaceholder(params: {
|
|
|
598
599
|
|
|
599
600
|
navigator.sendBeacon = function sendBeacon(url: string | URL, data?: BodyInit | null) {
|
|
600
601
|
const urlStr = typeof url === "string" ? url : url.href;
|
|
601
|
-
const nextUrl = substitutePlaceholderByRealToken(urlStr);
|
|
602
|
+
const nextUrl = substitutePlaceholderByRealToken({ text: urlStr, doEncodeUriComponent: true });
|
|
602
603
|
let didSubstitute = nextUrl !== urlStr;
|
|
603
604
|
|
|
604
605
|
const { hostname } = new URL(nextUrl, window.location.href);
|
|
@@ -606,7 +607,7 @@ function patchNavigatorSendBeaconApiToSubstituteTokenPlaceholder(params: {
|
|
|
606
607
|
let nextData = data;
|
|
607
608
|
|
|
608
609
|
if (typeof data === "string") {
|
|
609
|
-
const next = substitutePlaceholderByRealToken(data);
|
|
610
|
+
const next = substitutePlaceholderByRealToken({ text: data, doEncodeUriComponent: false });
|
|
610
611
|
|
|
611
612
|
if (next !== data) {
|
|
612
613
|
didSubstitute = true;
|
|
@@ -618,7 +619,10 @@ function patchNavigatorSendBeaconApiToSubstituteTokenPlaceholder(params: {
|
|
|
618
619
|
const next = new URLSearchParams();
|
|
619
620
|
|
|
620
621
|
data.forEach((value, key) => {
|
|
621
|
-
const nextValue = substitutePlaceholderByRealToken(
|
|
622
|
+
const nextValue = substitutePlaceholderByRealToken({
|
|
623
|
+
text: value,
|
|
624
|
+
doEncodeUriComponent: false
|
|
625
|
+
});
|
|
622
626
|
|
|
623
627
|
if (nextValue !== value) {
|
|
624
628
|
didUrlSearchParamsSubstitute = true;
|
|
@@ -637,7 +641,10 @@ function patchNavigatorSendBeaconApiToSubstituteTokenPlaceholder(params: {
|
|
|
637
641
|
|
|
638
642
|
data.forEach((value, key) => {
|
|
639
643
|
if (typeof value === "string") {
|
|
640
|
-
const nextValue = substitutePlaceholderByRealToken(
|
|
644
|
+
const nextValue = substitutePlaceholderByRealToken({
|
|
645
|
+
text: value,
|
|
646
|
+
doEncodeUriComponent: false
|
|
647
|
+
});
|
|
641
648
|
|
|
642
649
|
if (nextValue !== value) {
|
|
643
650
|
didFormDataSubstitute = true;
|
|
@@ -705,6 +712,7 @@ function runMonkeyPatchingPrevention() {
|
|
|
705
712
|
"ServiceWorkerRegistration",
|
|
706
713
|
"ServiceWorker",
|
|
707
714
|
"FormData",
|
|
715
|
+
"WeakMap",
|
|
708
716
|
"Blob",
|
|
709
717
|
"String",
|
|
710
718
|
"Object",
|
|
@@ -66,7 +66,12 @@ export function getTokensPlaceholders(params: { configId: string; tokens: Tokens
|
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
export function substitutePlaceholderByRealToken(
|
|
69
|
+
export function substitutePlaceholderByRealToken(params: {
|
|
70
|
+
text: string;
|
|
71
|
+
doEncodeUriComponent: boolean;
|
|
72
|
+
}): string {
|
|
73
|
+
const { text, doEncodeUriComponent } = params;
|
|
74
|
+
|
|
70
75
|
let text_modified = text;
|
|
71
76
|
|
|
72
77
|
for (const [tokenType, regExp] of [
|
|
@@ -89,15 +94,19 @@ export function substitutePlaceholderByRealToken(text: string): string {
|
|
|
89
94
|
);
|
|
90
95
|
}
|
|
91
96
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
const token = (() => {
|
|
98
|
+
switch (tokenType) {
|
|
99
|
+
case "access_token":
|
|
100
|
+
return entry.tokens.accessToken;
|
|
101
|
+
case "id_token":
|
|
102
|
+
return entry.tokens.idToken;
|
|
103
|
+
case "refresh_token":
|
|
104
|
+
assert(entry.tokens.refreshToken !== undefined, "204392284");
|
|
105
|
+
return entry.tokens.refreshToken;
|
|
106
|
+
}
|
|
107
|
+
})();
|
|
108
|
+
|
|
109
|
+
return doEncodeUriComponent ? encodeURIComponent(token) : token;
|
|
101
110
|
});
|
|
102
111
|
}
|
|
103
112
|
|