@heddleagent/execution-host-client 6.7.0 → 8.0.0

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 +54 -35
  2. package/dist/agentcore/agentcore-execution-host.d.ts +1 -1
  3. package/dist/agentcore/agentcore-execution-host.d.ts.map +1 -1
  4. package/dist/agentcore/agentcore-execution-host.js +6 -3
  5. package/dist/agentcore/agentcore-execution-host.js.map +1 -1
  6. package/dist/cli/hostctl.d.ts +11 -0
  7. package/dist/cli/hostctl.d.ts.map +1 -0
  8. package/dist/cli/hostctl.js +49 -0
  9. package/dist/cli/hostctl.js.map +1 -0
  10. package/dist/contracts/index.d.ts +18 -1
  11. package/dist/contracts/index.d.ts.map +1 -1
  12. package/dist/contracts/index.js +28 -1
  13. package/dist/contracts/index.js.map +1 -1
  14. package/dist/conversation/hosted-conversation-turn-service.js +2 -2
  15. package/dist/conversation/hosted-conversation-turn-service.js.map +1 -1
  16. package/dist/conversation/types.d.ts +2 -2
  17. package/dist/conversation/types.d.ts.map +1 -1
  18. package/dist/conversation/types.js.map +1 -1
  19. package/dist/coordinator/contracts.d.ts +148 -5
  20. package/dist/coordinator/contracts.d.ts.map +1 -1
  21. package/dist/coordinator/contracts.js +74 -3
  22. package/dist/coordinator/contracts.js.map +1 -1
  23. package/dist/coordinator/hosted-heartbeat-execution-client.d.ts +30 -0
  24. package/dist/coordinator/hosted-heartbeat-execution-client.d.ts.map +1 -0
  25. package/dist/coordinator/hosted-heartbeat-execution-client.js +240 -0
  26. package/dist/coordinator/hosted-heartbeat-execution-client.js.map +1 -0
  27. package/dist/coordinator/hosted-heartbeat-execution-service.d.ts +14 -0
  28. package/dist/coordinator/hosted-heartbeat-execution-service.d.ts.map +1 -0
  29. package/dist/coordinator/hosted-heartbeat-execution-service.js +100 -0
  30. package/dist/coordinator/hosted-heartbeat-execution-service.js.map +1 -0
  31. package/dist/coordinator/index.d.ts +5 -5
  32. package/dist/coordinator/index.d.ts.map +1 -1
  33. package/dist/coordinator/index.js +3 -3
  34. package/dist/coordinator/index.js.map +1 -1
  35. package/dist/coordinator/node/execution-http-service.d.ts +11 -0
  36. package/dist/coordinator/node/execution-http-service.d.ts.map +1 -0
  37. package/dist/coordinator/node/{delegation-http-service.js → execution-http-service.js} +52 -39
  38. package/dist/coordinator/node/execution-http-service.js.map +1 -0
  39. package/dist/coordinator/node/index.d.ts +2 -2
  40. package/dist/coordinator/node/index.d.ts.map +1 -1
  41. package/dist/coordinator/node/index.js +1 -1
  42. package/dist/coordinator/node/index.js.map +1 -1
  43. package/dist/coordinator/node/types.d.ts +10 -9
  44. package/dist/coordinator/node/types.d.ts.map +1 -1
  45. package/dist/coordinator/node/types.js.map +1 -1
  46. package/dist/coordinator/types.d.ts +51 -13
  47. package/dist/coordinator/types.d.ts.map +1 -1
  48. package/dist/coordinator/types.js.map +1 -1
  49. package/dist/heartbeat/hosted-heartbeat-task-service.js +2 -2
  50. package/dist/heartbeat/hosted-heartbeat-task-service.js.map +1 -1
  51. package/dist/host/jwt-execution-host-mcp-capability-verifier.d.ts.map +1 -1
  52. package/dist/host/jwt-execution-host-mcp-capability-verifier.js +4 -2
  53. package/dist/host/jwt-execution-host-mcp-capability-verifier.js.map +1 -1
  54. package/dist/host/jwt-execution-identity-verifier.d.ts.map +1 -1
  55. package/dist/host/jwt-execution-identity-verifier.js +4 -2
  56. package/dist/host/jwt-execution-identity-verifier.js.map +1 -1
  57. package/dist/host/runtime-session/service.d.ts +2 -2
  58. package/dist/host/runtime-session/service.d.ts.map +1 -1
  59. package/dist/host/runtime-session/service.js +1 -1
  60. package/dist/host/runtime-session/service.js.map +1 -1
  61. package/dist/host/runtime-session/types.d.ts +2 -2
  62. package/dist/host/runtime-session/types.d.ts.map +1 -1
  63. package/dist/host/runtime-session/types.js.map +1 -1
  64. package/dist/host/schemas.d.ts +6 -0
  65. package/dist/host/schemas.d.ts.map +1 -1
  66. package/dist/host/schemas.js +18 -1
  67. package/dist/host/schemas.js.map +1 -1
  68. package/dist/host/types.d.ts +2 -0
  69. package/dist/host/types.d.ts.map +1 -1
  70. package/dist/host/types.js.map +1 -1
  71. package/dist/http-sse/direct-http-execution-host.d.ts.map +1 -1
  72. package/dist/http-sse/direct-http-execution-host.js +8 -7
  73. package/dist/http-sse/direct-http-execution-host.js.map +1 -1
  74. package/dist/http-sse/types.d.ts +5 -3
  75. package/dist/http-sse/types.d.ts.map +1 -1
  76. package/dist/http-sse/types.js.map +1 -1
  77. package/dist/node/authority-key.d.ts.map +1 -1
  78. package/dist/node/authority-key.js +24 -49
  79. package/dist/node/authority-key.js.map +1 -1
  80. package/dist/node/direct-credentials.d.ts +3 -7
  81. package/dist/node/direct-credentials.d.ts.map +1 -1
  82. package/dist/node/direct-credentials.js +3 -15
  83. package/dist/node/direct-credentials.js.map +1 -1
  84. package/dist/node/environment-value.d.ts +17 -0
  85. package/dist/node/environment-value.d.ts.map +1 -0
  86. package/dist/node/environment-value.js +78 -0
  87. package/dist/node/environment-value.js.map +1 -0
  88. package/dist/node/index.d.ts +4 -0
  89. package/dist/node/index.d.ts.map +1 -1
  90. package/dist/node/index.js +2 -0
  91. package/dist/node/index.js.map +1 -1
  92. package/dist/node/local-credential-bundle.d.ts +33 -0
  93. package/dist/node/local-credential-bundle.d.ts.map +1 -0
  94. package/dist/node/local-credential-bundle.js +196 -0
  95. package/dist/node/local-credential-bundle.js.map +1 -0
  96. package/dist/node/owner-only-file.d.ts +17 -0
  97. package/dist/node/owner-only-file.d.ts.map +1 -0
  98. package/dist/node/owner-only-file.js +59 -0
  99. package/dist/node/owner-only-file.js.map +1 -0
  100. package/dist/testing/request.d.ts.map +1 -1
  101. package/dist/testing/request.js +14 -4
  102. package/dist/testing/request.js.map +1 -1
  103. package/package.json +5 -1
  104. package/spec/v1/openapi.json +62 -4
  105. package/spec/v1/schema-bundle.json +58 -0
  106. package/dist/coordinator/hosted-heartbeat-delegation-client.d.ts +0 -21
  107. package/dist/coordinator/hosted-heartbeat-delegation-client.d.ts.map +0 -1
  108. package/dist/coordinator/hosted-heartbeat-delegation-client.js +0 -117
  109. package/dist/coordinator/hosted-heartbeat-delegation-client.js.map +0 -1
  110. package/dist/coordinator/hosted-heartbeat-delegation-service.d.ts +0 -15
  111. package/dist/coordinator/hosted-heartbeat-delegation-service.d.ts.map +0 -1
  112. package/dist/coordinator/hosted-heartbeat-delegation-service.js +0 -81
  113. package/dist/coordinator/hosted-heartbeat-delegation-service.js.map +0 -1
  114. package/dist/coordinator/node/delegation-http-service.d.ts +0 -11
  115. package/dist/coordinator/node/delegation-http-service.d.ts.map +0 -1
  116. package/dist/coordinator/node/delegation-http-service.js.map +0 -1
@@ -1,21 +1,17 @@
1
- import type { HostedConversationModelCredentialProvider } from '../conversation/index.js';
2
1
  export type DirectExecutionHostCredentialEnvironmentNames = {
3
2
  localToken: string;
4
- modelApiKey: string;
5
3
  };
6
4
  /**
7
- * Non-enumerable direct-host and model credentials for a local/reviewed
8
- * deployment. Production secret retrieval may construct the same class.
5
+ * Non-enumerable direct-host ingress credential for a local/reviewed
6
+ * deployment. Model credentials use the separate per-invocation provider.
9
7
  */
10
- export declare class DirectExecutionHostCredentials implements HostedConversationModelCredentialProvider {
8
+ export declare class DirectExecutionHostCredentials {
11
9
  #private;
12
10
  constructor(input: {
13
11
  localToken: string;
14
- modelApiKey: string;
15
12
  });
16
13
  /** Takes configured values out of the process environment before parsing. */
17
14
  static takeFromEnvironment(environment: NodeJS.ProcessEnv, rawNames: DirectExecutionHostCredentialEnvironmentNames): DirectExecutionHostCredentials;
18
15
  localToken(): string;
19
- resolveModelApiKey(): Promise<string>;
20
16
  }
21
17
  //# sourceMappingURL=direct-credentials.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"direct-credentials.d.ts","sourceRoot":"","sources":["../../src/node/direct-credentials.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yCAAyC,EAC1C,MAAM,0BAA0B,CAAC;AASlC,MAAM,MAAM,6CAA6C,GAAG;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,qBAAa,8BACb,YAAW,yCAAyC;;gBAItC,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;IAS9D,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CACxB,WAAW,EAAE,MAAM,CAAC,UAAU,EAC9B,QAAQ,EAAE,6CAA6C,GACtD,8BAA8B;IAmBjC,UAAU,IAAI,MAAM;IAId,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC;CAG5C"}
1
+ {"version":3,"file":"direct-credentials.d.ts","sourceRoot":"","sources":["../../src/node/direct-credentials.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,6CAA6C,GAAG;IAC1D,UAAU,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;GAGG;AACH,qBAAa,8BAA8B;;gBAG7B,KAAK,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE;IAQzC,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CACxB,WAAW,EAAE,MAAM,CAAC,UAAU,EAC9B,QAAQ,EAAE,6CAA6C,GACtD,8BAA8B;IAajC,UAAU,IAAI,MAAM;CAGrB"}
@@ -1,18 +1,15 @@
1
1
  import { z } from 'zod';
2
2
  const LocalTokenSchema = z.string().trim().min(32).max(4_096);
3
- const ModelApiKeySchema = z.string().trim().min(8).max(4_096);
4
3
  const EnvironmentNameSchema = z.string().min(1).max(128).regex(/^[A-Za-z_][A-Za-z0-9_]*$/, 'must be an environment variable name');
5
4
  /**
6
- * Non-enumerable direct-host and model credentials for a local/reviewed
7
- * deployment. Production secret retrieval may construct the same class.
5
+ * Non-enumerable direct-host ingress credential for a local/reviewed
6
+ * deployment. Model credentials use the separate per-invocation provider.
8
7
  */
9
8
  export class DirectExecutionHostCredentials {
10
9
  #localToken;
11
- #modelApiKey;
12
10
  constructor(input) {
13
11
  try {
14
12
  this.#localToken = LocalTokenSchema.parse(input.localToken);
15
- this.#modelApiKey = ModelApiKeySchema.parse(input.modelApiKey);
16
13
  }
17
14
  catch {
18
15
  throw new Error('Direct Execution Host credentials are invalid.');
@@ -22,26 +19,17 @@ export class DirectExecutionHostCredentials {
22
19
  static takeFromEnvironment(environment, rawNames) {
23
20
  const names = z.object({
24
21
  localToken: EnvironmentNameSchema,
25
- modelApiKey: EnvironmentNameSchema,
26
- }).strict().refine((value) => value.localToken !== value.modelApiKey, {
27
- message: 'credential environment names must be distinct',
28
- }).parse(rawNames);
22
+ }).strict().parse(rawNames);
29
23
  const input = {
30
24
  localToken: environment[names.localToken],
31
- modelApiKey: environment[names.modelApiKey],
32
25
  };
33
26
  delete environment[names.localToken];
34
- delete environment[names.modelApiKey];
35
27
  return new DirectExecutionHostCredentials({
36
28
  localToken: input.localToken ?? '',
37
- modelApiKey: input.modelApiKey ?? '',
38
29
  });
39
30
  }
40
31
  localToken() {
41
32
  return this.#localToken;
42
33
  }
43
- async resolveModelApiKey() {
44
- return this.#modelApiKey;
45
- }
46
34
  }
47
35
  //# sourceMappingURL=direct-credentials.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"direct-credentials.js","sourceRoot":"","sources":["../../src/node/direct-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC9D,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC9D,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAC5D,0BAA0B,EAC1B,sCAAsC,CACvC,CAAC;AAOF;;;GAGG;AACH,MAAM,OAAO,8BAA8B;IAEhC,WAAW,CAAS;IACpB,YAAY,CAAS;IAE9B,YAAY,KAAkD;QAC5D,IAAI,CAAC;YACH,IAAI,CAAC,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC5D,IAAI,CAAC,YAAY,GAAG,iBAAiB,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CACxB,WAA8B,EAC9B,QAAuD;QAEvD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;YACrB,UAAU,EAAE,qBAAqB;YACjC,WAAW,EAAE,qBAAqB;SACnC,CAAC,CAAC,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,KAAK,KAAK,CAAC,WAAW,EAAE;YACpE,OAAO,EAAE,+CAA+C;SACzD,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACnB,MAAM,KAAK,GAAG;YACZ,UAAU,EAAE,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC;YACzC,WAAW,EAAE,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC;SAC5C,CAAC;QACF,OAAO,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrC,OAAO,WAAW,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACtC,OAAO,IAAI,8BAA8B,CAAC;YACxC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;YAClC,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;SACrC,CAAC,CAAC;IACL,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,kBAAkB;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;CACF","sourcesContent":["import { z } from 'zod';\nimport type {\n HostedConversationModelCredentialProvider,\n} from '../conversation/index.js';\n\nconst LocalTokenSchema = z.string().trim().min(32).max(4_096);\nconst ModelApiKeySchema = z.string().trim().min(8).max(4_096);\nconst EnvironmentNameSchema = z.string().min(1).max(128).regex(\n /^[A-Za-z_][A-Za-z0-9_]*$/,\n 'must be an environment variable name',\n);\n\nexport type DirectExecutionHostCredentialEnvironmentNames = {\n localToken: string;\n modelApiKey: string;\n};\n\n/**\n * Non-enumerable direct-host and model credentials for a local/reviewed\n * deployment. Production secret retrieval may construct the same class.\n */\nexport class DirectExecutionHostCredentials\nimplements HostedConversationModelCredentialProvider {\n readonly #localToken: string;\n readonly #modelApiKey: string;\n\n constructor(input: { localToken: string; modelApiKey: string }) {\n try {\n this.#localToken = LocalTokenSchema.parse(input.localToken);\n this.#modelApiKey = ModelApiKeySchema.parse(input.modelApiKey);\n } catch {\n throw new Error('Direct Execution Host credentials are invalid.');\n }\n }\n\n /** Takes configured values out of the process environment before parsing. */\n static takeFromEnvironment(\n environment: NodeJS.ProcessEnv,\n rawNames: DirectExecutionHostCredentialEnvironmentNames,\n ): DirectExecutionHostCredentials {\n const names = z.object({\n localToken: EnvironmentNameSchema,\n modelApiKey: EnvironmentNameSchema,\n }).strict().refine((value) => value.localToken !== value.modelApiKey, {\n message: 'credential environment names must be distinct',\n }).parse(rawNames);\n const input = {\n localToken: environment[names.localToken],\n modelApiKey: environment[names.modelApiKey],\n };\n delete environment[names.localToken];\n delete environment[names.modelApiKey];\n return new DirectExecutionHostCredentials({\n localToken: input.localToken ?? '',\n modelApiKey: input.modelApiKey ?? '',\n });\n }\n\n localToken(): string {\n return this.#localToken;\n }\n\n async resolveModelApiKey(): Promise<string> {\n return this.#modelApiKey;\n }\n}\n"]}
1
+ {"version":3,"file":"direct-credentials.js","sourceRoot":"","sources":["../../src/node/direct-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;AAC9D,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,KAAK,CAC5D,0BAA0B,EAC1B,sCAAsC,CACvC,CAAC;AAMF;;;GAGG;AACH,MAAM,OAAO,8BAA8B;IAChC,WAAW,CAAS;IAE7B,YAAY,KAA6B;QACvC,IAAI,CAAC;YACH,IAAI,CAAC,WAAW,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QAC9D,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,6EAA6E;IAC7E,MAAM,CAAC,mBAAmB,CACxB,WAA8B,EAC9B,QAAuD;QAEvD,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM,CAAC;YACrB,UAAU,EAAE,qBAAqB;SAClC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAC5B,MAAM,KAAK,GAAG;YACZ,UAAU,EAAE,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC;SAC1C,CAAC;QACF,OAAO,WAAW,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACrC,OAAO,IAAI,8BAA8B,CAAC;YACxC,UAAU,EAAE,KAAK,CAAC,UAAU,IAAI,EAAE;SACnC,CAAC,CAAC;IACL,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAC,WAAW,CAAC;IAC1B,CAAC;CACF","sourcesContent":["import { z } from 'zod';\n\nconst LocalTokenSchema = z.string().trim().min(32).max(4_096);\nconst EnvironmentNameSchema = z.string().min(1).max(128).regex(\n /^[A-Za-z_][A-Za-z0-9_]*$/,\n 'must be an environment variable name',\n);\n\nexport type DirectExecutionHostCredentialEnvironmentNames = {\n localToken: string;\n};\n\n/**\n * Non-enumerable direct-host ingress credential for a local/reviewed\n * deployment. Model credentials use the separate per-invocation provider.\n */\nexport class DirectExecutionHostCredentials {\n readonly #localToken: string;\n\n constructor(input: { localToken: string }) {\n try {\n this.#localToken = LocalTokenSchema.parse(input.localToken);\n } catch {\n throw new Error('Direct Execution Host credentials are invalid.');\n }\n }\n\n /** Takes configured values out of the process environment before parsing. */\n static takeFromEnvironment(\n environment: NodeJS.ProcessEnv,\n rawNames: DirectExecutionHostCredentialEnvironmentNames,\n ): DirectExecutionHostCredentials {\n const names = z.object({\n localToken: EnvironmentNameSchema,\n }).strict().parse(rawNames);\n const input = {\n localToken: environment[names.localToken],\n };\n delete environment[names.localToken];\n return new DirectExecutionHostCredentials({\n localToken: input.localToken ?? '',\n });\n }\n\n localToken(): string {\n return this.#localToken;\n }\n}\n"]}
@@ -0,0 +1,17 @@
1
+ export type EnvironmentFileValueOptions = {
2
+ maxFileBytes?: number;
3
+ };
4
+ /**
5
+ * Takes one deployment value from either `NAME` or the conventional
6
+ * `NAME_FILE` indirection. Both source variables are removed before parsing.
7
+ */
8
+ export declare function takeEnvironmentValueOrFile(environment: NodeJS.ProcessEnv, name: string, options?: EnvironmentFileValueOptions): string | undefined;
9
+ /**
10
+ * Atomically takes a set of deployment values from `NAME` / `NAME_FILE`
11
+ * sources. Every source is removed even when one value is invalid.
12
+ */
13
+ export declare function takeEnvironmentValuesOrFiles(environment: NodeJS.ProcessEnv, rawNames: readonly string[], options?: EnvironmentFileValueOptions): Readonly<Record<string, string | undefined>>;
14
+ export declare class EnvironmentFileValueError extends Error {
15
+ readonly name = "EnvironmentFileValueError";
16
+ }
17
+ //# sourceMappingURL=environment-value.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment-value.d.ts","sourceRoot":"","sources":["../../src/node/environment-value.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,2BAA2B,GAAG;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAQF;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,CAAC,UAAU,EAC9B,IAAI,EAAE,MAAM,EACZ,OAAO,GAAE,2BAAgC,GACxC,MAAM,GAAG,SAAS,CAEpB;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,WAAW,EAAE,MAAM,CAAC,UAAU,EAC9B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,OAAO,GAAE,2BAAgC,GACxC,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAoB9C;AAqDD,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,+BAA+B;CAC7C"}
@@ -0,0 +1,78 @@
1
+ import { closeSync, fstatSync, openSync, readFileSync, } from 'node:fs';
2
+ import { z } from 'zod';
3
+ import { removeOneTrailingLineEnding } from './owner-only-file.js';
4
+ const DEFAULT_MAX_FILE_BYTES = 16 * 1_024;
5
+ const EnvironmentNameSchema = z.string().min(1).max(128).regex(/^[A-Za-z_][A-Za-z0-9_]*$/, 'must be an environment variable name');
6
+ const EnvironmentNamesSchema = z.array(EnvironmentNameSchema).min(1)
7
+ .refine((names) => new Set(names).size === names.length, 'must contain unique environment variable names');
8
+ /**
9
+ * Takes one deployment value from either `NAME` or the conventional
10
+ * `NAME_FILE` indirection. Both source variables are removed before parsing.
11
+ */
12
+ export function takeEnvironmentValueOrFile(environment, name, options = {}) {
13
+ return takeEnvironmentValuesOrFiles(environment, [name], options)[name];
14
+ }
15
+ /**
16
+ * Atomically takes a set of deployment values from `NAME` / `NAME_FILE`
17
+ * sources. Every source is removed even when one value is invalid.
18
+ */
19
+ export function takeEnvironmentValuesOrFiles(environment, rawNames, options = {}) {
20
+ const names = EnvironmentNamesSchema.parse(rawNames);
21
+ const maxFileBytes = z.number().int().min(1).max(1024 * 1_024)
22
+ .default(DEFAULT_MAX_FILE_BYTES)
23
+ .parse(options.maxFileBytes);
24
+ const sources = names.map((name) => ({
25
+ name,
26
+ directValue: environment[name],
27
+ filePath: environment[`${name}_FILE`],
28
+ }));
29
+ sources.forEach(({ name }) => {
30
+ delete environment[name];
31
+ delete environment[`${name}_FILE`];
32
+ });
33
+ return Object.freeze(Object.fromEntries(sources.map((source) => [
34
+ source.name,
35
+ readEnvironmentValueSource(source, maxFileBytes),
36
+ ])));
37
+ }
38
+ function readEnvironmentValueSource(source, maxFileBytes) {
39
+ if (source.directValue !== undefined && source.filePath !== undefined) {
40
+ throw new EnvironmentFileValueError(`${source.name} and ${source.name}_FILE are mutually exclusive.`);
41
+ }
42
+ if (source.directValue !== undefined) {
43
+ return source.directValue;
44
+ }
45
+ if (source.filePath === undefined) {
46
+ return undefined;
47
+ }
48
+ const filePath = source.filePath.trim();
49
+ if (!filePath) {
50
+ throw new EnvironmentFileValueError(`${source.name}_FILE must name a readable regular file.`);
51
+ }
52
+ let descriptor;
53
+ try {
54
+ descriptor = openSync(filePath, 'r');
55
+ const metadata = fstatSync(descriptor);
56
+ if (!metadata.isFile()
57
+ || metadata.size <= 0
58
+ || metadata.size > maxFileBytes) {
59
+ throw new EnvironmentFileValueError(`${source.name}_FILE must name a non-empty regular file no larger than ${maxFileBytes} bytes.`);
60
+ }
61
+ return removeOneTrailingLineEnding(readFileSync(descriptor, 'utf8'));
62
+ }
63
+ catch (error) {
64
+ if (error instanceof EnvironmentFileValueError) {
65
+ throw error;
66
+ }
67
+ throw new EnvironmentFileValueError(`${source.name}_FILE could not be read.`);
68
+ }
69
+ finally {
70
+ if (descriptor !== undefined) {
71
+ closeSync(descriptor);
72
+ }
73
+ }
74
+ }
75
+ export class EnvironmentFileValueError extends Error {
76
+ name = 'EnvironmentFileValueError';
77
+ }
78
+ //# sourceMappingURL=environment-value.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment-value.js","sourceRoot":"","sources":["../../src/node/environment-value.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n closeSync,\n fstatSync,\n openSync,\n readFileSync,\n} from 'node:fs';\nimport { z } from 'zod';\nimport { removeOneTrailingLineEnding } from './owner-only-file.js';\n\nconst DEFAULT_MAX_FILE_BYTES = 16 * 1_024;\nconst EnvironmentNameSchema = z.string().min(1).max(128).regex(\n /^[A-Za-z_][A-Za-z0-9_]*$/,\n 'must be an environment variable name',\n);\nconst EnvironmentNamesSchema = z.array(EnvironmentNameSchema).min(1)\n .refine(\n (names) => new Set(names).size === names.length,\n 'must contain unique environment variable names',\n );\n\nexport type EnvironmentFileValueOptions = {\n maxFileBytes?: number;\n};\n\ntype EnvironmentValueSource = {\n name: string;\n directValue: string | undefined;\n filePath: string | undefined;\n};\n\n/**\n * Takes one deployment value from either `NAME` or the conventional\n * `NAME_FILE` indirection. Both source variables are removed before parsing.\n */\nexport function takeEnvironmentValueOrFile(\n environment: NodeJS.ProcessEnv,\n name: string,\n options: EnvironmentFileValueOptions = {},\n): string | undefined {\n return takeEnvironmentValuesOrFiles(environment, [name], options)[name];\n}\n\n/**\n * Atomically takes a set of deployment values from `NAME` / `NAME_FILE`\n * sources. Every source is removed even when one value is invalid.\n */\nexport function takeEnvironmentValuesOrFiles(\n environment: NodeJS.ProcessEnv,\n rawNames: readonly string[],\n options: EnvironmentFileValueOptions = {},\n): Readonly<Record<string, string | undefined>> {\n const names = EnvironmentNamesSchema.parse(rawNames);\n const maxFileBytes = z.number().int().min(1).max(1024 * 1_024)\n .default(DEFAULT_MAX_FILE_BYTES)\n .parse(options.maxFileBytes);\n const sources = names.map((name): EnvironmentValueSource => ({\n name,\n directValue: environment[name],\n filePath: environment[`${name}_FILE`],\n }));\n\n sources.forEach(({ name }) => {\n delete environment[name];\n delete environment[`${name}_FILE`];\n });\n\n return Object.freeze(Object.fromEntries(sources.map((source) => [\n source.name,\n readEnvironmentValueSource(source, maxFileBytes),\n ])));\n}\n\nfunction readEnvironmentValueSource(\n source: EnvironmentValueSource,\n maxFileBytes: number,\n): string | undefined {\n if (source.directValue !== undefined && source.filePath !== undefined) {\n throw new EnvironmentFileValueError(\n `${source.name} and ${source.name}_FILE are mutually exclusive.`,\n );\n }\n if (source.directValue !== undefined) {\n return source.directValue;\n }\n if (source.filePath === undefined) {\n return undefined;\n }\n\n const filePath = source.filePath.trim();\n if (!filePath) {\n throw new EnvironmentFileValueError(\n `${source.name}_FILE must name a readable regular file.`,\n );\n }\n\n let descriptor: number | undefined;\n try {\n descriptor = openSync(filePath, 'r');\n const metadata = fstatSync(descriptor);\n if (\n !metadata.isFile()\n || metadata.size <= 0\n || metadata.size > maxFileBytes\n ) {\n throw new EnvironmentFileValueError(\n `${source.name}_FILE must name a non-empty regular file no larger than ${maxFileBytes} bytes.`,\n );\n }\n return removeOneTrailingLineEnding(readFileSync(descriptor, 'utf8'));\n } catch (error) {\n if (error instanceof EnvironmentFileValueError) {\n throw error;\n }\n throw new EnvironmentFileValueError(\n `${source.name}_FILE could not be read.`,\n );\n } finally {\n if (descriptor !== undefined) {\n closeSync(descriptor);\n }\n }\n}\n\nexport class EnvironmentFileValueError extends Error {\n readonly name = 'EnvironmentFileValueError';\n}\n"]}
@@ -1,4 +1,8 @@
1
1
  export { ExecutionAuthorityKeyFileError, generateEphemeralExecutionAuthorityKeyPair, generateExecutionAuthorityKeyFile, loadExecutionAuthorityKeyPairFromFile, } from './authority-key.js';
2
+ export { EnvironmentFileValueError, takeEnvironmentValueOrFile, takeEnvironmentValuesOrFiles, } from './environment-value.js';
3
+ export type { EnvironmentFileValueOptions } from './environment-value.js';
4
+ export { initializeLocalCredentialBundle, LOCAL_CREDENTIAL_BUNDLE_FILES, LocalCredentialBundleError, validateLocalCredentialBundle, } from './local-credential-bundle.js';
5
+ export type { LocalCredentialBundleInitialization, LocalCredentialBundlePaths, } from './local-credential-bundle.js';
2
6
  export { DirectExecutionHostCredentials } from './direct-credentials.js';
3
7
  export type { DirectExecutionHostCredentialEnvironmentNames, } from './direct-credentials.js';
4
8
  export { DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH, DEFAULT_ADOPTER_JWKS_PATH, } from '../adopter/index.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,0CAA0C,EAC1C,iCAAiC,EACjC,qCAAqC,GACtC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,8BAA8B,EAAE,MAAM,yBAAyB,CAAC;AACzE,YAAY,EACV,6CAA6C,GAC9C,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,EACvC,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,+BAA+B,GAChC,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,uCAAuC,EACvC,iCAAiC,EACjC,qCAAqC,EACrC,uCAAuC,EACvC,2BAA2B,EAC3B,+BAA+B,EAC/B,6BAA6B,EAC7B,qCAAqC,EACrC,+BAA+B,GAChC,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,0CAA0C,EAC1C,iCAAiC,EACjC,qCAAqC,GACtC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EACL,+BAA+B,EAC/B,6BAA6B,EAC7B,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,mCAAmC,EACnC,0BAA0B,GAC3B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,8BAA8B,EAAE,MAAM,yBAAyB,CAAC;AACzE,YAAY,EACV,6CAA6C,GAC9C,MAAM,yBAAyB,CAAC;AACjC,OAAO,EACL,uCAAuC,EACvC,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,+BAA+B,GAChC,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,uCAAuC,EACvC,iCAAiC,EACjC,qCAAqC,EACrC,uCAAuC,EACvC,2BAA2B,EAC3B,+BAA+B,EAC/B,6BAA6B,EAC7B,qCAAqC,EACrC,+BAA+B,GAChC,MAAM,YAAY,CAAC"}
@@ -1,4 +1,6 @@
1
1
  export { ExecutionAuthorityKeyFileError, generateEphemeralExecutionAuthorityKeyPair, generateExecutionAuthorityKeyFile, loadExecutionAuthorityKeyPairFromFile, } from './authority-key.js';
2
+ export { EnvironmentFileValueError, takeEnvironmentValueOrFile, takeEnvironmentValuesOrFiles, } from './environment-value.js';
3
+ export { initializeLocalCredentialBundle, LOCAL_CREDENTIAL_BUNDLE_FILES, LocalCredentialBundleError, validateLocalCredentialBundle, } from './local-credential-bundle.js';
2
4
  export { DirectExecutionHostCredentials } from './direct-credentials.js';
3
5
  export { DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH, DEFAULT_ADOPTER_JWKS_PATH, } from '../adopter/index.js';
4
6
  export { NodeExecutionAdopterHttpService, } from './http-service.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,0CAA0C,EAC1C,iCAAiC,EACjC,qCAAqC,GACtC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,8BAA8B,EAAE,MAAM,yBAAyB,CAAC;AAIzE,OAAO,EACL,uCAAuC,EACvC,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,+BAA+B,GAChC,MAAM,mBAAmB,CAAC","sourcesContent":["export {\n ExecutionAuthorityKeyFileError,\n generateEphemeralExecutionAuthorityKeyPair,\n generateExecutionAuthorityKeyFile,\n loadExecutionAuthorityKeyPairFromFile,\n} from './authority-key.js';\nexport { DirectExecutionHostCredentials } from './direct-credentials.js';\nexport type {\n DirectExecutionHostCredentialEnvironmentNames,\n} from './direct-credentials.js';\nexport {\n DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH,\n DEFAULT_ADOPTER_JWKS_PATH,\n} from '../adopter/index.js';\nexport {\n NodeExecutionAdopterHttpService,\n} from './http-service.js';\nexport type {\n NodeExecutionAdopterAuthenticationInput,\n NodeExecutionAdopterAuthenticator,\n NodeExecutionAdopterConversationInput,\n NodeExecutionAdopterConversationService,\n NodeExecutionAdopterFailure,\n NodeExecutionAdopterHttpHandler,\n NodeExecutionAdopterHttpPaths,\n NodeExecutionAdopterHttpServiceConfig,\n NodeExecutionAdopterPublicError,\n} from './types.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,0CAA0C,EAC1C,iCAAiC,EACjC,qCAAqC,GACtC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,yBAAyB,EACzB,0BAA0B,EAC1B,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,+BAA+B,EAC/B,6BAA6B,EAC7B,0BAA0B,EAC1B,6BAA6B,GAC9B,MAAM,8BAA8B,CAAC;AAKtC,OAAO,EAAE,8BAA8B,EAAE,MAAM,yBAAyB,CAAC;AAIzE,OAAO,EACL,uCAAuC,EACvC,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,+BAA+B,GAChC,MAAM,mBAAmB,CAAC","sourcesContent":["export {\n ExecutionAuthorityKeyFileError,\n generateEphemeralExecutionAuthorityKeyPair,\n generateExecutionAuthorityKeyFile,\n loadExecutionAuthorityKeyPairFromFile,\n} from './authority-key.js';\nexport {\n EnvironmentFileValueError,\n takeEnvironmentValueOrFile,\n takeEnvironmentValuesOrFiles,\n} from './environment-value.js';\nexport type { EnvironmentFileValueOptions } from './environment-value.js';\nexport {\n initializeLocalCredentialBundle,\n LOCAL_CREDENTIAL_BUNDLE_FILES,\n LocalCredentialBundleError,\n validateLocalCredentialBundle,\n} from './local-credential-bundle.js';\nexport type {\n LocalCredentialBundleInitialization,\n LocalCredentialBundlePaths,\n} from './local-credential-bundle.js';\nexport { DirectExecutionHostCredentials } from './direct-credentials.js';\nexport type {\n DirectExecutionHostCredentialEnvironmentNames,\n} from './direct-credentials.js';\nexport {\n DEFAULT_ADOPTER_CONVERSATION_TURNS_PATH,\n DEFAULT_ADOPTER_JWKS_PATH,\n} from '../adopter/index.js';\nexport {\n NodeExecutionAdopterHttpService,\n} from './http-service.js';\nexport type {\n NodeExecutionAdopterAuthenticationInput,\n NodeExecutionAdopterAuthenticator,\n NodeExecutionAdopterConversationInput,\n NodeExecutionAdopterConversationService,\n NodeExecutionAdopterFailure,\n NodeExecutionAdopterHttpHandler,\n NodeExecutionAdopterHttpPaths,\n NodeExecutionAdopterHttpServiceConfig,\n NodeExecutionAdopterPublicError,\n} from './types.js';\n"]}
@@ -0,0 +1,33 @@
1
+ export declare const LOCAL_CREDENTIAL_BUNDLE_FILES: Readonly<{
2
+ readonly executionHostLocalToken: "execution-host-local-token";
3
+ readonly executionHostLocalTokenSha256: "execution-host-local-token.sha256";
4
+ readonly coordinatorApiToken: "coordinator-api-token";
5
+ readonly coordinatorProductExecutionToken: "coordinator-product-execution-token";
6
+ readonly executionAuthorityPrivateJwk: "execution-authority-private.jwk";
7
+ readonly manifest: "manifest.json";
8
+ }>;
9
+ export type LocalCredentialBundlePaths = {
10
+ directory: string;
11
+ manifest: string;
12
+ executionHostLocalToken: string;
13
+ executionHostLocalTokenSha256: string;
14
+ coordinatorApiToken: string;
15
+ coordinatorProductExecutionToken: string;
16
+ executionAuthorityPrivateJwk: string;
17
+ };
18
+ export type LocalCredentialBundleInitialization = {
19
+ created: boolean;
20
+ paths: LocalCredentialBundlePaths;
21
+ };
22
+ /**
23
+ * Creates one versioned, owner-only credential bundle for local hosted-service
24
+ * composition. A valid existing bundle is returned unchanged; partial or
25
+ * unmanaged directories fail closed.
26
+ */
27
+ export declare function initializeLocalCredentialBundle(rawOutputDirectory: string): Promise<LocalCredentialBundleInitialization>;
28
+ /** Validates one generated bundle without returning any credential value. */
29
+ export declare function validateLocalCredentialBundle(rawDirectory: string): Promise<LocalCredentialBundlePaths>;
30
+ export declare class LocalCredentialBundleError extends Error {
31
+ readonly name = "LocalCredentialBundleError";
32
+ }
33
+ //# sourceMappingURL=local-credential-bundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-credential-bundle.d.ts","sourceRoot":"","sources":["../../src/node/local-credential-bundle.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,6BAA6B;;;;;;;EAO/B,CAAC;AA0BZ,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,uBAAuB,EAAE,MAAM,CAAC;IAChC,6BAA6B,EAAE,MAAM,CAAC;IACtC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gCAAgC,EAAE,MAAM,CAAC;IACzC,4BAA4B,EAAE,MAAM,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,mCAAmC,GAAG;IAChD,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,0BAA0B,CAAC;CACnC,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,+BAA+B,CACnD,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAAC,mCAAmC,CAAC,CA2C9C;AAED,6EAA6E;AAC7E,wBAAsB,6BAA6B,CACjD,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,0BAA0B,CAAC,CA4CrC;AAiID,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,gCAAgC;CAC9C"}
@@ -0,0 +1,196 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { chmod, lstat, mkdir, mkdtemp, rename, rm, } from 'node:fs/promises';
3
+ import { basename, dirname, join, resolve } from 'node:path';
4
+ import { z } from 'zod';
5
+ import { generateExecutionAuthorityKeyFile, loadExecutionAuthorityKeyPairFromFile, } from './authority-key.js';
6
+ import { createOwnerOnlyFile, OwnerOnlyFileError, readOwnerOnlyFile, removeOneTrailingLineEnding, } from './owner-only-file.js';
7
+ const BUNDLE_KIND = 'heddle-execution-host-local-credentials';
8
+ const BUNDLE_SCHEMA_VERSION = 2;
9
+ const MAX_BUNDLE_FILE_BYTES = 16 * 1_024;
10
+ const OWNER_DIRECTORY_MODE = 0o700;
11
+ const TokenSchema = z.string().regex(/^[a-f0-9]{64}$/);
12
+ export const LOCAL_CREDENTIAL_BUNDLE_FILES = Object.freeze({
13
+ executionHostLocalToken: 'execution-host-local-token',
14
+ executionHostLocalTokenSha256: 'execution-host-local-token.sha256',
15
+ coordinatorApiToken: 'coordinator-api-token',
16
+ coordinatorProductExecutionToken: 'coordinator-product-execution-token',
17
+ executionAuthorityPrivateJwk: 'execution-authority-private.jwk',
18
+ manifest: 'manifest.json',
19
+ });
20
+ const SecretBundleFilesSchema = z.object({
21
+ executionHostLocalToken: z.literal(LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalToken),
22
+ executionHostLocalTokenSha256: z.literal(LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalTokenSha256),
23
+ coordinatorApiToken: z.literal(LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorApiToken),
24
+ coordinatorProductExecutionToken: z.literal(LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorProductExecutionToken),
25
+ executionAuthorityPrivateJwk: z.literal(LOCAL_CREDENTIAL_BUNDLE_FILES.executionAuthorityPrivateJwk),
26
+ }).strict();
27
+ const LocalCredentialBundleManifestSchema = z.object({
28
+ kind: z.literal(BUNDLE_KIND),
29
+ schemaVersion: z.literal(BUNDLE_SCHEMA_VERSION),
30
+ files: SecretBundleFilesSchema,
31
+ }).strict();
32
+ /**
33
+ * Creates one versioned, owner-only credential bundle for local hosted-service
34
+ * composition. A valid existing bundle is returned unchanged; partial or
35
+ * unmanaged directories fail closed.
36
+ */
37
+ export async function initializeLocalCredentialBundle(rawOutputDirectory) {
38
+ const outputDirectory = resolveOutputDirectory(rawOutputDirectory);
39
+ const existing = await pathExists(outputDirectory);
40
+ if (existing) {
41
+ return {
42
+ created: false,
43
+ paths: await validateLocalCredentialBundle(outputDirectory),
44
+ };
45
+ }
46
+ const parentDirectory = dirname(outputDirectory);
47
+ await mkdir(parentDirectory, { recursive: true, mode: OWNER_DIRECTORY_MODE });
48
+ const temporaryDirectory = await mkdtemp(join(parentDirectory, `.${basename(outputDirectory)}.tmp-`));
49
+ await chmod(temporaryDirectory, OWNER_DIRECTORY_MODE);
50
+ try {
51
+ await populateCredentialBundle(temporaryDirectory);
52
+ let created = true;
53
+ try {
54
+ await rename(temporaryDirectory, outputDirectory);
55
+ }
56
+ catch (error) {
57
+ if (!(await pathExists(outputDirectory))) {
58
+ throw error;
59
+ }
60
+ await rm(temporaryDirectory, { recursive: true, force: true });
61
+ created = false;
62
+ }
63
+ return {
64
+ created,
65
+ paths: await validateLocalCredentialBundle(outputDirectory),
66
+ };
67
+ }
68
+ catch (error) {
69
+ await rm(temporaryDirectory, { recursive: true, force: true });
70
+ if (error instanceof LocalCredentialBundleError) {
71
+ throw error;
72
+ }
73
+ throw new LocalCredentialBundleError('Local Execution Host credentials could not be initialized.');
74
+ }
75
+ }
76
+ /** Validates one generated bundle without returning any credential value. */
77
+ export async function validateLocalCredentialBundle(rawDirectory) {
78
+ const directory = resolveOutputDirectory(rawDirectory);
79
+ try {
80
+ await assertOwnerOnlyDirectory(directory);
81
+ const paths = resolveBundlePaths(directory);
82
+ LocalCredentialBundleManifestSchema.parse(JSON.parse(await readBundleEntry(paths.manifest)));
83
+ const [localToken, localTokenDigest, coordinatorToken, productExecutionToken,] = await Promise.all([
84
+ readBundleEntry(paths.executionHostLocalToken),
85
+ readBundleEntry(paths.executionHostLocalTokenSha256),
86
+ readBundleEntry(paths.coordinatorApiToken),
87
+ readBundleEntry(paths.coordinatorProductExecutionToken),
88
+ ]);
89
+ TokenSchema.parse(localToken);
90
+ TokenSchema.parse(localTokenDigest);
91
+ TokenSchema.parse(coordinatorToken);
92
+ TokenSchema.parse(productExecutionToken);
93
+ if (sha256(localToken) !== localTokenDigest) {
94
+ throw new LocalCredentialBundleError('Local credential bundle token verifier does not match its token.');
95
+ }
96
+ await loadExecutionAuthorityKeyPairFromFile(paths.executionAuthorityPrivateJwk);
97
+ return Object.freeze(paths);
98
+ }
99
+ catch (error) {
100
+ if (error instanceof LocalCredentialBundleError) {
101
+ throw error;
102
+ }
103
+ if (error instanceof OwnerOnlyFileError) {
104
+ throw new LocalCredentialBundleError(error.message);
105
+ }
106
+ throw new LocalCredentialBundleError('Existing local credential directory is incomplete, invalid, or unmanaged.');
107
+ }
108
+ }
109
+ async function populateCredentialBundle(directory) {
110
+ const paths = resolveBundlePaths(directory);
111
+ const localToken = generateToken();
112
+ await Promise.all([
113
+ writeBundleEntry(paths.executionHostLocalToken, localToken),
114
+ writeBundleEntry(paths.executionHostLocalTokenSha256, sha256(localToken)),
115
+ writeBundleEntry(paths.coordinatorApiToken, generateToken()),
116
+ writeBundleEntry(paths.coordinatorProductExecutionToken, generateToken()),
117
+ generateExecutionAuthorityKeyFile(paths.executionAuthorityPrivateJwk),
118
+ ]);
119
+ await writeBundleEntry(paths.manifest, JSON.stringify({
120
+ kind: BUNDLE_KIND,
121
+ schemaVersion: BUNDLE_SCHEMA_VERSION,
122
+ files: secretFileManifest(),
123
+ }, null, 2));
124
+ }
125
+ function secretFileManifest() {
126
+ return {
127
+ executionHostLocalToken: LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalToken,
128
+ executionHostLocalTokenSha256: LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalTokenSha256,
129
+ coordinatorApiToken: LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorApiToken,
130
+ coordinatorProductExecutionToken: LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorProductExecutionToken,
131
+ executionAuthorityPrivateJwk: LOCAL_CREDENTIAL_BUNDLE_FILES.executionAuthorityPrivateJwk,
132
+ };
133
+ }
134
+ function resolveBundlePaths(directory) {
135
+ return {
136
+ directory,
137
+ manifest: join(directory, LOCAL_CREDENTIAL_BUNDLE_FILES.manifest),
138
+ executionHostLocalToken: join(directory, LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalToken),
139
+ executionHostLocalTokenSha256: join(directory, LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalTokenSha256),
140
+ coordinatorApiToken: join(directory, LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorApiToken),
141
+ coordinatorProductExecutionToken: join(directory, LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorProductExecutionToken),
142
+ executionAuthorityPrivateJwk: join(directory, LOCAL_CREDENTIAL_BUNDLE_FILES.executionAuthorityPrivateJwk),
143
+ };
144
+ }
145
+ function resolveOutputDirectory(rawDirectory) {
146
+ const directory = rawDirectory.trim();
147
+ if (!directory) {
148
+ throw new LocalCredentialBundleError('A local credential output directory is required.');
149
+ }
150
+ return resolve(directory);
151
+ }
152
+ async function assertOwnerOnlyDirectory(directory) {
153
+ const metadata = await lstat(directory);
154
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
155
+ throw new LocalCredentialBundleError('Local credential bundle path must be a real directory.');
156
+ }
157
+ if (process.platform !== 'win32' && (metadata.mode & 0o077) !== 0) {
158
+ throw new LocalCredentialBundleError('Local credential bundle directory must not be group- or world-accessible.');
159
+ }
160
+ }
161
+ async function readBundleEntry(filePath) {
162
+ return removeOneTrailingLineEnding(await readOwnerOnlyFile(filePath, {
163
+ description: 'Local credential bundle entry',
164
+ maxBytes: MAX_BUNDLE_FILE_BYTES,
165
+ }));
166
+ }
167
+ function writeBundleEntry(filePath, value) {
168
+ return createOwnerOnlyFile(filePath, `${value}\n`);
169
+ }
170
+ function generateToken() {
171
+ return randomBytes(32).toString('hex');
172
+ }
173
+ function sha256(value) {
174
+ return createHash('sha256').update(value, 'utf8').digest('hex');
175
+ }
176
+ async function pathExists(path) {
177
+ try {
178
+ await lstat(path);
179
+ return true;
180
+ }
181
+ catch (error) {
182
+ if (isErrnoCode(error, 'ENOENT')) {
183
+ return false;
184
+ }
185
+ throw new LocalCredentialBundleError('Local credential bundle path could not be inspected.');
186
+ }
187
+ }
188
+ function isErrnoCode(error, code) {
189
+ return error instanceof Error
190
+ && 'code' in error
191
+ && error.code === code;
192
+ }
193
+ export class LocalCredentialBundleError extends Error {
194
+ name = 'LocalCredentialBundleError';
195
+ }
196
+ //# sourceMappingURL=local-credential-bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-credential-bundle.js","sourceRoot":"","sources":["../../src/node/local-credential-bundle.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport {\n chmod,\n lstat,\n mkdir,\n mkdtemp,\n rename,\n rm,\n} from 'node:fs/promises';\nimport { basename, dirname, join, resolve } from 'node:path';\nimport { z } from 'zod';\nimport {\n generateExecutionAuthorityKeyFile,\n loadExecutionAuthorityKeyPairFromFile,\n} from './authority-key.js';\nimport {\n createOwnerOnlyFile,\n OwnerOnlyFileError,\n readOwnerOnlyFile,\n removeOneTrailingLineEnding,\n} from './owner-only-file.js';\n\nconst BUNDLE_KIND = 'heddle-execution-host-local-credentials';\nconst BUNDLE_SCHEMA_VERSION = 2;\nconst MAX_BUNDLE_FILE_BYTES = 16 * 1_024;\nconst OWNER_DIRECTORY_MODE = 0o700;\nconst TokenSchema = z.string().regex(/^[a-f0-9]{64}$/);\n\nexport const LOCAL_CREDENTIAL_BUNDLE_FILES = Object.freeze({\n executionHostLocalToken: 'execution-host-local-token',\n executionHostLocalTokenSha256: 'execution-host-local-token.sha256',\n coordinatorApiToken: 'coordinator-api-token',\n coordinatorProductExecutionToken: 'coordinator-product-execution-token',\n executionAuthorityPrivateJwk: 'execution-authority-private.jwk',\n manifest: 'manifest.json',\n} as const);\n\nconst SecretBundleFilesSchema = z.object({\n executionHostLocalToken: z.literal(\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalToken,\n ),\n executionHostLocalTokenSha256: z.literal(\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalTokenSha256,\n ),\n coordinatorApiToken: z.literal(\n LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorApiToken,\n ),\n coordinatorProductExecutionToken: z.literal(\n LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorProductExecutionToken,\n ),\n executionAuthorityPrivateJwk: z.literal(\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionAuthorityPrivateJwk,\n ),\n}).strict();\n\nconst LocalCredentialBundleManifestSchema = z.object({\n kind: z.literal(BUNDLE_KIND),\n schemaVersion: z.literal(BUNDLE_SCHEMA_VERSION),\n files: SecretBundleFilesSchema,\n}).strict();\n\nexport type LocalCredentialBundlePaths = {\n directory: string;\n manifest: string;\n executionHostLocalToken: string;\n executionHostLocalTokenSha256: string;\n coordinatorApiToken: string;\n coordinatorProductExecutionToken: string;\n executionAuthorityPrivateJwk: string;\n};\n\nexport type LocalCredentialBundleInitialization = {\n created: boolean;\n paths: LocalCredentialBundlePaths;\n};\n\n/**\n * Creates one versioned, owner-only credential bundle for local hosted-service\n * composition. A valid existing bundle is returned unchanged; partial or\n * unmanaged directories fail closed.\n */\nexport async function initializeLocalCredentialBundle(\n rawOutputDirectory: string,\n): Promise<LocalCredentialBundleInitialization> {\n const outputDirectory = resolveOutputDirectory(rawOutputDirectory);\n const existing = await pathExists(outputDirectory);\n if (existing) {\n return {\n created: false,\n paths: await validateLocalCredentialBundle(outputDirectory),\n };\n }\n\n const parentDirectory = dirname(outputDirectory);\n await mkdir(parentDirectory, { recursive: true, mode: OWNER_DIRECTORY_MODE });\n const temporaryDirectory = await mkdtemp(join(\n parentDirectory,\n `.${basename(outputDirectory)}.tmp-`,\n ));\n await chmod(temporaryDirectory, OWNER_DIRECTORY_MODE);\n\n try {\n await populateCredentialBundle(temporaryDirectory);\n let created = true;\n try {\n await rename(temporaryDirectory, outputDirectory);\n } catch (error) {\n if (!(await pathExists(outputDirectory))) {\n throw error;\n }\n await rm(temporaryDirectory, { recursive: true, force: true });\n created = false;\n }\n return {\n created,\n paths: await validateLocalCredentialBundle(outputDirectory),\n };\n } catch (error) {\n await rm(temporaryDirectory, { recursive: true, force: true });\n if (error instanceof LocalCredentialBundleError) {\n throw error;\n }\n throw new LocalCredentialBundleError(\n 'Local Execution Host credentials could not be initialized.',\n );\n }\n}\n\n/** Validates one generated bundle without returning any credential value. */\nexport async function validateLocalCredentialBundle(\n rawDirectory: string,\n): Promise<LocalCredentialBundlePaths> {\n const directory = resolveOutputDirectory(rawDirectory);\n try {\n await assertOwnerOnlyDirectory(directory);\n const paths = resolveBundlePaths(directory);\n LocalCredentialBundleManifestSchema.parse(\n JSON.parse(await readBundleEntry(paths.manifest)) as unknown,\n );\n\n const [\n localToken,\n localTokenDigest,\n coordinatorToken,\n productExecutionToken,\n ] = await Promise.all([\n readBundleEntry(paths.executionHostLocalToken),\n readBundleEntry(paths.executionHostLocalTokenSha256),\n readBundleEntry(paths.coordinatorApiToken),\n readBundleEntry(paths.coordinatorProductExecutionToken),\n ]);\n TokenSchema.parse(localToken);\n TokenSchema.parse(localTokenDigest);\n TokenSchema.parse(coordinatorToken);\n TokenSchema.parse(productExecutionToken);\n if (sha256(localToken) !== localTokenDigest) {\n throw new LocalCredentialBundleError(\n 'Local credential bundle token verifier does not match its token.',\n );\n }\n await loadExecutionAuthorityKeyPairFromFile(\n paths.executionAuthorityPrivateJwk,\n );\n return Object.freeze(paths);\n } catch (error) {\n if (error instanceof LocalCredentialBundleError) {\n throw error;\n }\n if (error instanceof OwnerOnlyFileError) {\n throw new LocalCredentialBundleError(error.message);\n }\n throw new LocalCredentialBundleError(\n 'Existing local credential directory is incomplete, invalid, or unmanaged.',\n );\n }\n}\n\nasync function populateCredentialBundle(directory: string): Promise<void> {\n const paths = resolveBundlePaths(directory);\n const localToken = generateToken();\n await Promise.all([\n writeBundleEntry(paths.executionHostLocalToken, localToken),\n writeBundleEntry(\n paths.executionHostLocalTokenSha256,\n sha256(localToken),\n ),\n writeBundleEntry(paths.coordinatorApiToken, generateToken()),\n writeBundleEntry(\n paths.coordinatorProductExecutionToken,\n generateToken(),\n ),\n generateExecutionAuthorityKeyFile(paths.executionAuthorityPrivateJwk),\n ]);\n await writeBundleEntry(paths.manifest, JSON.stringify({\n kind: BUNDLE_KIND,\n schemaVersion: BUNDLE_SCHEMA_VERSION,\n files: secretFileManifest(),\n }, null, 2));\n}\n\nfunction secretFileManifest() {\n return {\n executionHostLocalToken:\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalToken,\n executionHostLocalTokenSha256:\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalTokenSha256,\n coordinatorApiToken: LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorApiToken,\n coordinatorProductExecutionToken:\n LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorProductExecutionToken,\n executionAuthorityPrivateJwk:\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionAuthorityPrivateJwk,\n } as const;\n}\n\nfunction resolveBundlePaths(directory: string): LocalCredentialBundlePaths {\n return {\n directory,\n manifest: join(directory, LOCAL_CREDENTIAL_BUNDLE_FILES.manifest),\n executionHostLocalToken: join(\n directory,\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalToken,\n ),\n executionHostLocalTokenSha256: join(\n directory,\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionHostLocalTokenSha256,\n ),\n coordinatorApiToken: join(\n directory,\n LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorApiToken,\n ),\n coordinatorProductExecutionToken: join(\n directory,\n LOCAL_CREDENTIAL_BUNDLE_FILES.coordinatorProductExecutionToken,\n ),\n executionAuthorityPrivateJwk: join(\n directory,\n LOCAL_CREDENTIAL_BUNDLE_FILES.executionAuthorityPrivateJwk,\n ),\n };\n}\n\nfunction resolveOutputDirectory(rawDirectory: string): string {\n const directory = rawDirectory.trim();\n if (!directory) {\n throw new LocalCredentialBundleError(\n 'A local credential output directory is required.',\n );\n }\n return resolve(directory);\n}\n\nasync function assertOwnerOnlyDirectory(directory: string): Promise<void> {\n const metadata = await lstat(directory);\n if (!metadata.isDirectory() || metadata.isSymbolicLink()) {\n throw new LocalCredentialBundleError(\n 'Local credential bundle path must be a real directory.',\n );\n }\n if (process.platform !== 'win32' && (metadata.mode & 0o077) !== 0) {\n throw new LocalCredentialBundleError(\n 'Local credential bundle directory must not be group- or world-accessible.',\n );\n }\n}\n\nasync function readBundleEntry(filePath: string): Promise<string> {\n return removeOneTrailingLineEnding(await readOwnerOnlyFile(filePath, {\n description: 'Local credential bundle entry',\n maxBytes: MAX_BUNDLE_FILE_BYTES,\n }));\n}\n\nfunction writeBundleEntry(filePath: string, value: string): Promise<void> {\n return createOwnerOnlyFile(filePath, `${value}\\n`);\n}\n\nfunction generateToken(): string {\n return randomBytes(32).toString('hex');\n}\n\nfunction sha256(value: string): string {\n return createHash('sha256').update(value, 'utf8').digest('hex');\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await lstat(path);\n return true;\n } catch (error) {\n if (isErrnoCode(error, 'ENOENT')) {\n return false;\n }\n throw new LocalCredentialBundleError(\n 'Local credential bundle path could not be inspected.',\n );\n }\n}\n\nfunction isErrnoCode(error: unknown, code: string): boolean {\n return error instanceof Error\n && 'code' in error\n && error.code === code;\n}\n\nexport class LocalCredentialBundleError extends Error {\n readonly name = 'LocalCredentialBundleError';\n}\n"]}
@@ -0,0 +1,17 @@
1
+ type OwnerOnlyFileReadOptions = {
2
+ description: string;
3
+ maxBytes: number;
4
+ };
5
+ /**
6
+ * Reads a bounded regular file without following its final symlink and rejects
7
+ * group- or world-accessible POSIX files.
8
+ */
9
+ export declare function readOwnerOnlyFile(filePath: string, options: OwnerOnlyFileReadOptions): Promise<string>;
10
+ /** Creates, flushes, and never overwrites one owner-only file. */
11
+ export declare function createOwnerOnlyFile(filePath: string, value: string): Promise<void>;
12
+ export declare function removeOneTrailingLineEnding(value: string): string;
13
+ export declare class OwnerOnlyFileError extends Error {
14
+ readonly name = "OwnerOnlyFileError";
15
+ }
16
+ export {};
17
+ //# sourceMappingURL=owner-only-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-only-file.d.ts","sourceRoot":"","sources":["../../src/node/owner-only-file.ts"],"names":[],"mappings":"AASA,KAAK,wBAAwB,GAAG;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED,kEAAkE;AAClE,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMjE;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,wBAAwB;CACtC"}
@@ -0,0 +1,59 @@
1
+ import { constants } from 'node:fs';
2
+ import { open, unlink, } from 'node:fs/promises';
3
+ const OWNER_FILE_MODE = 0o600;
4
+ /**
5
+ * Reads a bounded regular file without following its final symlink and rejects
6
+ * group- or world-accessible POSIX files.
7
+ */
8
+ export async function readOwnerOnlyFile(filePath, options) {
9
+ let file;
10
+ try {
11
+ file = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);
12
+ const metadata = await file.stat();
13
+ if (!metadata.isFile()
14
+ || metadata.size <= 0
15
+ || metadata.size > options.maxBytes) {
16
+ throw new OwnerOnlyFileError(`${options.description} must be a non-empty regular file no larger than ${options.maxBytes} bytes.`);
17
+ }
18
+ if (process.platform !== 'win32' && (metadata.mode & 0o077) !== 0) {
19
+ throw new OwnerOnlyFileError(`${options.description} must not be group- or world-accessible.`);
20
+ }
21
+ return await file.readFile('utf8');
22
+ }
23
+ finally {
24
+ await file?.close().catch(() => undefined);
25
+ }
26
+ }
27
+ /** Creates, flushes, and never overwrites one owner-only file. */
28
+ export async function createOwnerOnlyFile(filePath, value) {
29
+ let file;
30
+ let created = false;
31
+ try {
32
+ file = await open(filePath, 'wx', OWNER_FILE_MODE);
33
+ created = true;
34
+ await file.writeFile(value, 'utf8');
35
+ await file.sync();
36
+ }
37
+ catch (error) {
38
+ await file?.close().catch(() => undefined);
39
+ file = undefined;
40
+ if (created) {
41
+ await unlink(filePath).catch(() => undefined);
42
+ }
43
+ throw error;
44
+ }
45
+ finally {
46
+ await file?.close().catch(() => undefined);
47
+ }
48
+ }
49
+ export function removeOneTrailingLineEnding(value) {
50
+ return value.endsWith('\r\n')
51
+ ? value.slice(0, -2)
52
+ : value.endsWith('\n')
53
+ ? value.slice(0, -1)
54
+ : value;
55
+ }
56
+ export class OwnerOnlyFileError extends Error {
57
+ name = 'OwnerOnlyFileError';
58
+ }
59
+ //# sourceMappingURL=owner-only-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"owner-only-file.js","sourceRoot":"","sources":["../../src/node/owner-only-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EACL,IAAI,EACJ,MAAM,GAEP,MAAM,kBAAkB,CAAC;AAE1B,MAAM,eAAe,GAAG,KAAK,CAAC;AAO9B;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,OAAiC;IAEjC,IAAI,IAA4B,CAAC;IACjC,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,QAAQ,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC;QACvE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACnC,IACE,CAAC,QAAQ,CAAC,MAAM,EAAE;eACf,QAAQ,CAAC,IAAI,IAAI,CAAC;eAClB,QAAQ,CAAC,IAAI,GAAG,OAAO,CAAC,QAAQ,EACnC,CAAC;YACD,MAAM,IAAI,kBAAkB,CAC1B,GAAG,OAAO,CAAC,WAAW,oDAAoD,OAAO,CAAC,QAAQ,SAAS,CACpG,CAAC;QACJ,CAAC;QACD,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;YAClE,MAAM,IAAI,kBAAkB,CAC1B,GAAG,OAAO,CAAC,WAAW,0CAA0C,CACjE,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,KAAa;IAEb,IAAI,IAA4B,CAAC;IACjC,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC;QACnD,OAAO,GAAG,IAAI,CAAC;QACf,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QACpC,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;IACpB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC3C,IAAI,GAAG,SAAS,CAAC;QACjB,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChD,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,KAAa;IACvD,OAAO,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3B,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpB,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;YACpB,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACpB,CAAC,CAAC,KAAK,CAAC;AACd,CAAC;AAED,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,GAAG,oBAAoB,CAAC;CACtC","sourcesContent":["import { constants } from 'node:fs';\nimport {\n open,\n unlink,\n type FileHandle,\n} from 'node:fs/promises';\n\nconst OWNER_FILE_MODE = 0o600;\n\ntype OwnerOnlyFileReadOptions = {\n description: string;\n maxBytes: number;\n};\n\n/**\n * Reads a bounded regular file without following its final symlink and rejects\n * group- or world-accessible POSIX files.\n */\nexport async function readOwnerOnlyFile(\n filePath: string,\n options: OwnerOnlyFileReadOptions,\n): Promise<string> {\n let file: FileHandle | undefined;\n try {\n file = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW);\n const metadata = await file.stat();\n if (\n !metadata.isFile()\n || metadata.size <= 0\n || metadata.size > options.maxBytes\n ) {\n throw new OwnerOnlyFileError(\n `${options.description} must be a non-empty regular file no larger than ${options.maxBytes} bytes.`,\n );\n }\n if (process.platform !== 'win32' && (metadata.mode & 0o077) !== 0) {\n throw new OwnerOnlyFileError(\n `${options.description} must not be group- or world-accessible.`,\n );\n }\n return await file.readFile('utf8');\n } finally {\n await file?.close().catch(() => undefined);\n }\n}\n\n/** Creates, flushes, and never overwrites one owner-only file. */\nexport async function createOwnerOnlyFile(\n filePath: string,\n value: string,\n): Promise<void> {\n let file: FileHandle | undefined;\n let created = false;\n try {\n file = await open(filePath, 'wx', OWNER_FILE_MODE);\n created = true;\n await file.writeFile(value, 'utf8');\n await file.sync();\n } catch (error) {\n await file?.close().catch(() => undefined);\n file = undefined;\n if (created) {\n await unlink(filePath).catch(() => undefined);\n }\n throw error;\n } finally {\n await file?.close().catch(() => undefined);\n }\n}\n\nexport function removeOneTrailingLineEnding(value: string): string {\n return value.endsWith('\\r\\n')\n ? value.slice(0, -2)\n : value.endsWith('\\n')\n ? value.slice(0, -1)\n : value;\n}\n\nexport class OwnerOnlyFileError extends Error {\n readonly name = 'OwnerOnlyFileError';\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/testing/request.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EAQL,KAAK,oCAAoC,EAC1C,MAAM,uBAAuB,CAAC;AAQ/B,MAAM,MAAM,+BAA+B,GAAG;IAC5C,OAAO,EAAE,oCAAoC,CAAC;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,eAAe,EACxB,kBAAkB,EAAE,MAAM,EAC1B,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,+BAA+B,CAAC,CAyC1C;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,OAAO,GACb,IAAI,CAkBN"}
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/testing/request.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EACf,MAAM,WAAW,CAAC;AAEnB,OAAO,EASL,KAAK,oCAAoC,EAC1C,MAAM,uBAAuB,CAAC;AAO/B,MAAM,MAAM,+BAA+B,GAAG;IAC5C,OAAO,EAAE,oCAAoC,CAAC;IAC9C,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAsB,8BAA8B,CAClD,OAAO,EAAE,eAAe,EACxB,kBAAkB,EAAE,MAAM,EAC1B,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,+BAA+B,CAAC,CAyC1C;AAED,wBAAgB,gCAAgC,CAC9C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,OAAO,GACb,IAAI,CAkBN"}