@voxgig/sdkgen 4.6.0 → 4.7.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 (115) hide show
  1. package/bin/voxgig-sdkgen +1 -1
  2. package/dist/cmp/Feature.js +5 -0
  3. package/dist/cmp/Feature.js.map +1 -1
  4. package/dist/cmp/TestControl.d.ts +4 -0
  5. package/dist/cmp/TestControl.js +51 -0
  6. package/dist/cmp/TestControl.js.map +1 -0
  7. package/dist/helpers/featureSource.d.ts +3 -1
  8. package/dist/helpers/featureSource.js +105 -0
  9. package/dist/helpers/featureSource.js.map +1 -1
  10. package/dist/sdkgen.d.ts +3 -2
  11. package/dist/sdkgen.js +9 -4
  12. package/dist/sdkgen.js.map +1 -1
  13. package/dist/tsconfig.tsbuildinfo +1 -1
  14. package/dist/utility.d.ts +2 -1
  15. package/dist/utility.js +52 -0
  16. package/dist/utility.js.map +1 -1
  17. package/model/sdkgen.aon +58 -0
  18. package/package.json +1 -1
  19. package/project/.sdk/model/feature/secrets.aon +82 -0
  20. package/project/.sdk/src/cmp/cpp/Main_cpp.ts +2 -1
  21. package/project/.sdk/src/cmp/cpp/Test_cpp.ts +5 -1
  22. package/project/.sdk/src/cmp/csharp/Main_csharp.ts +2 -1
  23. package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +30 -6
  24. package/project/.sdk/src/cmp/csharp/TestEntity_csharp.ts +29 -5
  25. package/project/.sdk/src/cmp/csharp/Test_csharp.ts +5 -1
  26. package/project/.sdk/src/cmp/dart/Main_dart.ts +2 -1
  27. package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +23 -6
  28. package/project/.sdk/src/cmp/dart/TestEntity_dart.ts +28 -7
  29. package/project/.sdk/src/cmp/dart/Test_dart.ts +5 -1
  30. package/project/.sdk/src/cmp/go/Main_go.ts +2 -1
  31. package/project/.sdk/src/cmp/go/Test_go.ts +5 -1
  32. package/project/.sdk/src/cmp/java/Main_java.ts +2 -1
  33. package/project/.sdk/src/cmp/java/Test_java.ts +5 -1
  34. package/project/.sdk/src/cmp/js/Main_js.ts +2 -1
  35. package/project/.sdk/src/cmp/js/TestDirect_js.ts +30 -5
  36. package/project/.sdk/src/cmp/js/TestEntity_js.ts +35 -5
  37. package/project/.sdk/src/cmp/js/Test_js.ts +5 -1
  38. package/project/.sdk/src/cmp/js/fragment/Direct.test.fragment.js +7 -1
  39. package/project/.sdk/src/cmp/js/fragment/Entity.test.fragment.js +7 -1
  40. package/project/.sdk/src/cmp/kotlin/Main_kotlin.ts +2 -1
  41. package/project/.sdk/src/cmp/kotlin/Test_kotlin.ts +5 -1
  42. package/project/.sdk/src/cmp/lua/Main_lua.ts +2 -1
  43. package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +28 -4
  44. package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +24 -4
  45. package/project/.sdk/src/cmp/lua/Test_lua.ts +5 -1
  46. package/project/.sdk/src/cmp/ocaml/Gitignore_ocaml.ts +1 -0
  47. package/project/.sdk/src/cmp/perl/Main_perl.ts +2 -1
  48. package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +25 -4
  49. package/project/.sdk/src/cmp/perl/TestEntity_perl.ts +24 -4
  50. package/project/.sdk/src/cmp/perl/Test_perl.ts +5 -1
  51. package/project/.sdk/src/cmp/perl/utility_perl.ts +10 -0
  52. package/project/.sdk/src/cmp/php/Main_php.ts +2 -1
  53. package/project/.sdk/src/cmp/php/TestDirect_php.ts +24 -5
  54. package/project/.sdk/src/cmp/php/TestEntity_php.ts +28 -4
  55. package/project/.sdk/src/cmp/php/Test_php.ts +5 -1
  56. package/project/.sdk/src/cmp/py/Main_py.ts +2 -1
  57. package/project/.sdk/src/cmp/py/Test_py.ts +5 -1
  58. package/project/.sdk/src/cmp/rb/Main_rb.ts +2 -1
  59. package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +24 -5
  60. package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +24 -4
  61. package/project/.sdk/src/cmp/rb/Test_rb.ts +5 -1
  62. package/project/.sdk/src/cmp/rb/utility_rb.ts +3 -1
  63. package/project/.sdk/src/cmp/rust/Main_rust.ts +2 -1
  64. package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +31 -4
  65. package/project/.sdk/src/cmp/rust/TestEntity_rust.ts +37 -4
  66. package/project/.sdk/src/cmp/rust/Test_rust.ts +5 -1
  67. package/project/.sdk/src/cmp/scala/Main_scala.ts +2 -1
  68. package/project/.sdk/src/cmp/scala/Test_scala.ts +5 -1
  69. package/project/.sdk/src/cmp/ts/Config_ts.ts +50 -8
  70. package/project/.sdk/src/cmp/ts/Main_ts.ts +10 -2
  71. package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +10 -3
  72. package/project/.sdk/src/cmp/ts/TestEntity_ts.ts +11 -3
  73. package/project/.sdk/src/cmp/ts/Test_ts.ts +5 -1
  74. package/project/.sdk/tm/csharp/test/Runner.cs +45 -0
  75. package/project/.sdk/tm/dart/test/utility.dart +39 -0
  76. package/project/.sdk/tm/js/test/sdk-test-control.json +19 -0
  77. package/project/.sdk/tm/js/test/utility.js +91 -1
  78. package/project/.sdk/tm/lua/test/runner.lua +40 -0
  79. package/project/.sdk/tm/ocaml/test/harness.ml +14 -4
  80. package/project/.sdk/tm/perl/t/runner.pm +30 -0
  81. package/project/.sdk/tm/php/test/Runner.php +36 -0
  82. package/project/.sdk/tm/rb/test/runner.rb +25 -0
  83. package/project/.sdk/tm/rust/tests/common/mod.rs +32 -0
  84. package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
  85. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sekreto.ts +8 -2
  86. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Sigv4.ts +29 -5
  87. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/index.ts +30 -21
  88. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/Registry.ts +90 -0
  89. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/addr.ts +48 -0
  90. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/aws.ts +219 -0
  91. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/azuresecrets.ts +144 -0
  92. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/boru.ts +113 -0
  93. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/doppler.ts +77 -0
  94. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/dotenv.ts +51 -0
  95. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/env.ts +31 -0
  96. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/file.ts +54 -0
  97. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/gcpsecrets.ts +118 -0
  98. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/hashicorp.ts +159 -0
  99. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/http.ts +66 -0
  100. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/infisical.ts +107 -0
  101. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/memory.ts +33 -0
  102. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/onepassword.ts +120 -0
  103. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/provider/support.ts +176 -0
  104. package/project/.sdk/tm/ts/test/vendor/omni/Runner.ts +15 -13
  105. package/project/.sdk/tm/ts/test/vendor/omni/Util.ts +25 -19
  106. package/project/.sdk/tm/ts/test/vendor/omni/compat.ts +1 -1
  107. package/project/.sdk/tm/ts/test/vendor/omni/index.ts +1 -1
  108. package/project/.sdk/tm/zig/core/utility.zig +1 -1
  109. package/project/sdkgen-package.json +1 -1
  110. package/src/cmp/Feature.ts +5 -0
  111. package/src/cmp/TestControl.ts +64 -0
  112. package/src/helpers/featureSource.ts +130 -0
  113. package/src/sdkgen.ts +7 -0
  114. package/src/utility.ts +61 -0
  115. package/project/.sdk/tm/ts/src/feature/secrets/sekreto/Providers.ts +0 -1221
@@ -93,6 +93,38 @@ pub fn is_control_skipped(kind: &str, name: &str, mode: &str) -> (bool, String)
93
93
  (false, String::new())
94
94
  }
95
95
 
96
+ /// Extra SDK options every LIVE client is constructed with, read from
97
+ /// sdk-test-control.json `test.client.options`.
98
+ ///
99
+ /// The generated live client knows two things: the base URL (from the spec)
100
+ /// and the credential (from the environment). Everything else about how a
101
+ /// particular API wants to be talked to - which features to switch on, and
102
+ /// with what settings - is a property of THAT API, known to the project and
103
+ /// to nothing in the toolchain.
104
+ ///
105
+ /// Merged UNDER the generated fields, so the suite's own base/apikey/server
106
+ /// values win: this ADDS to the live client, it does not redirect it.
107
+ ///
108
+ /// Reserved fields are stripped HERE rather than at each merge site: the
109
+ /// generated map only names a field when the model calls for one, so a
110
+ /// "base" in this block would face no competing value and would silently
111
+ /// redirect the whole suite - credential included - to another host.
112
+ pub const LIVE_RESERVED: [&str; 6] =
113
+ ["base", "prefix", "suffix", "server", "apikey", "secret"];
114
+
115
+ pub fn live_client_options() -> Value {
116
+ let ctrl = load_test_control();
117
+ let out = Value::empty_map();
118
+ if let Value::Map(m) = get_spec(&ctrl, &["test", "client", "options"]) {
119
+ for (k, v) in m.borrow().iter() {
120
+ if !LIVE_RESERVED.contains(&k.as_str()) {
121
+ setp(&out, k, v.clone());
122
+ }
123
+ }
124
+ }
125
+ out
126
+ }
127
+
96
128
  /// Configured per-test live delay in ms; default 500.
97
129
  pub fn live_delay_ms() -> i64 {
98
130
  let ctrl = load_test_control();
@@ -78,7 +78,7 @@ pub fn make_options_util(ctx: &Rc<Context>) -> Value {
78
78
  ("base", Value::str("http://localhost:8000")),
79
79
  ("prefix", Value::str("")),
80
80
  ("suffix", Value::str("")),
81
- ("auth", jo(vec![("prefix", Value::str(""))])),
81
+ ("auth", jo(vec![("prefix", Value::str("")), ("basic", Value::Bool(false))])),
82
82
  ("headers", jo(vec![("`$CHILD`", Value::str("`$STRING`"))])),
83
83
  (
84
84
  "allow",
@@ -1,5 +1,5 @@
1
1
  // VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/Sekreto.ts)
2
- // Source: https://github.com/voxgig/sekreto @ a8c293be1b6c33d65223b2b2275797c241b1a1f1
2
+ // Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
3
3
  // License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
4
4
  // sekreto: one interface for secrets, wherever they live.
5
5
  //
@@ -11,7 +11,13 @@
11
11
  // This file is CANONICAL. Every other port is a translation of it, and
12
12
  // spec/sekreto.json is the behavioural contract they all run.
13
13
 
14
- import { Provider, ProviderSpec, makeprovider } from './Providers'
14
+ // The REGISTRY, never './Providers'. Importing the barrel here is what
15
+ // made every provider kind reachable from the core: the chain needs
16
+ // `makeprovider`, and the barrel's edge dragged AWS request signing and
17
+ // seven HTTP clients in behind it. The registry knows only the kinds
18
+ // something actually imported. See docs/design/plugin-providers.md.
19
+ import { Provider, ProviderSpec } from './provider/support'
20
+ import { makeprovider } from './provider/Registry'
15
21
 
16
22
  /** A secret name: dot-separated lowercase segments, e.g. `api.token`. */
17
23
  export type Name = string
@@ -1,5 +1,5 @@
1
1
  // VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/Sigv4.ts)
2
- // Source: https://github.com/voxgig/sekreto @ a8c293be1b6c33d65223b2b2275797c241b1a1f1
2
+ // Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
3
3
  // License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
4
4
  // AWS Signature Version 4, hand-rolled.
5
5
  //
@@ -14,7 +14,28 @@
14
14
  // carry known-answer cases that all ten ports must reproduce bit-for-bit,
15
15
  // and lets the integration mock recompute the signature server-side.
16
16
 
17
- import { createHash, createHmac } from 'node:crypto'
17
+ // node:crypto is loaded ON FIRST USE, not at import time.
18
+ //
19
+ // Providers.ts imports sigv4 for the AWS providers, and Sekreto.ts imports
20
+ // Providers — so a top-level `node:crypto` here put it in the module graph
21
+ // of every sekreto consumer, including one that never signs anything.
22
+ // Deferring keeps this module pure until an AWS provider actually runs.
23
+ //
24
+ // require(), not `await import()`: these helpers are synchronous and are
25
+ // called from synchronous signing code. The package is CommonJS.
26
+ function crypto(): typeof import('node:crypto') {
27
+ try {
28
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
29
+ return require('node:crypto')
30
+ } catch (err: any) {
31
+ throw new Error(
32
+ 'sekreto: AWS request signing needs node:crypto, which this runtime ' +
33
+ 'does not provide: ' +
34
+ err.message,
35
+ { cause: err },
36
+ )
37
+ }
38
+ }
18
39
 
19
40
  export type Sigv4Input = {
20
41
  method: string
@@ -39,11 +60,11 @@ export type Sigv4Input = {
39
60
  export type Sigv4Output = Record<string, string>
40
61
 
41
62
  function sha256hex(text: string): string {
42
- return createHash('sha256').update(text, 'utf8').digest('hex')
63
+ return crypto().createHash('sha256').update(text, 'utf8').digest('hex')
43
64
  }
44
65
 
45
66
  function hmac(key: Buffer | string, text: string): Buffer {
46
- return createHmac('sha256', key).update(text, 'utf8').digest()
67
+ return crypto().createHmac('sha256', key).update(text, 'utf8').digest()
47
68
  }
48
69
 
49
70
  /** RFC 3986 escaping, which is stricter than encodeURIComponent: AWS
@@ -128,7 +149,10 @@ export function sigv4(input: Sigv4Input): Sigv4Output {
128
149
  const kregion = hmac(kdate, input.region)
129
150
  const kservice = hmac(kregion, input.service)
130
151
  const ksigning = hmac(kservice, 'aws4_request')
131
- const signature = createHmac('sha256', ksigning).update(stringtosign, 'utf8').digest('hex')
152
+ const signature = crypto()
153
+ .createHmac('sha256', ksigning)
154
+ .update(stringtosign, 'utf8')
155
+ .digest('hex')
132
156
 
133
157
  const out: Sigv4Output = {
134
158
  authorization:
@@ -1,5 +1,5 @@
1
1
  // VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/index.ts)
2
- // Source: https://github.com/voxgig/sekreto @ a8c293be1b6c33d65223b2b2275797c241b1a1f1
2
+ // Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
3
3
  // License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
4
4
  // @voxgig/sekreto - one interface for secrets, wherever they live.
5
5
 
@@ -18,25 +18,34 @@ export {
18
18
 
19
19
  export type { Name, SekretoOptions } from './Sekreto'
20
20
 
21
- export {
22
- awsparamsprovider,
23
- awssecretsprovider,
24
- azuresecretsprovider,
25
- boruprovider,
26
- checkaddr,
27
- dopplerprovider,
28
- dotenvprovider,
29
- envprovider,
30
- fileprovider,
31
- gcpsecretsprovider,
32
- hashicorpprovider,
33
- infisicalprovider,
34
- makeprovider,
35
- memoryprovider,
36
- onepasswordprovider,
37
- } from './Providers'
21
+ // THE CORE SURFACE. Deliberately does NOT re-export the eleven provider
22
+ // kinds that need something of their runtime: pulling one through this
23
+ // file would make all of them reachable and put AWS request signing in
24
+ // every build, which is the thing the split removes.
25
+ //
26
+ // `env` and `memory` are here because they import nothing at all, and a
27
+ // chain with nowhere to read from is not usable or testable.
28
+ //
29
+ // Everything else registers itself when its module is imported:
30
+ //
31
+ // import '@voxgig/sekreto/provider/dotenv'
32
+ //
33
+ // or, for the old all-in behaviour, `from '@voxgig/sekreto/Providers'`.
34
+ // See docs/design/plugin-providers.md.
35
+ export { envprovider } from './provider/env'
36
+ export { memoryprovider } from './provider/memory'
37
+
38
+ // A pure validator, no platform dependency - kept on the core surface
39
+ // because callers validate an address before configuring a provider.
40
+ export { checkaddr } from './provider/addr'
41
+
42
+ export { makeprovider, register, registered, kinds } from './provider/Registry'
43
+ export type { ProviderDefinition } from './provider/Registry'
38
44
 
39
- export type { Provider, ProviderSpec } from './Providers'
45
+ export type { Provider, ProviderSpec } from './provider/support'
40
46
 
41
- export { sigv4 } from './Sigv4'
42
- export type { Sigv4Input, Sigv4Output } from './Sigv4'
47
+ // `sigv4` is NOT on the core surface: it is the node:crypto edge, and
48
+ // only the two aws providers use it. Import it from the module that
49
+ // needs it - `@voxgig/sekreto/provider/aws` - or from the full-set
50
+ // barrel. Re-exporting it here would put request signing in every
51
+ // build again, which is the thing the split removes.
@@ -0,0 +1,90 @@
1
+ // VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/Registry.ts)
2
+ // Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
3
+ // License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
4
+ /* Copyright (c) 2025 Voxgig Ltd, MIT License */
5
+
6
+ // THE PROVIDER REGISTRY — the seam that replaced the `makeprovider`
7
+ // switch, and the reason an SDK no longer carries thirteen providers to
8
+ // use two.
9
+ //
10
+ // The switch named every kind in one function, so every kind was
11
+ // statically reachable from `Sekreto` and no bundler could drop one. Its
12
+ // replacement is a map that a provider ADDS ITSELF to. A kind that is
13
+ // never imported is never registered, is never reachable, and is not in
14
+ // the build — which is the whole point.
15
+ //
16
+ // The vocabulary is voxgig/plugin's (docs/design/plugin-providers.md): a
17
+ // provider kind is a plugin `Definition`, a configured provider is an
18
+ // instance addressed by name+tag. What sekreto does NOT route through
19
+ // plugin is the lookup itself: `Provider.lookup` is async, and plugin's
20
+ // dispatch is synchronous by design — twenty ports, many with no
21
+ // async/await. So plugin owns the catalog, the instances and their
22
+ // order; sekreto walks them in each language's own idiom.
23
+ //
24
+ // `define` is the definition's factory: spec in, Provider out. It is
25
+ // separate from the plugin `Definition.define` callback on purpose —
26
+ // this one runs at construction and returns a value, while plugin's runs
27
+ // at load and binds. They are bridged in Catalog.ts.
28
+
29
+ import { Provider, ProviderSpec, SekretoError } from './support'
30
+
31
+ export type ProviderDefinition = {
32
+ /** The `kind` a ProviderSpec names. */
33
+ name: string
34
+ /** What this provider needs of its runtime, so a host can refuse or
35
+ * report before a lookup fails: 'fs', 'fetch', 'crypto', 'process'. */
36
+ needs?: string[]
37
+ /** Build the provider from its declarative spec. */
38
+ define: (spec: ProviderSpec) => Provider
39
+ }
40
+
41
+ const registry: { [kind: string]: ProviderDefinition } = {}
42
+
43
+ /** Register a provider kind. Called by each provider module at import,
44
+ * so importing the module IS installing it. Re-registering the same name
45
+ * replaces it, which is how a host substitutes an implementation. */
46
+ export function register(def: ProviderDefinition): void {
47
+ if (null == def || 'string' !== typeof def.name || '' === def.name) {
48
+ throw new SekretoError('sekreto: invalid provider definition')
49
+ }
50
+ registry[def.name] = def
51
+ }
52
+
53
+ export function registered(kind: string): ProviderDefinition | undefined {
54
+ return registry[kind]
55
+ }
56
+
57
+ export function kinds(): string[] {
58
+ return Object.keys(registry).sort()
59
+ }
60
+
61
+ /** Build a provider from its spec.
62
+ *
63
+ * An UNREGISTERED kind is not the same failure as an unknown one, and the
64
+ * message says which: a kind sekreto has never heard of is a typo, while
65
+ * a known kind that was not imported is the leanness mechanism working as
66
+ * designed and telling you to import it. Collapsing the two was the first
67
+ * thing that made the split confusing to use. */
68
+ export function makeprovider(spec: ProviderSpec): Provider {
69
+ const kind = null == spec ? undefined : (spec as any).kind
70
+ const def = registry[kind as string]
71
+
72
+ if (null == def) {
73
+ throw new SekretoError(
74
+ 'sekreto: unknown provider kind: ' + String(kind) +
75
+ ' (registered: ' + (kinds().join(', ') || 'none') + ')' +
76
+ (KNOWN.indexOf(String(kind)) < 0 ? '' :
77
+ " - that kind exists but its module was not imported; import" +
78
+ " '@voxgig/sekreto/provider/" + String(kind) + "' to register it"))
79
+ }
80
+
81
+ return def.define(spec)
82
+ }
83
+
84
+ /** Every kind this library ships, registered or not. Used only to tell a
85
+ * typo from a module that was not imported. */
86
+ const KNOWN = [
87
+ 'env', 'memory', 'dotenv', 'file', 'hashicorp', 'boru',
88
+ 'awssecrets', 'awsparams', 'gcpsecrets', 'azuresecrets',
89
+ 'onepassword', 'doppler', 'infisical',
90
+ ]
@@ -0,0 +1,48 @@
1
+ // VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/addr.ts)
2
+ // Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
3
+ // License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
4
+ /* Copyright (c) 2025 Voxgig Ltd, MIT License */
5
+
6
+ import { SekretoError } from './support'
7
+
8
+ export function checkaddr(addr: string): void {
9
+ if (addr.startsWith('https://')) {
10
+ return
11
+ }
12
+
13
+ if (!addr.startsWith('http://')) {
14
+ throw new SekretoError('sekreto: not an http(s) address: ' + addr)
15
+ }
16
+
17
+ // Parsed, not split on ':'. Splitting the authority on the first colon
18
+ // yields '[' for a bracketed IPv6 address, so `http://[::1]:8200` could
19
+ // never match — which made the '::1' and '[::1]' entries below
20
+ // unreachable, and refused a legitimate local vault.
21
+ //
22
+ // The ALLOWLIST is unchanged. URL only fixes what the host IS: it strips
23
+ // userinfo (so `http://127.0.0.1@evil.com/` reads as evil.com, as
24
+ // before), lowercases, and normalises numeric forms — `0177.0.0.1` is
25
+ // 127.0.0.1 and is now recognised as the loopback it actually is.
26
+ // IPv4-mapped IPv6 (`[::ffff:127.0.0.1]`) is deliberately still refused:
27
+ // widening the set is a separate decision from parsing it correctly.
28
+ let host: string
29
+ try {
30
+ host = new URL(addr).hostname
31
+ } catch (err: any) {
32
+ throw new SekretoError('sekreto: not a valid http(s) address: ' + addr)
33
+ }
34
+
35
+ if ('localhost' === host || '127.0.0.1' === host || '::1' === host || '[::1]' === host) {
36
+ return
37
+ }
38
+
39
+ throw new SekretoError(
40
+ 'sekreto: refusing to send a token in plaintext to ' + addr + ' (use https)',
41
+ )
42
+ }
43
+
44
+ /** How long any single vault round-trip may take before it is treated as
45
+ * unreachable. Ports carry the same bound. */
46
+
47
+ /** One JSON round-trip. Network failure is always an error - an
48
+ * unreachable store is a store that could not answer. */
@@ -0,0 +1,219 @@
1
+ // VENDORED: @voxgig/sekreto 0.1.2 (typescript/src/provider/aws.ts)
2
+ // Source: https://github.com/voxgig/sekreto @ 65009cb5758850db767785ab666e71895f86086b
3
+ // License: MIT (c) voxgig - see repository LICENSE. Do not edit: resync from upstream.
4
+ /* Copyright (c) 2025 Voxgig Ltd, MIT License */
5
+
6
+ import {
7
+ ProviderSpec, Provider, SekretoError, awsparam, flatname, vaultref,
8
+ } from './support'
9
+ import { checkaddr } from './addr'
10
+ import { sigv4 } from '../Sigv4'
11
+ import { fetchjson } from './http'
12
+
13
+ function awsnow(): string {
14
+ return new Date()
15
+ .toISOString()
16
+ .replace(/[-:]/g, '')
17
+ .replace(/\.\d+Z$/, 'Z')
18
+ }
19
+
20
+ type Awsopts = {
21
+ region?: string
22
+ keyid?: string
23
+ secret?: string
24
+ session?: string
25
+ addr?: string
26
+ prefix?: string
27
+ }
28
+
29
+ /** Region and credentials, from config first and the standard AWS_*
30
+ * environment variables second - those are AWS's own convention, and a
31
+ * pod or CI job that has them set should just work. Missing either is
32
+ * an error: an AWS store with no credentials could not answer. */
33
+ function awsauth(opts: Awsopts): {
34
+ region: string
35
+ keyid: string
36
+ secret: string
37
+ session?: string
38
+ } {
39
+ const env = process.env
40
+
41
+ const region = opts.region || env.AWS_REGION || env.AWS_DEFAULT_REGION || ''
42
+ const keyid = opts.keyid || env.AWS_ACCESS_KEY_ID || ''
43
+ const secret = opts.secret || env.AWS_SECRET_ACCESS_KEY || ''
44
+ const session = opts.session || env.AWS_SESSION_TOKEN || undefined
45
+
46
+ if ('' === region) {
47
+ throw new SekretoError('sekreto: aws: no region (set region or AWS_REGION)')
48
+ }
49
+ if ('' === keyid || '' === secret) {
50
+ throw new SekretoError(
51
+ 'sekreto: aws: no credentials (set keyid/secret or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY)',
52
+ )
53
+ }
54
+
55
+ return { region, keyid, secret, session }
56
+ }
57
+
58
+ /** One signed call to an AWS JSON-1.1 API. */
59
+ async function awscall(
60
+ opts: Awsopts,
61
+ service: string,
62
+ target: string,
63
+ payload: any,
64
+ ): Promise<{ status: number; body: any }> {
65
+ const auth = awsauth(opts)
66
+ // The China partition lives under its own suffix; every other
67
+ // commercial region is plain amazonaws.com.
68
+ const suffix = auth.region.startsWith('cn-') ? '.amazonaws.com.cn' : '.amazonaws.com'
69
+ const addr = opts.addr || 'https://' + service + '.' + auth.region + suffix
70
+ checkaddr(addr)
71
+
72
+ const url = addr.replace(/\/$/, '') + '/'
73
+ const body = JSON.stringify(payload)
74
+ const headers: Record<string, string> = {
75
+ 'content-type': 'application/x-amz-json-1.1',
76
+ 'x-amz-target': target,
77
+ }
78
+
79
+ const signed = sigv4({
80
+ method: 'POST',
81
+ url,
82
+ headers,
83
+ body,
84
+ service,
85
+ region: auth.region,
86
+ keyid: auth.keyid,
87
+ secret: auth.secret,
88
+ session: auth.session,
89
+ datetime: awsnow(),
90
+ })
91
+
92
+ return fetchjson('POST', url, { ...headers, ...signed }, body)
93
+ }
94
+
95
+ /** Does this AWS error body name one of the not-found types? Those are
96
+ * a miss; every other failure is a store that could not answer. */
97
+ function awsmiss(body: any, types: string[]): boolean {
98
+ const errtype = body && 'string' === typeof body.__type ? body.__type : ''
99
+ return types.some((name) => errtype.includes(name))
100
+ }
101
+
102
+ /** AWS Secrets Manager.
103
+ *
104
+ * `api.token` reads the secret named `api` (the vaultref path, so
105
+ * `db.pass.main` reads `db/pass`) and takes the `token` field of its
106
+ * JSON SecretString - the AWS idiom of one JSON map per secret. A
107
+ * SecretString that is not JSON is the value itself, under the
108
+ * conventional field `value`. Requests are SigV4-signed in-tree; see
109
+ * Sigv4.ts. */
110
+ export function awssecretsprovider(options?: Awsopts): Provider {
111
+ const opts = options || {}
112
+
113
+ return {
114
+ lookup: async (name: string) => {
115
+ const ref = vaultref(name)
116
+
117
+ const res = await awscall(opts, 'secretsmanager', 'secretsmanager.GetSecretValue', {
118
+ SecretId: ref.path,
119
+ })
120
+
121
+ if (400 === res.status && awsmiss(res.body, ['ResourceNotFoundException'])) {
122
+ return undefined
123
+ }
124
+
125
+ if (200 !== res.status) {
126
+ throw new SekretoError('sekreto: aws secretsmanager error: ' + res.status)
127
+ }
128
+
129
+ const text = res.body && res.body.SecretString
130
+
131
+ if ('string' !== typeof text) {
132
+ // A binary secret has no fields to address; only the conventional
133
+ // `value` field can mean "the bytes themselves".
134
+ const bin = res.body && res.body.SecretBinary
135
+ if ('string' === typeof bin && 'value' === ref.field) {
136
+ return Buffer.from(bin, 'base64').toString('utf8')
137
+ }
138
+ return undefined
139
+ }
140
+
141
+ let parsed: any
142
+ try {
143
+ parsed = JSON.parse(text)
144
+ } catch (err: any) {
145
+ parsed = undefined
146
+ }
147
+
148
+ if (parsed && 'object' === typeof parsed && !Array.isArray(parsed)) {
149
+ const value = parsed[ref.field]
150
+ return undefined === value || null === value ? undefined : String(value)
151
+ }
152
+
153
+ // A plain-string secret is the whole value; it has no named fields.
154
+ return 'value' === ref.field ? text : undefined
155
+ },
156
+ // Config only, never the environment: describe() feeds the spec's
157
+ // sources group, which must answer the same everywhere.
158
+ describe: () => 'awssecrets:' + (opts.region || ''),
159
+ }
160
+ }
161
+
162
+ /** AWS SSM Parameter Store.
163
+ *
164
+ * `db.pass.main` reads the parameter `/db/pass/main` (under an optional
165
+ * prefix path), decrypted. Parameter Store carries flat strings, so
166
+ * there is no field indirection. */
167
+ export function awsparamsprovider(options?: Awsopts): Provider {
168
+ const opts = options || {}
169
+
170
+ return {
171
+ lookup: async (name: string) => {
172
+ const res = await awscall(opts, 'ssm', 'AmazonSSM.GetParameter', {
173
+ Name: awsparam(name, opts.prefix),
174
+ WithDecryption: true,
175
+ })
176
+
177
+ if (400 === res.status && awsmiss(res.body, ['ParameterNotFound'])) {
178
+ return undefined
179
+ }
180
+
181
+ if (200 !== res.status) {
182
+ throw new SekretoError('sekreto: aws ssm error: ' + res.status)
183
+ }
184
+
185
+ const value = res.body && res.body.Parameter && res.body.Parameter.Value
186
+ return undefined === value || null === value ? undefined : String(value)
187
+ },
188
+ describe: () => 'awsparams:' + (opts.region || '') + (opts.prefix || ''),
189
+ }
190
+ }
191
+
192
+ /** GCP Secret Manager.
193
+ *
194
+ * `api.token` reads secret `api_token` (dots flattened to `_`; Secret
195
+ * Manager ids have no hierarchy and reject dots), latest version. The
196
+ * token comes from config, then `GOOGLE_OAUTH_ACCESS_TOKEN`, then the
197
+ * GCE/GKE metadata server - so on Google's own platform no credential
198
+ * configuration is needed at all.
199
+ *
200
+ * The metadata call itself is plain http to a link-local host by
201
+ * platform design; no credential rides on it, so `checkaddr` guards the
202
+ * Secret Manager address instead. */
203
+
204
+
205
+ // Registering at import is what makes this module's presence the only
206
+ // thing that decides whether the kind exists in a build.
207
+ import { register } from './Registry'
208
+
209
+ register({
210
+ name: 'awssecrets',
211
+ needs: ['fetch', 'crypto'],
212
+ define: (spec: ProviderSpec) => awssecretsprovider(spec),
213
+ })
214
+
215
+ register({
216
+ name: 'awsparams',
217
+ needs: ['fetch', 'crypto'],
218
+ define: (spec: ProviderSpec) => awsparamsprovider(spec),
219
+ })