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.
Files changed (103) hide show
  1. package/dist/node-server/_shared/content-type.d.ts +24 -0
  2. package/dist/node-server/_shared/content-type.d.ts.map +1 -1
  3. package/dist/node-server/_shared/content-type.js +32 -0
  4. package/dist/node-server/_shared/content-type.js.map +1 -1
  5. package/dist/node-server/_shared/form-body-unmask.d.ts +35 -0
  6. package/dist/node-server/_shared/form-body-unmask.d.ts.map +1 -0
  7. package/dist/node-server/_shared/form-body-unmask.js +81 -0
  8. package/dist/node-server/_shared/form-body-unmask.js.map +1 -0
  9. package/dist/node-server/_shared/index.d.ts +1 -0
  10. package/dist/node-server/_shared/index.d.ts.map +1 -1
  11. package/dist/node-server/_shared/index.js +1 -0
  12. package/dist/node-server/_shared/index.js.map +1 -1
  13. package/dist/node-server/routes/fetch-proxy.js +15 -6
  14. package/dist/ui/.vite/manifest.json +146 -164
  15. package/dist/ui/assets/{account-store-Bmrsj97P.js → account-store-D5BbCFxk.js} +1 -1
  16. package/dist/ui/assets/{account-store-BUbJ2BKn.js → account-store-DKT9SwjY.js} +1 -1
  17. package/dist/ui/assets/{adobe-BhAD8u-r.js → adobe-BbtPtejn.js} +1 -1
  18. package/dist/ui/assets/{adobe-DXFAJ2mg.js → adobe-CnEakFtG.js} +1 -1
  19. package/dist/ui/assets/{agent-bridge-PjE8zIFt.js → agent-bridge-C_h54Rwc.js} +1 -1
  20. package/dist/ui/assets/{leader-session-events-DxNdvTBd.js → agent-handle-CNeA3sd0.js} +1 -1
  21. package/dist/ui/assets/{apply-layout-CCRSRPXb.js → apply-layout-C5R70JfS.js} +1 -1
  22. package/dist/ui/assets/{apps--2j-Iyhq.js → apps-Vrn4aFGY.js} +1 -1
  23. package/dist/ui/assets/{azure-openai-B_VagXbE.js → azure-openai-BdEPMqa2.js} +1 -1
  24. package/dist/ui/assets/{azure-openai-4gQ5yrEB.js → azure-openai-CGSifPed.js} +1 -1
  25. package/dist/ui/assets/{cerebras-C6uFuOeg.js → cerebras-CjRBnonc.js} +1 -1
  26. package/dist/ui/assets/{cerebras-D5tNdV9j.js → cerebras-DzK7G6xZ.js} +1 -1
  27. package/dist/ui/assets/{connect-surface-yK5u_Q6n.js → connect-surface-BM43FjeP.js} +1 -1
  28. package/dist/ui/assets/{directed-approval-CrF6_0G_.js → directed-approval-BMidHvD7.js} +1 -1
  29. package/dist/ui/assets/{fs-BsNm5sve.js → fs-CuAZiRXc.js} +2 -2
  30. package/dist/ui/assets/{fs-CIG_gygq.js → fs-D1sCSc8D.js} +1 -1
  31. package/dist/ui/assets/{github-oda0q1k7.js → github-QbcUpix6.js} +2 -2
  32. package/dist/ui/assets/{github-copilot-DSq-yJL0.js → github-copilot-DmqKZrJ5.js} +1 -1
  33. package/dist/ui/assets/{github-copilot-R2mY5QJt.js → github-copilot-rCQ6iJOY.js} +1 -1
  34. package/dist/ui/assets/{github-D1VYHrL_.js → github-dj7Q2kBs.js} +1 -1
  35. package/dist/ui/assets/{github-install-Bk9gnpB8.js → github-install-2MZ9qxoI.js} +1 -1
  36. package/dist/ui/assets/{github-zip-B48HdBrD.js → github-zip-ByDGipuA.js} +1 -1
  37. package/dist/ui/assets/{help-B0IiDmol.js → help-D-jT6Puh.js} +1 -1
  38. package/dist/ui/assets/{js-realm-worker-8q3ZGlPL.js → js-realm-worker-XwLsnEuL.js} +1 -1
  39. package/dist/ui/assets/{kernel-worker-B6U0_YOv.js → kernel-worker-Bw0LSu56.js} +29 -29
  40. package/dist/ui/assets/{local-llm-DKePJeWv.js → local-llm-BpjLZBNI.js} +1 -1
  41. package/dist/ui/assets/{local-llm-DC5fIHCn.js → local-llm-DxMn0OUg.js} +1 -1
  42. package/dist/ui/assets/{main-CuVCCQNK.js → main-0l96x34-.js} +3 -3
  43. package/dist/ui/assets/{mount-DcMpudOH.js → mount-4uIRBLNn.js} +1 -1
  44. package/dist/ui/assets/{mount-D-2UMW7R.js → mount-Ba9kfTv0.js} +3 -3
  45. package/dist/ui/assets/{mount-recovery-D5t6XoiM.js → mount-recovery-BHD-Kg_U.js} +1 -1
  46. package/dist/ui/assets/{new-session-_PRk-79-.js → new-session-reI8CBls.js} +2 -2
  47. package/dist/ui/assets/{oauth-bootstrap-B48OSs9E.js → oauth-bootstrap-DlzYrJQN.js} +2 -2
  48. package/dist/ui/assets/{openai-codex-DG0mqdFd.js → openai-codex-BG9ha87D.js} +1 -1
  49. package/dist/ui/assets/{openai-codex-DwzYBt6J.js → openai-codex-DuwPw2QY.js} +1 -1
  50. package/dist/ui/assets/{openrouter-BcsJPN98.js → openrouter-CXEnXBiQ.js} +1 -1
  51. package/dist/ui/assets/{openrouter-DPyNbaLg.js → openrouter-Ck2IqxR4.js} +1 -1
  52. package/dist/ui/assets/{openrouter-oauth-BWdNYF4G.js → openrouter-oauth-Bz7fKyOZ.js} +1 -1
  53. package/dist/ui/assets/{openrouter-oauth-DOlvCf2Y.js → openrouter-oauth-HC2tj5Mr.js} +1 -1
  54. package/dist/ui/assets/{panel-rpc-handlers-BqfKh49m.js → panel-rpc-handlers-BDODGm_S.js} +2 -2
  55. package/dist/ui/assets/{provider-DttjQZqw.js → provider-B0fVtrQF.js} +1 -1
  56. package/dist/ui/assets/{provider-Db3AG108.js → provider-C2l5bFep.js} +2 -2
  57. package/dist/ui/assets/{provider-settings-DqpsrFWW.js → provider-settings-BOJ-iGnH.js} +1 -1
  58. package/dist/ui/assets/{provider-settings-CrqIgfZq.js → provider-settings-CMP3nC-p.js} +1 -1
  59. package/dist/ui/assets/provider-store-access-DijABiWO.js +1 -0
  60. package/dist/ui/assets/{provider-store-access-Bt6T9H0P.js → provider-store-access-gQflNxmD.js} +2 -2
  61. package/dist/ui/assets/{providers-7Mp63lDh.js → providers-8BUK3qfv.js} +1 -1
  62. package/dist/ui/assets/{providers-B8SeBoUY.js → providers-DKP4lWog.js} +2 -2
  63. package/dist/ui/assets/{quick-llm-DSy7lilD.js → quick-llm-DYtfIriH.js} +1 -1
  64. package/dist/ui/assets/{recovery-screen-CQBlhrpX.js → recovery-screen-BbBLl68U.js} +1 -1
  65. package/dist/ui/assets/{run-Bf0QmRQ4.js → run-C2jCCAE8.js} +1 -1
  66. package/dist/ui/assets/{run-BOrsTRRp.js → run-VBEA56eF.js} +2 -2
  67. package/dist/ui/assets/{session-freezer-irJQXRgi.js → session-freezer-bFjAPT3S.js} +3 -3
  68. package/dist/ui/assets/{setup-feature-flags-remote-BHstTmo2.js → setup-feature-flags-remote-Cl029oIF.js} +1 -1
  69. package/dist/ui/assets/{setup-standalone-prelude-BHxUCr2_.js → setup-standalone-prelude-CD8DxIvq.js} +1 -1
  70. package/dist/ui/assets/{setup-sudo-vpsz4NvS.js → setup-sudo-D7GDTzgD.js} +2 -2
  71. package/dist/ui/assets/{skills-BHHfjloS.js → skills-Dszq4AuT.js} +1 -1
  72. package/dist/ui/assets/slicc-version-hXpjQDyN.js +1 -0
  73. package/dist/ui/assets/{snapshot-CTschz62.js → snapshot-DLkWp_JX.js} +1 -1
  74. package/dist/ui/assets/{snapshot-features-CDjFkhp3.js → snapshot-features-B7r5Dj3P.js} +1 -1
  75. package/dist/ui/assets/{state-B_IDx0h1.js → state-WpvhcudV.js} +1 -1
  76. package/dist/ui/assets/{store-Dz9xCMJC.js → store-CHi0px9Y.js} +1 -1
  77. package/dist/ui/assets/{store-DcjUaO0s.js → store-uldIP9Cn.js} +1 -1
  78. package/dist/ui/assets/{sudo-Dx__R2Bg.js → sudo-CDcrPMvY.js} +1 -1
  79. package/dist/ui/assets/{upgrade-detection-DFF7V0-i.js → upgrade-detection-DkCLo0kx.js} +1 -1
  80. package/dist/ui/assets/wc-extension-DrZ2VBoo.js +2 -0
  81. package/dist/ui/assets/wc-float-label-Biud2gCU.js +1 -0
  82. package/dist/ui/assets/wc-follower-LOUwFa6B.js +17 -0
  83. package/dist/ui/assets/wc-live-BfY4xw54.js +8 -0
  84. package/dist/ui/assets/{wc-nav-Bjjvz95M.js → wc-nav-TZHFfrem.js} +2 -2
  85. package/dist/ui/assets/{wc-onboarding-Crn_BK89.js → wc-onboarding-CahkVADg.js} +2 -2
  86. package/dist/ui/assets/{wc-placeholder-D874Ml5G.js → wc-placeholder-uNMl8Xvx.js} +2 -2
  87. package/dist/ui/assets/{wc-settings-DVG9WKT5.js → wc-settings-DINEt1t8.js} +2 -2
  88. package/dist/ui/assets/{wc-shell-BJz-J2C1.js → wc-shell-BdDjQ1zm.js} +3 -3
  89. package/dist/ui/assets/{wc-sprinkles-BOAttQBt.js → wc-sprinkles-DkYNoNxD.js} +2 -2
  90. package/dist/ui/assets/{wc-tray-D2VXp5Th.js → wc-tray-DHL-ENMW.js} +4 -4
  91. package/dist/ui/assets/{xai-grok-GdfivWIl.js → xai-grok-BR8Rr8AN.js} +1 -1
  92. package/dist/ui/assets/{xai-grok-CO3EaJqD.js → xai-grok-CIccr61p.js} +1 -1
  93. package/dist/ui/index.html +6 -6
  94. package/dist/ui/llm-proxy-sw.js +1 -1
  95. package/dist/ui/packages/webapp/index.html +6 -6
  96. package/package.json +3 -3
  97. package/dist/ui/assets/provider-store-access-emhGtS9D.js +0 -1
  98. package/dist/ui/assets/slicc-version-VI-K_YsZ.js +0 -1
  99. package/dist/ui/assets/wc-extension-CecdIyMT.js +0 -2
  100. package/dist/ui/assets/wc-float-label-DCnN6-DZ.js +0 -1
  101. package/dist/ui/assets/wc-floatbar-online-BRk6j7Xu.js +0 -1
  102. package/dist/ui/assets/wc-follower-DIhqDkJs.js +0 -17
  103. 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 (isTextContentType(contentType) && secretProxy.hasSecrets()) {
131
- const bodyResult = secretProxy.unmaskBody(rawBody.toString('utf-8'), targetHostname);
132
- return Buffer.from(bodyResult.text, 'utf-8');
133
- }
134
- return rawBody;
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