sliccy 6.128.6 → 6.128.7
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/dist/node-server/_shared/content-type.d.ts +24 -0
- package/dist/node-server/_shared/content-type.d.ts.map +1 -1
- package/dist/node-server/_shared/content-type.js +32 -0
- package/dist/node-server/_shared/content-type.js.map +1 -1
- package/dist/node-server/_shared/form-body-unmask.d.ts +35 -0
- package/dist/node-server/_shared/form-body-unmask.d.ts.map +1 -0
- package/dist/node-server/_shared/form-body-unmask.js +81 -0
- package/dist/node-server/_shared/form-body-unmask.js.map +1 -0
- package/dist/node-server/_shared/index.d.ts +1 -0
- package/dist/node-server/_shared/index.d.ts.map +1 -1
- package/dist/node-server/_shared/index.js +1 -0
- package/dist/node-server/_shared/index.js.map +1 -1
- package/dist/node-server/routes/fetch-proxy.js +15 -6
- package/dist/ui/.vite/manifest.json +146 -164
- package/dist/ui/assets/{account-store-Bmrsj97P.js → account-store-D5BbCFxk.js} +1 -1
- package/dist/ui/assets/{account-store-BUbJ2BKn.js → account-store-DKT9SwjY.js} +1 -1
- package/dist/ui/assets/{adobe-BhAD8u-r.js → adobe-BbtPtejn.js} +1 -1
- package/dist/ui/assets/{adobe-DXFAJ2mg.js → adobe-CnEakFtG.js} +1 -1
- package/dist/ui/assets/{agent-bridge-PjE8zIFt.js → agent-bridge-C_h54Rwc.js} +1 -1
- package/dist/ui/assets/{leader-session-events-DxNdvTBd.js → agent-handle-CNeA3sd0.js} +1 -1
- package/dist/ui/assets/{apply-layout-CCRSRPXb.js → apply-layout-C5R70JfS.js} +1 -1
- package/dist/ui/assets/{apps--2j-Iyhq.js → apps-Vrn4aFGY.js} +1 -1
- package/dist/ui/assets/{azure-openai-B_VagXbE.js → azure-openai-BdEPMqa2.js} +1 -1
- package/dist/ui/assets/{azure-openai-4gQ5yrEB.js → azure-openai-CGSifPed.js} +1 -1
- package/dist/ui/assets/{cerebras-C6uFuOeg.js → cerebras-CjRBnonc.js} +1 -1
- package/dist/ui/assets/{cerebras-D5tNdV9j.js → cerebras-DzK7G6xZ.js} +1 -1
- package/dist/ui/assets/{connect-surface-yK5u_Q6n.js → connect-surface-BM43FjeP.js} +1 -1
- package/dist/ui/assets/{directed-approval-CrF6_0G_.js → directed-approval-BMidHvD7.js} +1 -1
- package/dist/ui/assets/{fs-BsNm5sve.js → fs-CuAZiRXc.js} +2 -2
- package/dist/ui/assets/{fs-CIG_gygq.js → fs-D1sCSc8D.js} +1 -1
- package/dist/ui/assets/{github-oda0q1k7.js → github-QbcUpix6.js} +2 -2
- package/dist/ui/assets/{github-copilot-DSq-yJL0.js → github-copilot-DmqKZrJ5.js} +1 -1
- package/dist/ui/assets/{github-copilot-R2mY5QJt.js → github-copilot-rCQ6iJOY.js} +1 -1
- package/dist/ui/assets/{github-D1VYHrL_.js → github-dj7Q2kBs.js} +1 -1
- package/dist/ui/assets/{github-install-Bk9gnpB8.js → github-install-2MZ9qxoI.js} +1 -1
- package/dist/ui/assets/{github-zip-B48HdBrD.js → github-zip-ByDGipuA.js} +1 -1
- package/dist/ui/assets/{help-B0IiDmol.js → help-D-jT6Puh.js} +1 -1
- package/dist/ui/assets/{js-realm-worker-8q3ZGlPL.js → js-realm-worker-XwLsnEuL.js} +1 -1
- package/dist/ui/assets/{kernel-worker-B6U0_YOv.js → kernel-worker-Bw0LSu56.js} +29 -29
- package/dist/ui/assets/{local-llm-DKePJeWv.js → local-llm-BpjLZBNI.js} +1 -1
- package/dist/ui/assets/{local-llm-DC5fIHCn.js → local-llm-DxMn0OUg.js} +1 -1
- package/dist/ui/assets/{main-CuVCCQNK.js → main-0l96x34-.js} +3 -3
- package/dist/ui/assets/{mount-DcMpudOH.js → mount-4uIRBLNn.js} +1 -1
- package/dist/ui/assets/{mount-D-2UMW7R.js → mount-Ba9kfTv0.js} +3 -3
- package/dist/ui/assets/{mount-recovery-D5t6XoiM.js → mount-recovery-BHD-Kg_U.js} +1 -1
- package/dist/ui/assets/{new-session-_PRk-79-.js → new-session-reI8CBls.js} +2 -2
- package/dist/ui/assets/{oauth-bootstrap-B48OSs9E.js → oauth-bootstrap-DlzYrJQN.js} +2 -2
- package/dist/ui/assets/{openai-codex-DG0mqdFd.js → openai-codex-BG9ha87D.js} +1 -1
- package/dist/ui/assets/{openai-codex-DwzYBt6J.js → openai-codex-DuwPw2QY.js} +1 -1
- package/dist/ui/assets/{openrouter-BcsJPN98.js → openrouter-CXEnXBiQ.js} +1 -1
- package/dist/ui/assets/{openrouter-DPyNbaLg.js → openrouter-Ck2IqxR4.js} +1 -1
- package/dist/ui/assets/{openrouter-oauth-BWdNYF4G.js → openrouter-oauth-Bz7fKyOZ.js} +1 -1
- package/dist/ui/assets/{openrouter-oauth-DOlvCf2Y.js → openrouter-oauth-HC2tj5Mr.js} +1 -1
- package/dist/ui/assets/{panel-rpc-handlers-BqfKh49m.js → panel-rpc-handlers-BDODGm_S.js} +2 -2
- package/dist/ui/assets/{provider-DttjQZqw.js → provider-B0fVtrQF.js} +1 -1
- package/dist/ui/assets/{provider-Db3AG108.js → provider-C2l5bFep.js} +2 -2
- package/dist/ui/assets/{provider-settings-DqpsrFWW.js → provider-settings-BOJ-iGnH.js} +1 -1
- package/dist/ui/assets/{provider-settings-CrqIgfZq.js → provider-settings-CMP3nC-p.js} +1 -1
- package/dist/ui/assets/provider-store-access-DijABiWO.js +1 -0
- package/dist/ui/assets/{provider-store-access-Bt6T9H0P.js → provider-store-access-gQflNxmD.js} +2 -2
- package/dist/ui/assets/{providers-7Mp63lDh.js → providers-8BUK3qfv.js} +1 -1
- package/dist/ui/assets/{providers-B8SeBoUY.js → providers-DKP4lWog.js} +2 -2
- package/dist/ui/assets/{quick-llm-DSy7lilD.js → quick-llm-DYtfIriH.js} +1 -1
- package/dist/ui/assets/{recovery-screen-CQBlhrpX.js → recovery-screen-BbBLl68U.js} +1 -1
- package/dist/ui/assets/{run-Bf0QmRQ4.js → run-C2jCCAE8.js} +1 -1
- package/dist/ui/assets/{run-BOrsTRRp.js → run-VBEA56eF.js} +2 -2
- package/dist/ui/assets/{session-freezer-irJQXRgi.js → session-freezer-bFjAPT3S.js} +3 -3
- package/dist/ui/assets/{setup-feature-flags-remote-BHstTmo2.js → setup-feature-flags-remote-Cl029oIF.js} +1 -1
- package/dist/ui/assets/{setup-standalone-prelude-BHxUCr2_.js → setup-standalone-prelude-CD8DxIvq.js} +1 -1
- package/dist/ui/assets/{setup-sudo-vpsz4NvS.js → setup-sudo-D7GDTzgD.js} +2 -2
- package/dist/ui/assets/{skills-BHHfjloS.js → skills-Dszq4AuT.js} +1 -1
- package/dist/ui/assets/slicc-version-hXpjQDyN.js +1 -0
- package/dist/ui/assets/{snapshot-CTschz62.js → snapshot-DLkWp_JX.js} +1 -1
- package/dist/ui/assets/{snapshot-features-CDjFkhp3.js → snapshot-features-B7r5Dj3P.js} +1 -1
- package/dist/ui/assets/{state-B_IDx0h1.js → state-WpvhcudV.js} +1 -1
- package/dist/ui/assets/{store-Dz9xCMJC.js → store-CHi0px9Y.js} +1 -1
- package/dist/ui/assets/{store-DcjUaO0s.js → store-uldIP9Cn.js} +1 -1
- package/dist/ui/assets/{sudo-Dx__R2Bg.js → sudo-CDcrPMvY.js} +1 -1
- package/dist/ui/assets/{upgrade-detection-DFF7V0-i.js → upgrade-detection-DkCLo0kx.js} +1 -1
- package/dist/ui/assets/wc-extension-DrZ2VBoo.js +2 -0
- package/dist/ui/assets/wc-float-label-Biud2gCU.js +1 -0
- package/dist/ui/assets/wc-follower-LOUwFa6B.js +17 -0
- package/dist/ui/assets/wc-live-BfY4xw54.js +8 -0
- package/dist/ui/assets/{wc-nav-Bjjvz95M.js → wc-nav-TZHFfrem.js} +2 -2
- package/dist/ui/assets/{wc-onboarding-Crn_BK89.js → wc-onboarding-CahkVADg.js} +2 -2
- package/dist/ui/assets/{wc-placeholder-D874Ml5G.js → wc-placeholder-uNMl8Xvx.js} +2 -2
- package/dist/ui/assets/{wc-settings-DVG9WKT5.js → wc-settings-DINEt1t8.js} +2 -2
- package/dist/ui/assets/{wc-shell-BJz-J2C1.js → wc-shell-BdDjQ1zm.js} +3 -3
- package/dist/ui/assets/{wc-sprinkles-BOAttQBt.js → wc-sprinkles-DkYNoNxD.js} +2 -2
- package/dist/ui/assets/{wc-tray-D2VXp5Th.js → wc-tray-DHL-ENMW.js} +4 -4
- package/dist/ui/assets/{xai-grok-GdfivWIl.js → xai-grok-BR8Rr8AN.js} +1 -1
- package/dist/ui/assets/{xai-grok-CO3EaJqD.js → xai-grok-CIccr61p.js} +1 -1
- package/dist/ui/index.html +6 -6
- package/dist/ui/llm-proxy-sw.js +1 -1
- package/dist/ui/packages/webapp/index.html +6 -6
- package/package.json +3 -3
- package/dist/ui/assets/provider-store-access-emhGtS9D.js +0 -1
- package/dist/ui/assets/slicc-version-VI-K_YsZ.js +0 -1
- package/dist/ui/assets/wc-extension-CecdIyMT.js +0 -2
- package/dist/ui/assets/wc-float-label-DCnN6-DZ.js +0 -1
- package/dist/ui/assets/wc-floatbar-online-BRk6j7Xu.js +0 -1
- package/dist/ui/assets/wc-follower-DIhqDkJs.js +0 -17
- package/dist/ui/assets/wc-live-BwAemf5l.js +0 -8
|
@@ -1,3 +1,27 @@
|
|
|
1
1
|
/** Check whether a content type is safe to decode as UTF-8 text. */
|
|
2
2
|
export declare function isTextContentType(contentType: string): boolean;
|
|
3
|
+
/**
|
|
4
|
+
* Whether a content type is `application/x-www-form-urlencoded`.
|
|
5
|
+
*
|
|
6
|
+
* Callers need this on top of {@link isTextRequestContentType} because a form
|
|
7
|
+
* body is not just text: substituting a secret into one has to respect the
|
|
8
|
+
* percent-encoding (see `unmaskFormBody`).
|
|
9
|
+
*/
|
|
10
|
+
export declare function isFormContentType(contentType: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Request-side variant of {@link isTextContentType}, additionally matching
|
|
13
|
+
* `application/x-www-form-urlencoded`.
|
|
14
|
+
*
|
|
15
|
+
* Form bodies carry secrets — OAuth token exchange and a long tail of provider
|
|
16
|
+
* APIs POST `client_secret=…` / `token=…` as a form — so a masked token in one
|
|
17
|
+
* MUST be unmasked before the proxy forwards it upstream. The plain predicate
|
|
18
|
+
* stays urlencoded-free because it also gates response scrub and body caching,
|
|
19
|
+
* where a form body is rare and the extra match buys nothing.
|
|
20
|
+
*
|
|
21
|
+
* Mirrored by `isTextRequestContentType` in
|
|
22
|
+
* `packages/swift-server/Sources/Server/ContentType.swift`; the classification
|
|
23
|
+
* table is pinned in both `tests/cross-impl-vectors.test.ts` and
|
|
24
|
+
* `packages/swift-server/Tests/CrossImplementationTests.swift`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isTextRequestContentType(contentType: string): boolean;
|
|
3
27
|
//# sourceMappingURL=content-type.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAa9D"}
|
|
1
|
+
{"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAa9D;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAG9D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAGrE"}
|
|
@@ -12,4 +12,36 @@ export function isTextContentType(contentType) {
|
|
|
12
12
|
normalized.includes('css') ||
|
|
13
13
|
normalized.includes('svg'));
|
|
14
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Whether a content type is `application/x-www-form-urlencoded`.
|
|
17
|
+
*
|
|
18
|
+
* Callers need this on top of {@link isTextRequestContentType} because a form
|
|
19
|
+
* body is not just text: substituting a secret into one has to respect the
|
|
20
|
+
* percent-encoding (see `unmaskFormBody`).
|
|
21
|
+
*/
|
|
22
|
+
export function isFormContentType(contentType) {
|
|
23
|
+
if (!contentType)
|
|
24
|
+
return false;
|
|
25
|
+
return contentType.toLowerCase().includes('urlencoded');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Request-side variant of {@link isTextContentType}, additionally matching
|
|
29
|
+
* `application/x-www-form-urlencoded`.
|
|
30
|
+
*
|
|
31
|
+
* Form bodies carry secrets — OAuth token exchange and a long tail of provider
|
|
32
|
+
* APIs POST `client_secret=…` / `token=…` as a form — so a masked token in one
|
|
33
|
+
* MUST be unmasked before the proxy forwards it upstream. The plain predicate
|
|
34
|
+
* stays urlencoded-free because it also gates response scrub and body caching,
|
|
35
|
+
* where a form body is rare and the extra match buys nothing.
|
|
36
|
+
*
|
|
37
|
+
* Mirrored by `isTextRequestContentType` in
|
|
38
|
+
* `packages/swift-server/Sources/Server/ContentType.swift`; the classification
|
|
39
|
+
* table is pinned in both `tests/cross-impl-vectors.test.ts` and
|
|
40
|
+
* `packages/swift-server/Tests/CrossImplementationTests.swift`.
|
|
41
|
+
*/
|
|
42
|
+
export function isTextRequestContentType(contentType) {
|
|
43
|
+
if (!contentType)
|
|
44
|
+
return false;
|
|
45
|
+
return isTextContentType(contentType) || isFormContentType(contentType);
|
|
46
|
+
}
|
|
15
47
|
//# sourceMappingURL=content-type.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type.js","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;IAC7C,OAAO,CACL,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC;QAC9B,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3B,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC1B,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QACjC,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QACjC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3B,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC1B,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC3B,CAAC;AACJ,CAAC"}
|
|
1
|
+
{"version":3,"file":"content-type.js","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,MAAM,UAAU,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;IAC7C,OAAO,CACL,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC;QAC9B,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3B,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC1B,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QACjC,UAAU,CAAC,QAAQ,CAAC,YAAY,CAAC;QACjC,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3B,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC;QAC1B,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAC3B,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,WAAmB;IACnD,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,OAAO,WAAW,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,wBAAwB,CAAC,WAAmB;IAC1D,IAAI,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAC/B,OAAO,iBAAiB,CAAC,WAAW,CAAC,IAAI,iBAAiB,CAAC,WAAW,CAAC,CAAC;AAC1E,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encoding-aware masked→real unmask for `application/x-www-form-urlencoded`
|
|
3
|
+
* request bodies.
|
|
4
|
+
*
|
|
5
|
+
* A form body is text, so the naive path is a substring replace of the masked
|
|
6
|
+
* token with the real value. That corrupts the body whenever the real secret
|
|
7
|
+
* contains a character that is reserved in a form: `&` and `=` split one field
|
|
8
|
+
* into two (or inject an attacker-shaped extra parameter), `+` is decoded
|
|
9
|
+
* upstream as a space, and a bare `%` becomes a malformed escape. Base64
|
|
10
|
+
* secrets — AWS keys, many OAuth client secrets — routinely contain `+`, `/`,
|
|
11
|
+
* and `=`, so the naive path silently breaks exactly the token exchanges that
|
|
12
|
+
* made form unmasking necessary in the first place.
|
|
13
|
+
*
|
|
14
|
+
* This walks the body field by field, unmasks each value in its DECODED form,
|
|
15
|
+
* and re-encodes only the values that actually changed. Untouched fields keep
|
|
16
|
+
* their original bytes, so a body with no masked token is forwarded verbatim.
|
|
17
|
+
*
|
|
18
|
+
* Same shape as `SecretsPipeline.unmaskAuthorizationBasic`, which decodes
|
|
19
|
+
* base64, unmasks, and re-encodes rather than substituting into the wire form.
|
|
20
|
+
*
|
|
21
|
+
* Mirrored by `unmaskFormBody` in
|
|
22
|
+
* `packages/swift-server/Sources/Server/FormBodyUnmask.swift`; the
|
|
23
|
+
* substitution table is pinned in both cross-impl test files.
|
|
24
|
+
*/
|
|
25
|
+
import type { SecretsPipeline } from './secrets-pipeline.js';
|
|
26
|
+
/** The unmask surface this helper needs — `SecretsPipeline` or a wrapper over one. */
|
|
27
|
+
export type FormBodyUnmasker = Pick<SecretsPipeline, 'unmaskBody' | 'hasSecrets'>;
|
|
28
|
+
/**
|
|
29
|
+
* Unmask a form-urlencoded body. Returns the original string when nothing
|
|
30
|
+
* changed, so the caller can skip re-encoding the buffer.
|
|
31
|
+
*/
|
|
32
|
+
export declare function unmaskFormBody(pipeline: FormBodyUnmasker, body: string, hostname: string): {
|
|
33
|
+
text: string;
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=form-body-unmask.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-body-unmask.d.ts","sourceRoot":"","sources":["../src/form-body-unmask.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,sFAAsF;AACtF,MAAM,MAAM,gBAAgB,GAAG,IAAI,CAAC,eAAe,EAAE,YAAY,GAAG,YAAY,CAAC,CAAC;AAyBlF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,gBAAgB,EAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CA6BlB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Encoding-aware masked→real unmask for `application/x-www-form-urlencoded`
|
|
3
|
+
* request bodies.
|
|
4
|
+
*
|
|
5
|
+
* A form body is text, so the naive path is a substring replace of the masked
|
|
6
|
+
* token with the real value. That corrupts the body whenever the real secret
|
|
7
|
+
* contains a character that is reserved in a form: `&` and `=` split one field
|
|
8
|
+
* into two (or inject an attacker-shaped extra parameter), `+` is decoded
|
|
9
|
+
* upstream as a space, and a bare `%` becomes a malformed escape. Base64
|
|
10
|
+
* secrets — AWS keys, many OAuth client secrets — routinely contain `+`, `/`,
|
|
11
|
+
* and `=`, so the naive path silently breaks exactly the token exchanges that
|
|
12
|
+
* made form unmasking necessary in the first place.
|
|
13
|
+
*
|
|
14
|
+
* This walks the body field by field, unmasks each value in its DECODED form,
|
|
15
|
+
* and re-encodes only the values that actually changed. Untouched fields keep
|
|
16
|
+
* their original bytes, so a body with no masked token is forwarded verbatim.
|
|
17
|
+
*
|
|
18
|
+
* Same shape as `SecretsPipeline.unmaskAuthorizationBasic`, which decodes
|
|
19
|
+
* base64, unmasks, and re-encodes rather than substituting into the wire form.
|
|
20
|
+
*
|
|
21
|
+
* Mirrored by `unmaskFormBody` in
|
|
22
|
+
* `packages/swift-server/Sources/Server/FormBodyUnmask.swift`; the
|
|
23
|
+
* substitution table is pinned in both cross-impl test files.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Percent-decode one form component. `+` means space in a form body, so it is
|
|
27
|
+
* promoted to `%20` before decoding. Returns `null` for a malformed escape,
|
|
28
|
+
* which the caller treats as "cannot reason about this field's encoding".
|
|
29
|
+
*/
|
|
30
|
+
function decodeFormComponent(raw) {
|
|
31
|
+
try {
|
|
32
|
+
return decodeURIComponent(raw.replace(/\+/g, '%20'));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Percent-encode one form component. `encodeURIComponent` escapes every
|
|
40
|
+
* character that is reserved in a form body and leaves only unreserved ones
|
|
41
|
+
* (`A-Za-z0-9-_.!~*'()`), all of which are legal in a form value. Space becomes
|
|
42
|
+
* `%20` rather than `+`; both decode to a space.
|
|
43
|
+
*/
|
|
44
|
+
function encodeFormComponent(value) {
|
|
45
|
+
return encodeURIComponent(value);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Unmask a form-urlencoded body. Returns the original string when nothing
|
|
49
|
+
* changed, so the caller can skip re-encoding the buffer.
|
|
50
|
+
*/
|
|
51
|
+
export function unmaskFormBody(pipeline, body, hostname) {
|
|
52
|
+
if (!body || !pipeline.hasSecrets())
|
|
53
|
+
return { text: body };
|
|
54
|
+
let changed = false;
|
|
55
|
+
const fields = body.split('&').map((field) => {
|
|
56
|
+
const eq = field.indexOf('=');
|
|
57
|
+
const name = eq < 0 ? '' : field.slice(0, eq + 1);
|
|
58
|
+
const rawValue = eq < 0 ? field : field.slice(eq + 1);
|
|
59
|
+
if (!rawValue)
|
|
60
|
+
return field;
|
|
61
|
+
const decoded = decodeFormComponent(rawValue);
|
|
62
|
+
if (decoded === null) {
|
|
63
|
+
// Malformed percent-escape: the field's encoding is not interpretable, so
|
|
64
|
+
// fall back to the legacy substring replace rather than dropping the
|
|
65
|
+
// secret. Best-effort — a reserved character in the real value can still
|
|
66
|
+
// corrupt a field this broken.
|
|
67
|
+
const { text } = pipeline.unmaskBody(rawValue, hostname);
|
|
68
|
+
if (text === rawValue)
|
|
69
|
+
return field;
|
|
70
|
+
changed = true;
|
|
71
|
+
return `${name}${text}`;
|
|
72
|
+
}
|
|
73
|
+
const { text } = pipeline.unmaskBody(decoded, hostname);
|
|
74
|
+
if (text === decoded)
|
|
75
|
+
return field;
|
|
76
|
+
changed = true;
|
|
77
|
+
return `${name}${encodeFormComponent(text)}`;
|
|
78
|
+
});
|
|
79
|
+
return { text: changed ? fields.join('&') : body };
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=form-body-unmask.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-body-unmask.js","sourceRoot":"","sources":["../src/form-body-unmask.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAOH;;;;GAIG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,OAAO,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,KAAa;IACxC,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC5B,QAA0B,EAC1B,IAAY,EACZ,QAAgB;IAEhB,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAE3D,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC3C,MAAM,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAE5B,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,0EAA0E;YAC1E,qEAAqE;YACrE,yEAAyE;YACzE,+BAA+B;YAC/B,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACzD,IAAI,IAAI,KAAK,QAAQ;gBAAE,OAAO,KAAK,CAAC;YACpC,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;QAC1B,CAAC;QAED,MAAM,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACxD,IAAI,IAAI,KAAK,OAAO;YAAE,OAAO,KAAK,CAAC;QACnC,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,GAAG,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACrD,CAAC"}
|
|
@@ -5,6 +5,7 @@ export * from './cdp-frame-unmask.js';
|
|
|
5
5
|
export * from './content-type.js';
|
|
6
6
|
export * from './electron-overlay-route.js';
|
|
7
7
|
export * from './fetch-proxy-protocol.js';
|
|
8
|
+
export * from './form-body-unmask.js';
|
|
8
9
|
export * from './loopback.js';
|
|
9
10
|
export * from './oauth-extra-domains-storage.js';
|
|
10
11
|
export * from './preview-url.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,kCAAkC,CAAC;AACjD,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,kCAAkC,CAAC;AACjD,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC"}
|
|
@@ -6,6 +6,7 @@ export * from './cdp-frame-unmask.js';
|
|
|
6
6
|
export * from './content-type.js';
|
|
7
7
|
export * from './electron-overlay-route.js';
|
|
8
8
|
export * from './fetch-proxy-protocol.js';
|
|
9
|
+
export * from './form-body-unmask.js';
|
|
9
10
|
export * from './loopback.js';
|
|
10
11
|
export * from './oauth-extra-domains-storage.js';
|
|
11
12
|
export * from './preview-url.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,kCAAkC,CAAC;AACjD,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,cAAc,uBAAuB,CAAC;AACtC,cAAc,aAAa,CAAC;AAC5B,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAC;AAClC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC;AAC9B,cAAc,kCAAkC,CAAC;AACjD,cAAc,kBAAkB,CAAC;AACjC,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC;AACpC,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,yBAAyB,CAAC;AACxC,cAAc,sBAAsB,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Readable, Transform } from 'node:stream';
|
|
2
2
|
import { StringDecoder } from 'node:string_decoder';
|
|
3
|
-
import { HMAC_SIGN_HEADER, isLoopbackOrigin, isTextContentType } from '../_shared/index.js';
|
|
3
|
+
import { HMAC_SIGN_HEADER, isFormContentType, isLoopbackOrigin, isTextContentType, isTextRequestContentType, unmaskFormBody, } from '../_shared/index.js';
|
|
4
4
|
import { buildFetchProxyExposeHeaders, FETCH_PROXY_CONTENT_LENGTH_HEADER, FETCH_PROXY_SKIP_HEADERS, FETCH_PROXY_SKIP_RESPONSE_HEADERS, FETCH_PROXY_SKIP_RESPONSE_PREFIXES, } from '../fetch-proxy-headers.js';
|
|
5
5
|
import { AgentActivityTracker, registerAgentActivityRoute } from './agent-activity.js';
|
|
6
6
|
/** Pick the first value of a possibly-multi-valued request header. */
|
|
@@ -124,14 +124,23 @@ async function applyHmacSigning(secretProxy, headers, hmacSpec, body, targetHost
|
|
|
124
124
|
* Unmask masked secrets in a text request body. Non-text bodies (git
|
|
125
125
|
* packfiles, octet-stream, images, …) are left untouched — `toString('utf-8')`
|
|
126
126
|
* on arbitrary bytes corrupts them, and masked values never appear in binary.
|
|
127
|
+
*
|
|
128
|
+
* Uses the request-side predicate so form POSTs count as text: swift-server
|
|
129
|
+
* unmasks them and a masked `client_secret` reaching an upstream OAuth token
|
|
130
|
+
* endpoint verbatim is a silent auth failure, not a corrupt-bytes risk (#2821).
|
|
131
|
+
* A form body then takes the encoding-aware `unmaskFormBody` path — a plain
|
|
132
|
+
* substring splice would corrupt it whenever the real secret carries a
|
|
133
|
+
* form-reserved character (base64 `+` / `/` / `=`).
|
|
127
134
|
*/
|
|
128
135
|
function unmaskRequestBody(secretProxy, headers, rawBody, targetHostname) {
|
|
129
136
|
const contentType = headers['content-type'] ?? headers['Content-Type'] ?? '';
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
137
|
+
if (!isTextRequestContentType(contentType) || !secretProxy.hasSecrets())
|
|
138
|
+
return rawBody;
|
|
139
|
+
const body = rawBody.toString('utf-8');
|
|
140
|
+
const { text } = isFormContentType(contentType)
|
|
141
|
+
? unmaskFormBody(secretProxy, body, targetHostname)
|
|
142
|
+
: secretProxy.unmaskBody(body, targetHostname);
|
|
143
|
+
return text === body ? rawBody : Buffer.from(text, 'utf-8');
|
|
135
144
|
}
|
|
136
145
|
/**
|
|
137
146
|
* Forward the upstream status + response headers, stripping hop-by-hop and
|