@sysid/sandbox-runtime-improved 0.0.56-sysid.1 → 0.0.61-sysid.1

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 (116) hide show
  1. package/README.md +43 -2
  2. package/dist/index.d.ts +3 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/sandbox/credential-mask-files.d.ts +72 -0
  7. package/dist/sandbox/credential-mask-files.d.ts.map +1 -0
  8. package/dist/sandbox/credential-mask-files.js +146 -0
  9. package/dist/sandbox/credential-mask-files.js.map +1 -0
  10. package/dist/sandbox/credential-sentinel.d.ts +72 -0
  11. package/dist/sandbox/credential-sentinel.d.ts.map +1 -0
  12. package/dist/sandbox/credential-sentinel.js +130 -0
  13. package/dist/sandbox/credential-sentinel.js.map +1 -0
  14. package/dist/sandbox/domain-pattern.d.ts +36 -0
  15. package/dist/sandbox/domain-pattern.d.ts.map +1 -0
  16. package/dist/sandbox/domain-pattern.js +60 -0
  17. package/dist/sandbox/domain-pattern.js.map +1 -0
  18. package/dist/sandbox/http-proxy.d.ts +32 -1
  19. package/dist/sandbox/http-proxy.d.ts.map +1 -1
  20. package/dist/sandbox/http-proxy.js +10 -2
  21. package/dist/sandbox/http-proxy.js.map +1 -1
  22. package/dist/sandbox/linux-sandbox-utils.d.ts +15 -0
  23. package/dist/sandbox/linux-sandbox-utils.d.ts.map +1 -1
  24. package/dist/sandbox/linux-sandbox-utils.js +106 -47
  25. package/dist/sandbox/linux-sandbox-utils.js.map +1 -1
  26. package/dist/sandbox/listen-in-range.d.ts +12 -0
  27. package/dist/sandbox/listen-in-range.d.ts.map +1 -0
  28. package/dist/sandbox/listen-in-range.js +43 -0
  29. package/dist/sandbox/listen-in-range.js.map +1 -0
  30. package/dist/sandbox/macos-sandbox-utils.d.ts +11 -0
  31. package/dist/sandbox/macos-sandbox-utils.d.ts.map +1 -1
  32. package/dist/sandbox/macos-sandbox-utils.js +69 -14
  33. package/dist/sandbox/macos-sandbox-utils.js.map +1 -1
  34. package/dist/sandbox/mitm-ca.d.ts +18 -2
  35. package/dist/sandbox/mitm-ca.d.ts.map +1 -1
  36. package/dist/sandbox/mitm-ca.js +49 -9
  37. package/dist/sandbox/mitm-ca.js.map +1 -1
  38. package/dist/sandbox/mitm-leaf.d.ts.map +1 -1
  39. package/dist/sandbox/mitm-leaf.js +24 -6
  40. package/dist/sandbox/mitm-leaf.js.map +1 -1
  41. package/dist/sandbox/mux-proxy.d.ts +59 -0
  42. package/dist/sandbox/mux-proxy.d.ts.map +1 -0
  43. package/dist/sandbox/mux-proxy.js +159 -0
  44. package/dist/sandbox/mux-proxy.js.map +1 -0
  45. package/dist/sandbox/request-filter.d.ts +11 -1
  46. package/dist/sandbox/request-filter.d.ts.map +1 -1
  47. package/dist/sandbox/request-filter.js.map +1 -1
  48. package/dist/sandbox/sandbox-config.d.ts +254 -25
  49. package/dist/sandbox/sandbox-config.d.ts.map +1 -1
  50. package/dist/sandbox/sandbox-config.js +200 -13
  51. package/dist/sandbox/sandbox-config.js.map +1 -1
  52. package/dist/sandbox/sandbox-manager.d.ts +4 -0
  53. package/dist/sandbox/sandbox-manager.d.ts.map +1 -1
  54. package/dist/sandbox/sandbox-manager.js +601 -194
  55. package/dist/sandbox/sandbox-manager.js.map +1 -1
  56. package/dist/sandbox/sandbox-schemas.d.ts +15 -0
  57. package/dist/sandbox/sandbox-schemas.d.ts.map +1 -1
  58. package/dist/sandbox/sandbox-utils.d.ts +42 -6
  59. package/dist/sandbox/sandbox-utils.d.ts.map +1 -1
  60. package/dist/sandbox/sandbox-utils.js +115 -27
  61. package/dist/sandbox/sandbox-utils.js.map +1 -1
  62. package/dist/sandbox/socks-proxy.d.ts +11 -5
  63. package/dist/sandbox/socks-proxy.d.ts.map +1 -1
  64. package/dist/sandbox/socks-proxy.js +13 -81
  65. package/dist/sandbox/socks-proxy.js.map +1 -1
  66. package/dist/sandbox/tls-terminate-proxy.d.ts +2 -2
  67. package/dist/sandbox/tls-terminate-proxy.d.ts.map +1 -1
  68. package/dist/sandbox/tls-terminate-proxy.js +31 -5
  69. package/dist/sandbox/tls-terminate-proxy.js.map +1 -1
  70. package/dist/sandbox/windows-sandbox-utils.d.ts +347 -11
  71. package/dist/sandbox/windows-sandbox-utils.d.ts.map +1 -1
  72. package/dist/sandbox/windows-sandbox-utils.js +437 -45
  73. package/dist/sandbox/windows-sandbox-utils.js.map +1 -1
  74. package/package.json +7 -4
  75. package/vendor/seccomp/build.ts +8 -30
  76. package/vendor/srt-win/build.ts +21 -0
  77. package/dist/vendor/seccomp/Dockerfile.build +0 -6
  78. package/dist/vendor/seccomp/arm64/apply-seccomp +0 -0
  79. package/dist/vendor/seccomp/build.ts +0 -91
  80. package/dist/vendor/seccomp/x64/apply-seccomp +0 -0
  81. package/dist/vendor/seccomp-src/apply-seccomp.c +0 -280
  82. package/dist/vendor/seccomp-src/seccomp-unix-block.c +0 -148
  83. package/dist/vendor/srt-win/Cargo.lock +0 -361
  84. package/dist/vendor/srt-win/Cargo.toml +0 -46
  85. package/dist/vendor/srt-win/ci/cleanup.ps1 +0 -49
  86. package/dist/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
  87. package/dist/vendor/srt-win/ci/smoke.ps1 +0 -307
  88. package/dist/vendor/srt-win/src/job.rs +0 -102
  89. package/dist/vendor/srt-win/src/launch.rs +0 -732
  90. package/dist/vendor/srt-win/src/lib.rs +0 -20
  91. package/dist/vendor/srt-win/src/main.rs +0 -669
  92. package/dist/vendor/srt-win/src/self_protect.rs +0 -169
  93. package/dist/vendor/srt-win/src/sid.rs +0 -296
  94. package/dist/vendor/srt-win/src/token.rs +0 -341
  95. package/dist/vendor/srt-win/src/util.rs +0 -42
  96. package/dist/vendor/srt-win/src/wfp.rs +0 -992
  97. package/dist/vendor/srt-win/src/winsta.rs +0 -209
  98. package/dist/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
  99. package/vendor/seccomp-src/apply-seccomp.c +0 -280
  100. package/vendor/seccomp-src/seccomp-unix-block.c +0 -148
  101. package/vendor/srt-win/Cargo.lock +0 -361
  102. package/vendor/srt-win/Cargo.toml +0 -46
  103. package/vendor/srt-win/ci/cleanup.ps1 +0 -49
  104. package/vendor/srt-win/ci/smoke-exec.ps1 +0 -446
  105. package/vendor/srt-win/ci/smoke.ps1 +0 -307
  106. package/vendor/srt-win/src/job.rs +0 -102
  107. package/vendor/srt-win/src/launch.rs +0 -732
  108. package/vendor/srt-win/src/lib.rs +0 -20
  109. package/vendor/srt-win/src/main.rs +0 -669
  110. package/vendor/srt-win/src/self_protect.rs +0 -169
  111. package/vendor/srt-win/src/sid.rs +0 -296
  112. package/vendor/srt-win/src/token.rs +0 -341
  113. package/vendor/srt-win/src/util.rs +0 -42
  114. package/vendor/srt-win/src/wfp.rs +0 -992
  115. package/vendor/srt-win/src/winsta.rs +0 -209
  116. package/vendor/srt-win/tests/sd_access_check_matrix.rs +0 -259
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Based on [Anthropic Sandbox Runtime](https://github.com/anthropic-experimental/sandbox-runtime).
7
7
 
8
- This fork will be rebased on [SRT](https://github.com/anthropic-experimental/sandbox-runtime) continuously (currently based on upstream **v0.0.56**).
8
+ This fork will be rebased on [SRT](https://github.com/anthropic-experimental/sandbox-runtime) continuously (currently based on upstream **v0.0.61**).
9
9
  As new features become available, equivalent local ones will be removed.
10
10
 
11
11
  > Objective: keep as close to original as possible, while allowing selected additional features.
@@ -320,6 +320,24 @@ Uses an **allow-only pattern** - all network access is denied by default.
320
320
  - `https` - Proxy URL for HTTPS/CONNECT traffic (falls back to `http` if unset)
321
321
  - `noProxy` - Comma-separated bypass list: hostname suffixes and CIDR ranges that connect directly
322
322
 
323
+ **TLS termination** (`network.tlsTerminate`, experimental): when set, HTTPS CONNECTs are terminated in-process so SRT can see (and filter, via `network.filterRequest`) the decrypted requests. The sandboxed process is pointed at a trust bundle containing the MITM CA (`caCertPath`/`caKeyPath`, or an ephemeral CA if omitted) plus the host's regular roots, so proxy-minted certificates and real upstream certificates both verify.
324
+
325
+ - `network.tlsTerminate.excludeDomains` - Domain patterns (same syntax as `allowedDomains`) that are **not** terminated. Matching CONNECTs are tunnelled opaquely instead: they are still subject to the domain allowlist, but the client inside the sandbox completes its own TLS handshake with the real upstream, and `filterRequest` / credential injection do not apply to their HTTPS traffic. Use this for the two cases TLS termination fundamentally breaks:
326
+ - **mTLS upstreams** - only the in-sandbox client holds the client certificate, so the proxy cannot re-originate the connection on its behalf.
327
+ - **Certificate-pinning clients** - clients that verify the upstream's identity themselves (custom CAs, SAN pinning) and reject the MITM certificate.
328
+
329
+ ```json
330
+ {
331
+ "network": {
332
+ "allowedDomains": ["*.example.com", "internal-mtls.example.net"],
333
+ "deniedDomains": [],
334
+ "tlsTerminate": {
335
+ "excludeDomains": ["internal-mtls.example.net"]
336
+ }
337
+ }
338
+ }
339
+ ```
340
+
323
341
  **Unix Socket Settings** (platform-specific behavior):
324
342
 
325
343
  | Setting | macOS | Linux |
@@ -455,7 +473,7 @@ Watchman accesses files outside the sandbox boundaries, which will trigger permi
455
473
 
456
474
  - **macOS**: Uses `sandbox-exec` with custom profiles (no additional dependencies)
457
475
  - **Linux**: Uses `bubblewrap` (bwrap) for containerization
458
- - **Windows**: Not yet supported
476
+ - **Windows**: Alpha see [Windows (alpha)](#windows-alpha) below for the threat model and known limitations
459
477
 
460
478
  ### Platform-Specific Dependencies
461
479
 
@@ -498,6 +516,29 @@ The package includes pre-generated seccomp BPF filters for x86-64 and arm archit
498
516
  - Install via Homebrew: `brew install ripgrep`
499
517
  - Or download from: https://github.com/BurntSushi/ripgrep/releases
500
518
 
519
+ ## Windows (alpha)
520
+
521
+ Windows support is **alpha and the design is in flux**. The current implementation provides network egress filtering (via the Windows Filtering Platform) and file read/write denial (via ACL stamping), but it is **not a security boundary against a deliberately adversarial sandboxed process** — see [Threat model](#threat-model) below. The mechanism will be substantially revised (the sandboxed process will run under a separate sandbox user account) in a future version.
522
+
523
+ ### Setup
524
+
525
+ Run `npx @sysid/sandbox-runtime-improved windows-install` once per machine (requires admin; creates the `sandbox-runtime-net` local group and installs the WFP egress filters). **Log out and log back in** for group membership to take effect on your token. After that, `SandboxManager.initialize()` and the `srti` CLI work as on other platforms.
526
+
527
+ ### Threat model
528
+
529
+ **Important:** the Windows sandbox runs the child as the **same user** as the host, with a restricted token (the discriminator group is flipped to deny-only). This protects against **accidental** access by well-behaved tools, and against **deliberate in-token** access by the sandboxed process itself — code running under the restricted token cannot open denied files or make direct outbound connections.
530
+
531
+ It does **not** protect against a sandboxed process that deliberately escapes via system-service brokering. Task Scheduler, `PROC_THREAD_ATTRIBUTE_PARENT_PROCESS` (re-parenting onto an unrestricted process such as `explorer.exe`), BITS, and certain COM out-of-process servers will spawn a process under the user's full, unrestricted interactive token. That process can then read or write any "denied" file and make unrestricted network connections. There is no same-user fix for this class of escape; the structural answer is running the sandboxed process under a **separate sandbox user account**, which is the planned next revision.
532
+
533
+ Until that revision lands, treat the Windows sandbox as **best-effort defense against accidental access and well-behaved tooling**, not as containment for an adversarial process.
534
+
535
+ ### Known limitations
536
+
537
+ - **Not a security boundary against a deliberate child** — see [Threat model](#threat-model) above.
538
+ - `filesystem.allowWrite` (write allow-list) and `filesystem.allowRead` (re-allow within denyRead) are **not supported** on Windows — `initialize()` rejects a config that sets either. Only `denyRead` / `denyWrite` are applied.
539
+ - `network.tlsTerminate` is not yet supported on Windows.
540
+ - Directory targets in `filesystem.denyRead` / `filesystem.denyWrite` are not yet supported (individual files only).
541
+
501
542
  ## Development
502
543
 
503
544
  ```bash
package/dist/index.d.ts CHANGED
@@ -3,11 +3,11 @@ export { SandboxViolationStore } from './sandbox/sandbox-violation-store.js';
3
3
  export type { SandboxRuntimeConfig, NetworkConfig, FilesystemConfig, CredentialsConfig, CredentialFileConfig, CredentialEnvVarConfig, CredentialMode, IgnoreViolationsConfig, } from './sandbox/sandbox-config.js';
4
4
  export { SandboxRuntimeConfigSchema, NetworkConfigSchema, FilesystemConfigSchema, CredentialsConfigSchema, IgnoreViolationsConfigSchema, RipgrepConfigSchema, } from './sandbox/sandbox-config.js';
5
5
  export type { SandboxAskCallback, FsReadRestrictionConfig, FsWriteRestrictionConfig, CredentialRestrictionConfig, NetworkRestrictionConfig, NetworkHostPattern, } from './sandbox/sandbox-schemas.js';
6
- export type { FilterRequestCallback, RequestDecision, } from './sandbox/request-filter.js';
6
+ export type { FilterRequestCallback, RequestDecision, MutateForwardedHeaders, } from './sandbox/request-filter.js';
7
7
  export type { SandboxViolationEvent } from './sandbox/macos-sandbox-utils.js';
8
8
  export { type SandboxDependencyCheck } from './sandbox/linux-sandbox-utils.js';
9
- export { getSrtWinPath, getWindowsGroupStatus, getWindowsWfpStatus, installWindowsSandbox, uninstallWindowsSandbox, createWindowsGroup, deleteWindowsGroup, createWindowsWfp, windowsInstallInstructions, DEFAULT_WINDOWS_GROUP_NAME, DEFAULT_WINDOWS_PROXY_PORT_RANGE, } from './sandbox/windows-sandbox-utils.js';
10
- export type { WindowsGroupRef, WindowsInstallOptions, WindowsInstallResult, WindowsGroupStatus, WindowsGroupStatusResult, WindowsWfpStatus, WindowsWfpStatusResult, } from './sandbox/windows-sandbox-utils.js';
9
+ export { getSrtWinPath, getWindowsGroupStatus, getWindowsWfpStatus, getWindowsSandboxUserStatus, getWindowsSandboxCaCert, windowsTrustCa, installWindowsSandbox, uninstallWindowsSandbox, createWindowsGroup, deleteWindowsGroup, createWindowsWfp, windowsInstallInstructions, stampWindowsAcl, restoreWindowsAcl, expandWindowsFsDenyPaths, WINDOWS_ACL_PATH_OK, WINDOWS_ACL_PARENT_OK, DEFAULT_WINDOWS_GROUP_NAME, DEFAULT_WINDOWS_PROXY_PORT_RANGE, } from './sandbox/windows-sandbox-utils.js';
10
+ export type { WindowsGroupRef, WindowsInstallOptions, WindowsInstallResult, WindowsGroupStatus, WindowsGroupStatusResult, WindowsWfpStatus, WindowsAclStampOptions, WindowsAclRestoreOptions, WindowsAclRestoreResult, WindowsAclPathOutcome, WindowsAclParentOutcome, WindowsWfpStatusResult, WindowsSandboxUserStatus, } from './sandbox/windows-sandbox-utils.js';
11
11
  export type { WindowsConfig } from './sandbox/sandbox-config.js';
12
12
  export { WindowsConfigSchema } from './sandbox/sandbox-config.js';
13
13
  export { getDefaultWritePaths } from './sandbox/sandbox-utils.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AAG5E,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,sBAAsB,GACvB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,6BAA6B,CAAA;AAGpC,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,8BAA8B,CAAA;AAGrC,YAAY,EACV,qBAAqB,EACrB,eAAe,GAChB,MAAM,6BAA6B,CAAA;AAGpC,YAAY,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AAC7E,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAG9E,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,oCAAoC,CAAA;AAC3C,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,oCAAoC,CAAA;AAC3C,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAGjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAGjE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AAG5E,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,gBAAgB,EAChB,iBAAiB,EACjB,oBAAoB,EACpB,sBAAsB,EACtB,cAAc,EACd,sBAAsB,GACvB,MAAM,6BAA6B,CAAA;AAEpC,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,6BAA6B,CAAA;AAGpC,YAAY,EACV,kBAAkB,EAClB,uBAAuB,EACvB,wBAAwB,EACxB,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,8BAA8B,CAAA;AAGrC,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,sBAAsB,GACvB,MAAM,6BAA6B,CAAA;AAGpC,YAAY,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAA;AAC7E,OAAO,EAAE,KAAK,sBAAsB,EAAE,MAAM,kCAAkC,CAAA;AAG9E,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,oCAAoC,CAAA;AAC3C,YAAY,EACV,eAAe,EACf,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,EACxB,gBAAgB,EAChB,sBAAsB,EACtB,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,oCAAoC,CAAA;AAC3C,YAAY,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAGjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAGjE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,YAAY,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ export { SandboxManager } from './sandbox/sandbox-manager.js';
3
3
  export { SandboxViolationStore } from './sandbox/sandbox-violation-store.js';
4
4
  export { SandboxRuntimeConfigSchema, NetworkConfigSchema, FilesystemConfigSchema, CredentialsConfigSchema, IgnoreViolationsConfigSchema, RipgrepConfigSchema, } from './sandbox/sandbox-config.js';
5
5
  // Windows install/status API
6
- export { getSrtWinPath, getWindowsGroupStatus, getWindowsWfpStatus, installWindowsSandbox, uninstallWindowsSandbox, createWindowsGroup, deleteWindowsGroup, createWindowsWfp, windowsInstallInstructions, DEFAULT_WINDOWS_GROUP_NAME, DEFAULT_WINDOWS_PROXY_PORT_RANGE, } from './sandbox/windows-sandbox-utils.js';
6
+ export { getSrtWinPath, getWindowsGroupStatus, getWindowsWfpStatus, getWindowsSandboxUserStatus, getWindowsSandboxCaCert, windowsTrustCa, installWindowsSandbox, uninstallWindowsSandbox, createWindowsGroup, deleteWindowsGroup, createWindowsWfp, windowsInstallInstructions, stampWindowsAcl, restoreWindowsAcl, expandWindowsFsDenyPaths, WINDOWS_ACL_PATH_OK, WINDOWS_ACL_PARENT_OK, DEFAULT_WINDOWS_GROUP_NAME, DEFAULT_WINDOWS_PROXY_PORT_RANGE, } from './sandbox/windows-sandbox-utils.js';
7
7
  export { WindowsConfigSchema } from './sandbox/sandbox-config.js';
8
8
  // Utility functions
9
9
  export { getDefaultWritePaths } from './sandbox/sandbox-utils.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kBAAkB;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AAc5E,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,6BAA6B,CAAA;AAsBpC,6BAA6B;AAC7B,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,oCAAoC,CAAA;AAW3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAEjE,oBAAoB;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEjE,qBAAqB;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kBAAkB;AAClB,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sCAAsC,CAAA;AAc5E,OAAO,EACL,0BAA0B,EAC1B,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,GACpB,MAAM,6BAA6B,CAAA;AAuBpC,6BAA6B;AAC7B,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,2BAA2B,EAC3B,uBAAuB,EACvB,cAAc,EACd,qBAAqB,EACrB,uBAAuB,EACvB,kBAAkB,EAClB,kBAAkB,EAClB,gBAAgB,EAChB,0BAA0B,EAC1B,eAAe,EACf,iBAAiB,EACjB,wBAAwB,EACxB,mBAAmB,EACnB,qBAAqB,EACrB,0BAA0B,EAC1B,gCAAgC,GACjC,MAAM,oCAAoC,CAAA;AAiB3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AAEjE,oBAAoB;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,4BAA4B,CAAA;AAEjE,qBAAqB;AACrB,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Whole-file credential masking (Linux).
3
+ *
4
+ * For a `credentials.files` entry with `mode: "mask"`, srt reads the real
5
+ * file content on the host, registers a sentinel for it in the
6
+ * {@link SentinelRegistry}, and writes the sentinel to a fake file in a
7
+ * manager-owned temp directory. The Linux sandbox then `--ro-bind`s the
8
+ * fake over the real path, so the sandboxed process reads the sentinel.
9
+ * The proxy substitution from env-var masking already scans every header
10
+ * for any registered sentinel, so a tool that does
11
+ * `Authorization: Bearer $(cat <maskedFile>)` reaches the upstream with
12
+ * the real bytes — no proxy changes required.
13
+ *
14
+ * On macOS, SBPL cannot redirect reads, so masked files degrade to
15
+ * `mode: "deny"` (see macos-sandbox-utils.ts).
16
+ *
17
+ * LIMITATION: this is whole-file masking. It works when the file content
18
+ * *is* the credential (a token file). It does not work for structured
19
+ * files a tool parses (JSON/YAML/.netrc) — the tool will fail to parse
20
+ * the sentinel. See {@link CredentialFileConfigSchema}.
21
+ */
22
+ import type { CredentialFileConfig } from './sandbox-config.js';
23
+ import type { SentinelRegistry } from './credential-sentinel.js';
24
+ /** One masked file's bind mapping for the platform builder. */
25
+ export interface MaskedFileBind {
26
+ /** Resolved (tilde-expanded, realpath'd) host path of the real file. */
27
+ realPath: string;
28
+ /** Path to the fake file containing the sentinel. */
29
+ fakePath: string;
30
+ }
31
+ /**
32
+ * Manager-owned temp dir holding the fake files.
33
+ *
34
+ * INVARIANT: this directory must never be writable from inside the sandbox.
35
+ * The Linux layer enforces this by emitting `--ro-bind <dirPath> <dirPath>`
36
+ * after every other filesystem mount (see generateFilesystemArgs), so the
37
+ * store stays read-only even if a caller's allowWrite covers os.tmpdir() or
38
+ * the host's $TMPDIR points under a default-writable path. If the sandbox
39
+ * could write here it could replace a fake's content (the bind exposes the
40
+ * source file) or plant a symlink for a later host-side write() to follow.
41
+ */
42
+ export declare class MaskedFileStore {
43
+ private dir;
44
+ private readonly byKey;
45
+ /**
46
+ * Write `sentinel` to a fake file for `key` and return its path.
47
+ * Idempotent on `key`: a repeat call rewrites the same fake (so a
48
+ * changed sentinel after re-register propagates) instead of leaking a
49
+ * new file per wrapWithSandbox() call.
50
+ */
51
+ write(key: string, sentinel: string): string;
52
+ /** Remove the temp dir and every fake file in it. Idempotent. */
53
+ dispose(): void;
54
+ /** Temp dir path, or undefined if no fake has been written yet. */
55
+ get dirPath(): string | undefined;
56
+ }
57
+ /**
58
+ * For each `mode: "mask"` file entry: resolve the path, read the real
59
+ * content, register `(file:<path>, content, injectHosts)` in `registry`,
60
+ * write the sentinel to a fake file via `store`, and return the bind list.
61
+ *
62
+ * Entries whose path does not exist, is unreadable, or resolves to a
63
+ * directory are skipped with a debug log — same posture as a masked env
64
+ * var that's unset on the host: nothing to protect, and surfacing a hard
65
+ * error would make a portable config brittle across machines.
66
+ *
67
+ * The directory check is the authoritative one (the schema only catches a
68
+ * trailing slash); whole-file masking has no meaning for a directory.
69
+ */
70
+ export declare function buildMaskedFileBinds(files: readonly CredentialFileConfig[], allowedDomains: readonly string[], registry: SentinelRegistry, store: MaskedFileStore): MaskedFileBind[];
71
+ export declare const MASKED_FILE_STORE_PREFIX = "srt-credmask-";
72
+ //# sourceMappingURL=credential-mask-files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-mask-files.d.ts","sourceRoot":"","sources":["../../src/sandbox/credential-mask-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAOH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAA;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAA;AAShE,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,wEAAwE;IACxE,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAoB;IAC/B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA4B;IAElD;;;;;OAKG;IACH,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM;IAoB5C,iEAAiE;IACjE,OAAO,IAAI,IAAI;IAcf,mEAAmE;IACnE,IAAI,OAAO,IAAI,MAAM,GAAG,SAAS,CAEhC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,oBAAoB,EAAE,EACtC,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,eAAe,GACrB,cAAc,EAAE,CA8ClB;AAED,eAAO,MAAM,wBAAwB,kBAAkB,CAAA"}
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Whole-file credential masking (Linux).
3
+ *
4
+ * For a `credentials.files` entry with `mode: "mask"`, srt reads the real
5
+ * file content on the host, registers a sentinel for it in the
6
+ * {@link SentinelRegistry}, and writes the sentinel to a fake file in a
7
+ * manager-owned temp directory. The Linux sandbox then `--ro-bind`s the
8
+ * fake over the real path, so the sandboxed process reads the sentinel.
9
+ * The proxy substitution from env-var masking already scans every header
10
+ * for any registered sentinel, so a tool that does
11
+ * `Authorization: Bearer $(cat <maskedFile>)` reaches the upstream with
12
+ * the real bytes — no proxy changes required.
13
+ *
14
+ * On macOS, SBPL cannot redirect reads, so masked files degrade to
15
+ * `mode: "deny"` (see macos-sandbox-utils.ts).
16
+ *
17
+ * LIMITATION: this is whole-file masking. It works when the file content
18
+ * *is* the credential (a token file). It does not work for structured
19
+ * files a tool parses (JSON/YAML/.netrc) — the tool will fail to parse
20
+ * the sentinel. See {@link CredentialFileConfigSchema}.
21
+ */
22
+ import * as fs from 'node:fs';
23
+ import { tmpdir } from 'node:os';
24
+ import { join } from 'node:path';
25
+ import { logForDebugging } from '../utils/debug.js';
26
+ import { normalizePathForSandbox } from './sandbox-utils.js';
27
+ /**
28
+ * Sentinel-registry key prefix for masked files. Keeps file keys disjoint
29
+ * from env-var names so a credential file at path `GH_TOKEN` cannot collide
30
+ * with the env var `GH_TOKEN`.
31
+ */
32
+ const FILE_KEY_PREFIX = 'file:';
33
+ /**
34
+ * Manager-owned temp dir holding the fake files.
35
+ *
36
+ * INVARIANT: this directory must never be writable from inside the sandbox.
37
+ * The Linux layer enforces this by emitting `--ro-bind <dirPath> <dirPath>`
38
+ * after every other filesystem mount (see generateFilesystemArgs), so the
39
+ * store stays read-only even if a caller's allowWrite covers os.tmpdir() or
40
+ * the host's $TMPDIR points under a default-writable path. If the sandbox
41
+ * could write here it could replace a fake's content (the bind exposes the
42
+ * source file) or plant a symlink for a later host-side write() to follow.
43
+ */
44
+ export class MaskedFileStore {
45
+ constructor() {
46
+ this.byKey = new Map();
47
+ }
48
+ /**
49
+ * Write `sentinel` to a fake file for `key` and return its path.
50
+ * Idempotent on `key`: a repeat call rewrites the same fake (so a
51
+ * changed sentinel after re-register propagates) instead of leaking a
52
+ * new file per wrapWithSandbox() call.
53
+ */
54
+ write(key, sentinel) {
55
+ if (this.dir === undefined) {
56
+ this.dir = fs.mkdtempSync(join(tmpdir(), 'srt-credmask-'));
57
+ }
58
+ let fakePath = this.byKey.get(key);
59
+ if (fakePath === undefined) {
60
+ fakePath = join(this.dir, `${this.byKey.size}.fake`);
61
+ this.byKey.set(key, fakePath);
62
+ }
63
+ // Never follow a symlink at fakePath: a prior sandbox invocation may
64
+ // have planted one (the store dir is ro-bound now, but defence in
65
+ // depth). Unlink first so writeFileSync creates a fresh regular file.
66
+ fs.rmSync(fakePath, { force: true });
67
+ // 0600: owner rw so the idempotent rewrite above succeeds; the bind
68
+ // into the sandbox is --ro-bind so the sandboxed process sees it
69
+ // read-only regardless of the host mode. No group/other.
70
+ fs.writeFileSync(fakePath, sentinel, { mode: 0o600 });
71
+ return fakePath;
72
+ }
73
+ /** Remove the temp dir and every fake file in it. Idempotent. */
74
+ dispose() {
75
+ if (this.dir !== undefined) {
76
+ try {
77
+ fs.rmSync(this.dir, { recursive: true, force: true });
78
+ }
79
+ catch (err) {
80
+ logForDebugging(`MaskedFileStore cleanup failed: ${err}`, {
81
+ level: 'error',
82
+ });
83
+ }
84
+ }
85
+ this.dir = undefined;
86
+ this.byKey.clear();
87
+ }
88
+ /** Temp dir path, or undefined if no fake has been written yet. */
89
+ get dirPath() {
90
+ return this.dir;
91
+ }
92
+ }
93
+ /**
94
+ * For each `mode: "mask"` file entry: resolve the path, read the real
95
+ * content, register `(file:<path>, content, injectHosts)` in `registry`,
96
+ * write the sentinel to a fake file via `store`, and return the bind list.
97
+ *
98
+ * Entries whose path does not exist, is unreadable, or resolves to a
99
+ * directory are skipped with a debug log — same posture as a masked env
100
+ * var that's unset on the host: nothing to protect, and surfacing a hard
101
+ * error would make a portable config brittle across machines.
102
+ *
103
+ * The directory check is the authoritative one (the schema only catches a
104
+ * trailing slash); whole-file masking has no meaning for a directory.
105
+ */
106
+ export function buildMaskedFileBinds(files, allowedDomains, registry, store) {
107
+ const binds = [];
108
+ for (const f of files) {
109
+ if (f.mode !== 'mask')
110
+ continue;
111
+ const realPath = normalizePathForSandbox(f.path);
112
+ let content;
113
+ try {
114
+ const stat = fs.statSync(realPath);
115
+ if (stat.isDirectory()) {
116
+ logForDebugging(`[credential-mask] Skipping masked file entry that resolves to ` +
117
+ `a directory: ${f.path} — use mode "deny" for directories.`, { level: 'warn' });
118
+ continue;
119
+ }
120
+ // Read as bytes first: a utf8 read silently maps invalid sequences
121
+ // to U+FFFD, so the sentinel would round-trip to corrupted bytes at
122
+ // the proxy. Whole-file masking is for token files; reject binary.
123
+ const raw = fs.readFileSync(realPath);
124
+ content = raw.toString('utf8');
125
+ if (Buffer.byteLength(content, 'utf8') !== raw.length) {
126
+ logForDebugging(`[credential-mask] Skipping masked file with non-UTF-8 content ` +
127
+ `(binary credential files are not supported in whole-file ` +
128
+ `mask mode): ${f.path}`, { level: 'warn' });
129
+ continue;
130
+ }
131
+ }
132
+ catch (err) {
133
+ logForDebugging(`[credential-mask] Skipping masked file (unreadable on host): ` +
134
+ `${f.path} — ${err.message}`);
135
+ continue;
136
+ }
137
+ const injectHosts = f.injectHosts ?? allowedDomains;
138
+ const key = FILE_KEY_PREFIX + realPath;
139
+ const sentinel = registry.register(key, content, injectHosts);
140
+ const fakePath = store.write(key, sentinel);
141
+ binds.push({ realPath, fakePath });
142
+ }
143
+ return binds;
144
+ }
145
+ export const MASKED_FILE_STORE_PREFIX = 'srt-credmask-';
146
+ //# sourceMappingURL=credential-mask-files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-mask-files.js","sourceRoot":"","sources":["../../src/sandbox/credential-mask-files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Per-session sentinel registry for credential masking.
3
+ *
4
+ * A masked credential's real value is replaced inside the sandbox with a
5
+ * sentinel of the form `fake_value_<uuid4>`. The sandboxed process sees only
6
+ * the sentinel; the host-side proxy substitutes sentinel→real on egress to
7
+ * allowlisted destinations. The map lives only in process memory — it is
8
+ * never written to disk and never logged.
9
+ */
10
+ import type { IncomingHttpHeaders } from 'node:http';
11
+ export declare const SENTINEL_PREFIX = "fake_value_";
12
+ /** Predicate matching a destination host against one injectHosts pattern. */
13
+ export type HostMatcher = (host: string, pattern: string) => boolean;
14
+ /**
15
+ * Sentinel↔real-value map for one sandbox session, keyed by credential name.
16
+ *
17
+ * Each credential carries its own `injectHosts` list, and substitution is
18
+ * gated per sentinel: a sentinel is swapped to its real value only when the
19
+ * destination matches THAT credential's hosts. This prevents laundering
20
+ * credential A through credential B's allowlisted host by sending A's
21
+ * sentinel there — the proxy leaves A's sentinel intact on B's host.
22
+ *
23
+ * Keying on name (not value) means two env vars holding the same secret get
24
+ * distinct sentinels, so each can have an independent host list.
25
+ */
26
+ export declare class SentinelRegistry {
27
+ private readonly byName;
28
+ private readonly bySentinel;
29
+ /**
30
+ * Return the sentinel for the credential named `name`, minting a fresh one
31
+ * on first use. The sentinel is `fake_value_<uuid4>`: long enough that an
32
+ * accidental collision with legitimate header content is negligible, and
33
+ * free of shell/URL metacharacters so it survives `--setenv` and
34
+ * `env NAME=value` unquoted.
35
+ *
36
+ * Idempotent on `name`: a repeat call returns the same sentinel and updates
37
+ * `realValue`/`injectHosts` in place so `updateConfig()` can change either
38
+ * without invalidating sentinels the sandboxed process has already read.
39
+ */
40
+ register(name: string, realValue: string, injectHosts: readonly string[]): string;
41
+ /** Real value for `sentinel`, or undefined if not registered. */
42
+ lookupReal(sentinel: string): string | undefined;
43
+ /**
44
+ * Names of the registered credentials whose `injectHosts` cover
45
+ * `destHost`. Diagnostic helper: the proxy uses it to warn when a host is
46
+ * exempted from TLS termination (so substitution can never run there) but
47
+ * a masked credential is configured for injection at it.
48
+ */
49
+ namesInjectableAt(destHost: string, matches: HostMatcher): string[];
50
+ /** Iterate registered `[sentinel, realValue]` pairs. */
51
+ entries(): IterableIterator<[string, string]>;
52
+ /** Number of registered sentinels. */
53
+ get size(): number;
54
+ /** Drop every mapping. Called on session teardown. */
55
+ clear(): void;
56
+ /**
57
+ * Replace registered sentinels found in `headers` with their real values,
58
+ * in place. Each sentinel substitutes only when `destHost` matches one of
59
+ * THAT credential's `injectHosts` patterns (via `matches`); a sentinel
60
+ * whose host list does not cover `destHost` is left as the useless fake.
61
+ *
62
+ * Scans all header values rather than a fixed set — a sentinel showing up
63
+ * anywhere is the substitution trigger, regardless of header name
64
+ * (Authorization, X-Api-Key, Private-Token, ...).
65
+ *
66
+ * The caller remains responsible for transport gating (TLS-terminated path
67
+ * unless `allowPlaintextInject`).
68
+ */
69
+ substituteInHeaders(headers: IncomingHttpHeaders, destHost: string, matches: HostMatcher): void;
70
+ private substituteInString;
71
+ }
72
+ //# sourceMappingURL=credential-sentinel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-sentinel.d.ts","sourceRoot":"","sources":["../../src/sandbox/credential-sentinel.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAA;AAEpD,eAAO,MAAM,eAAe,gBAAgB,CAAA;AAE5C,6EAA6E;AAC7E,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAA;AASpE;;;;;;;;;;;GAWG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmC;IAC1D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAmC;IAE9D;;;;;;;;;;OAUG;IACH,QAAQ,CACN,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,MAAM;IAcT,iEAAiE;IACjE,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD;;;;;OAKG;IACH,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,EAAE;IAQnE,wDAAwD;IACvD,OAAO,IAAI,gBAAgB,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAI9C,sCAAsC;IACtC,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,sDAAsD;IACtD,KAAK,IAAI,IAAI;IAKb;;;;;;;;;;;;OAYG;IACH,mBAAmB,CACjB,OAAO,EAAE,mBAAmB,EAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,WAAW,GACnB,IAAI;IAcP,OAAO,CAAC,kBAAkB;CAgB3B"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Per-session sentinel registry for credential masking.
3
+ *
4
+ * A masked credential's real value is replaced inside the sandbox with a
5
+ * sentinel of the form `fake_value_<uuid4>`. The sandboxed process sees only
6
+ * the sentinel; the host-side proxy substitutes sentinel→real on egress to
7
+ * allowlisted destinations. The map lives only in process memory — it is
8
+ * never written to disk and never logged.
9
+ */
10
+ import { randomUUID } from 'node:crypto';
11
+ export const SENTINEL_PREFIX = 'fake_value_';
12
+ /**
13
+ * Sentinel↔real-value map for one sandbox session, keyed by credential name.
14
+ *
15
+ * Each credential carries its own `injectHosts` list, and substitution is
16
+ * gated per sentinel: a sentinel is swapped to its real value only when the
17
+ * destination matches THAT credential's hosts. This prevents laundering
18
+ * credential A through credential B's allowlisted host by sending A's
19
+ * sentinel there — the proxy leaves A's sentinel intact on B's host.
20
+ *
21
+ * Keying on name (not value) means two env vars holding the same secret get
22
+ * distinct sentinels, so each can have an independent host list.
23
+ */
24
+ export class SentinelRegistry {
25
+ constructor() {
26
+ this.byName = new Map();
27
+ this.bySentinel = new Map();
28
+ }
29
+ /**
30
+ * Return the sentinel for the credential named `name`, minting a fresh one
31
+ * on first use. The sentinel is `fake_value_<uuid4>`: long enough that an
32
+ * accidental collision with legitimate header content is negligible, and
33
+ * free of shell/URL metacharacters so it survives `--setenv` and
34
+ * `env NAME=value` unquoted.
35
+ *
36
+ * Idempotent on `name`: a repeat call returns the same sentinel and updates
37
+ * `realValue`/`injectHosts` in place so `updateConfig()` can change either
38
+ * without invalidating sentinels the sandboxed process has already read.
39
+ */
40
+ register(name, realValue, injectHosts) {
41
+ const existing = this.byName.get(name);
42
+ if (existing !== undefined) {
43
+ existing.realValue = realValue;
44
+ existing.injectHosts = injectHosts;
45
+ return existing.sentinel;
46
+ }
47
+ const sentinel = SENTINEL_PREFIX + randomUUID();
48
+ const entry = { name, sentinel, realValue, injectHosts };
49
+ this.byName.set(name, entry);
50
+ this.bySentinel.set(sentinel, entry);
51
+ return sentinel;
52
+ }
53
+ /** Real value for `sentinel`, or undefined if not registered. */
54
+ lookupReal(sentinel) {
55
+ return this.bySentinel.get(sentinel)?.realValue;
56
+ }
57
+ /**
58
+ * Names of the registered credentials whose `injectHosts` cover
59
+ * `destHost`. Diagnostic helper: the proxy uses it to warn when a host is
60
+ * exempted from TLS termination (so substitution can never run there) but
61
+ * a masked credential is configured for injection at it.
62
+ */
63
+ namesInjectableAt(destHost, matches) {
64
+ const names = [];
65
+ for (const e of this.byName.values()) {
66
+ if (e.injectHosts.some(p => matches(destHost, p)))
67
+ names.push(e.name);
68
+ }
69
+ return names;
70
+ }
71
+ /** Iterate registered `[sentinel, realValue]` pairs. */
72
+ *entries() {
73
+ for (const e of this.bySentinel.values())
74
+ yield [e.sentinel, e.realValue];
75
+ }
76
+ /** Number of registered sentinels. */
77
+ get size() {
78
+ return this.bySentinel.size;
79
+ }
80
+ /** Drop every mapping. Called on session teardown. */
81
+ clear() {
82
+ this.byName.clear();
83
+ this.bySentinel.clear();
84
+ }
85
+ /**
86
+ * Replace registered sentinels found in `headers` with their real values,
87
+ * in place. Each sentinel substitutes only when `destHost` matches one of
88
+ * THAT credential's `injectHosts` patterns (via `matches`); a sentinel
89
+ * whose host list does not cover `destHost` is left as the useless fake.
90
+ *
91
+ * Scans all header values rather than a fixed set — a sentinel showing up
92
+ * anywhere is the substitution trigger, regardless of header name
93
+ * (Authorization, X-Api-Key, Private-Token, ...).
94
+ *
95
+ * The caller remains responsible for transport gating (TLS-terminated path
96
+ * unless `allowPlaintextInject`).
97
+ */
98
+ substituteInHeaders(headers, destHost, matches) {
99
+ if (this.bySentinel.size === 0)
100
+ return;
101
+ for (const [name, value] of Object.entries(headers)) {
102
+ if (value === undefined)
103
+ continue;
104
+ if (Array.isArray(value)) {
105
+ for (let i = 0; i < value.length; i++) {
106
+ value[i] = this.substituteInString(value[i], destHost, matches);
107
+ }
108
+ }
109
+ else {
110
+ headers[name] = this.substituteInString(value, destHost, matches);
111
+ }
112
+ }
113
+ }
114
+ substituteInString(s, destHost, matches) {
115
+ // Fast path: the sentinel prefix is fixed, so a header value that
116
+ // doesn't contain it cannot contain any sentinel.
117
+ if (!s.includes(SENTINEL_PREFIX))
118
+ return s;
119
+ let out = s;
120
+ for (const e of this.bySentinel.values()) {
121
+ if (!out.includes(e.sentinel))
122
+ continue;
123
+ if (!e.injectHosts.some(p => matches(destHost, p)))
124
+ continue;
125
+ out = out.split(e.sentinel).join(e.realValue);
126
+ }
127
+ return out;
128
+ }
129
+ }
130
+ //# sourceMappingURL=credential-sentinel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-sentinel.js","sourceRoot":"","sources":["../../src/sandbox/credential-sentinel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Domain-pattern matching shared between runtime host filtering
3
+ * (sandbox-manager) and config-time validation (sandbox-config).
4
+ * Lives in its own module so the schema can import it without pulling
5
+ * in sandbox-manager (which imports the schema — circular).
6
+ */
7
+ /**
8
+ * Match a hostname against a domain pattern.
9
+ *
10
+ * Patterns:
11
+ * - `*` matches everything (deny-all; the schema only accepts this in
12
+ * deniedDomains).
13
+ * - `*.example.com` matches any strict subdomain of example.com.
14
+ * - anything else matches exactly (case-insensitive).
15
+ *
16
+ * Wildcard suffix matching is refused for IP literals so an IPv6 zone-ID
17
+ * payload like `::ffff:1.2.3.4%x.allowed.com` cannot pass `.endsWith()`
18
+ * while the OS connects to the bare IP. isValidHost already rejects `%`,
19
+ * but we refuse here too for defence in depth.
20
+ */
21
+ export declare function matchesDomainPattern(hostname: string, pattern: string): boolean;
22
+ /**
23
+ * Decide whether a per-credential `injectHosts` entry is reachable via
24
+ * `network.allowedDomains` — i.e. every concrete host that could match
25
+ * `injectHost` is allowed by at least one entry in `allowedDomains`.
26
+ *
27
+ * For an exact `injectHost` (`api.github.com`) this is just
28
+ * `matchesDomainPattern` against each allowed pattern.
29
+ *
30
+ * For a wildcard `injectHost` (`*.X`), an exact allowedDomain can never
31
+ * cover it (it admits only one host), so coverage requires an allowed
32
+ * wildcard `*.Y` whose base is `X` or an ancestor of `X` — e.g.
33
+ * `*.api.github.com` is covered by `*.github.com`.
34
+ */
35
+ export declare function isInjectHostCoveredByAllowedDomains(injectHost: string, allowedDomains: readonly string[]): boolean;
36
+ //# sourceMappingURL=domain-pattern.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-pattern.d.ts","sourceRoot":"","sources":["../../src/sandbox/domain-pattern.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,OAAO,CAST;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mCAAmC,CACjD,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,SAAS,MAAM,EAAE,GAChC,OAAO,CAUT"}