@go-to-k/cdkd 0.290.17 → 0.290.19

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.
@@ -0,0 +1,574 @@
1
+ import { defaultProvider } from "@aws-sdk/credential-provider-node";
2
+ import { NodeHttpHandler } from "@smithy/node-http-handler";
3
+ import { Agent } from "node:http";
4
+ import { Agent as Agent$1 } from "node:https";
5
+ import { Agent as Agent$2 } from "agent-base";
6
+ import { HttpProxyAgent } from "http-proxy-agent";
7
+ import { HttpsProxyAgent } from "https-proxy-agent";
8
+ import { getProxyForUrl } from "proxy-from-env";
9
+
10
+ //#region src/utils/proxy-routing-agent.ts
11
+ /**
12
+ * An `http.Agent` that routes each request through the environment's proxy, or
13
+ * direct, according to `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`.
14
+ *
15
+ * WHY A ROUTING AGENT AND NOT A PLAIN PROXY AGENT
16
+ *
17
+ * `NodeHttpHandler` picks its agent by PROTOCOL alone — `httpsAgent` for an
18
+ * `https:` request, `httpAgent` otherwise — and never consults the request's
19
+ * host. `https-proxy-agent` does not read `NO_PROXY` either. So a statically
20
+ * chosen proxy agent cannot express "this host is exempt", which is the whole
21
+ * of what `NO_PROXY` means. Deciding per REQUEST is the only place the host is
22
+ * known, and `agent-base` gives us that hook: when `connect()` returns an
23
+ * `http.Agent` rather than a socket, the base class delegates the request to it
24
+ * via `socket.addRequest(req, connectOpts)`.
25
+ *
26
+ * That delegation is also why the INNER agents carry `keepAlive` rather than
27
+ * this one: the sockets pool on whichever agent `connect()` hands back, so a
28
+ * `keepAlive` set only here would never be consulted.
29
+ *
30
+ * WHY THE INNER CACHE IS PER INSTANCE
31
+ *
32
+ * Node's `Agent.prototype.destroy()` walks `[this.freeSockets, this.sockets]`,
33
+ * and the second is the ACTIVE set — it aborts in-flight requests, not just
34
+ * idle sockets. `NodeHttpHandler.destroy()` forwards to `httpAgent` and
35
+ * `httpsAgent` unconditionally, external instances included, and cdkd destroys
36
+ * clients mid-run (`deploy` drops the STS client right after
37
+ * `GetCallerIdentity`). A module-global inner cache would therefore let one
38
+ * client's teardown kill another client's live request. Each `AwsClients`
39
+ * client gets its own routing agent, so the cache is scoped to `this` and
40
+ * {@link destroy} forwards to the inner agents — without that forwarding the
41
+ * tunneled sockets would outlive the client and keep the process alive.
42
+ *
43
+ * The cost of not sharing is duplicate CONNECT + TLS handshakes where two
44
+ * clients talk to the same host. Pooling is per host either way, so sharing
45
+ * could never remove the FIRST handshake to each distinct AWS endpoint, and
46
+ * most runtime traffic goes through the per-service `AwsClients` singleton, so
47
+ * the duplicates are few. This is also what the SDK already does: it builds a
48
+ * `keepAlive` agent per client on the unproxied path.
49
+ */
50
+ /**
51
+ * Applied to every inner agent.
52
+ *
53
+ * The SDK sets these itself, but ONLY when it is handed a plain option bag —
54
+ * an external `Agent` instance is passed through untouched
55
+ * (`@smithy/node-http-handler`'s `NodeHttpHandler` constructor). Since a
56
+ * routing agent IS such an instance, omitting them would silently drop
57
+ * connection reuse for every proxied run, and a concurrent deploy would
58
+ * renegotiate TLS per request.
59
+ */
60
+ const INNER_AGENT_OPTIONS = {
61
+ keepAlive: true,
62
+ maxSockets: 50
63
+ };
64
+ /**
65
+ * Build the absolute URL `getProxyForUrl` needs from what `connect()` is given.
66
+ *
67
+ * An IPv6 literal is bracketed, or the `host:port` join produces a string no
68
+ * URL parser accepts (`https://::1:443`) and `NO_PROXY` could never match an
69
+ * IPv6 entry. Unreachable with AWS hostnames; handled because the alternative
70
+ * is a silent wrong answer rather than an error.
71
+ */
72
+ function requestUrl(options) {
73
+ const secure = options.secureEndpoint;
74
+ const rawHost = options.host ?? "localhost";
75
+ const host = rawHost.includes(":") && !rawHost.startsWith("[") ? `[${rawHost}]` : rawHost;
76
+ const port = options.port ?? (secure ? 443 : 80);
77
+ return `${secure ? "https" : "http"}://${host}:${port}`;
78
+ }
79
+ var ProxyRoutingAgent = class extends Agent$2 {
80
+ /**
81
+ * Inner agents by `<scheme>|<proxy url or "direct">`.
82
+ *
83
+ * Keyed on the proxy URL and not merely on "proxied or not" because
84
+ * `NO_PROXY` is not the only thing that varies per host: `HTTP_PROXY` and
85
+ * `HTTPS_PROXY` may name different proxies, and a single agent bound to one
86
+ * of them would quietly send the other scheme's traffic to the wrong place.
87
+ */
88
+ innerAgents = /* @__PURE__ */ new Map();
89
+ connect(_req, options) {
90
+ const secure = options.secureEndpoint;
91
+ const proxyUrl = getProxyForUrl(requestUrl(options));
92
+ const key = `${secure ? "https" : "http"}|${proxyUrl || "direct"}`;
93
+ const cached = this.innerAgents.get(key);
94
+ if (cached) return cached;
95
+ const agent = createInnerAgent(secure, proxyUrl);
96
+ this.innerAgents.set(key, agent);
97
+ return agent;
98
+ }
99
+ destroy() {
100
+ for (const agent of this.innerAgents.values()) agent.destroy();
101
+ this.innerAgents.clear();
102
+ super.destroy();
103
+ }
104
+ };
105
+ function createInnerAgent(secure, proxyUrl) {
106
+ if (!proxyUrl) return secure ? new Agent$1(INNER_AGENT_OPTIONS) : new Agent(INNER_AGENT_OPTIONS);
107
+ return secure ? new HttpsProxyAgent(proxyUrl, INNER_AGENT_OPTIONS) : new HttpProxyAgent(proxyUrl, INNER_AGENT_OPTIONS);
108
+ }
109
+
110
+ //#endregion
111
+ //#region src/utils/aws-client-defaults.ts
112
+ /**
113
+ * Client config every AWS SDK client in cdkd must be built with.
114
+ *
115
+ * WHY THIS EXISTS
116
+ *
117
+ * The AWS SDK for JavaScript v3 does NOT read `HTTPS_PROXY` / `HTTP_PROXY` the
118
+ * way botocore (the AWS CLI) and Go's `net/http` do. Its own guide says a proxy
119
+ * is supplied "through a third-party HTTP agent" by whoever constructs the
120
+ * client — so on a machine whose only egress is a corporate proxy, an SDK call
121
+ * dials out directly and fails, typically as
122
+ * `CredentialsProviderError: self-signed certificate in certificate chain`
123
+ * because the direct route is what the network intercepts (issue #2388).
124
+ *
125
+ * Node 24's `NODE_USE_ENV_PROXY=1` is not a way out: it rewires the GLOBAL
126
+ * agent, and every SDK client builds its own.
127
+ *
128
+ * WHY THE CREDENTIAL CHAIN IS INJECTED TOO
129
+ *
130
+ * A client's `requestHandler` does not reach every credential hop:
131
+ *
132
+ * - STS (`role_arn` profiles, web identity) reads `requestHandler` off
133
+ * `parentClientConfig`, so it INHERITS ours. Nothing to do.
134
+ * - `@aws-sdk/credential-provider-sso` builds its portal client from
135
+ * `clientConfig` alone, coalescing only `logger`, `region` and
136
+ * `userAgentAppId` from the caller. `@aws-sdk/token-providers` does the same
137
+ * for the SSO-OIDC refresh. So an SSO profile fails at
138
+ * `resolveSSOCredentials` — BEFORE any service call — unless the chain is
139
+ * constructed here with the handler threaded through `clientConfig`.
140
+ * - IMDS (`@smithy/credential-provider-imds`) and ECS container credentials
141
+ * (`@aws-sdk/credential-provider-http`) call `node:http` / build their own
142
+ * handler, so they bypass a proxy on their own and need no special casing.
143
+ *
144
+ * The chain is therefore built per call. `defaultProvider` MEMOIZES resolved
145
+ * credentials inside the chain instance, so one shared instance would hand a
146
+ * client configured for profile A the credentials of profile B. Per call costs
147
+ * nothing relative to today, since each client already builds its own chain.
148
+ *
149
+ * `clientConfig` carries `requestHandler` and NOTHING else — a `region` there
150
+ * would override the SSO portal's own region — and `profile` is passed
151
+ * ALONGSIDE it, because the built-in chain sees a profile through the client
152
+ * config while an injected chain does not.
153
+ *
154
+ * SPREAD THIS FIRST, SITE-SPECIFIC CONFIG SECOND
155
+ *
156
+ * `new S3Client({ ...awsClientDefaults({ profile }), region, ...(creds && { credentials: creds }) })`
157
+ *
158
+ * A site that supplies its own `credentials` must keep them — `config-loader`'s
159
+ * default-bucket probe reuses the STS client's resolved provider on purpose, so
160
+ * that the bucket it probes is checked as the identity the name was derived
161
+ * from. That provider already carries the handler, because the STS client it
162
+ * came from was built through this helper.
163
+ *
164
+ * WHY THE ASSUMED ROLE IS PUBLISHED FROM HERE
165
+ *
166
+ * `--role-arn` / `CDKD_ROLE_ARN` used to reach the SDK through the `AWS_*`
167
+ * environment variables ALONE (`src/utils/role-arn.ts`). That channel is dead
168
+ * whenever a profile is selected: `@aws-sdk/credential-provider-node` skips the
169
+ * env-var provider entirely once `AWS_PROFILE` is set OR a `profile` is passed
170
+ * on the client config, so `cdkd deploy --profile ci --role-arn <r>` assumed the
171
+ * role, logged it as assumed, and then issued every AWS call as the profile's
172
+ * own principal — in the wrong account, silently (issue
173
+ * [#3130](https://github.com/go-to-k/cdkd/issues/3130)).
174
+ *
175
+ * Clearing `AWS_PROFILE` would not have closed it: cdkd ALSO threads `profile`
176
+ * as an explicit client-config key at every construction site, and an explicit
177
+ * key outranks the environment. So the assumed credentials are published HERE
178
+ * instead, as an explicit `credentials` value — which outranks BOTH, because
179
+ * `resolveAwsSdkSigV4Config` uses `config.credentials` when present and never
180
+ * builds the default chain, leaving `profile` to select non-credential settings
181
+ * only (`role-arn.ts`'s header carries why the profile is left in place).
182
+ *
183
+ * This is the one place that reaches every client without a per-site edit:
184
+ * `scripts/check-aws-client-defaults.ts` requires this helper to be spread
185
+ * FIRST at EVERY `new XxxClient(...)` under `src/**`, with an EMPTY allow-list.
186
+ * The same spread order keeps a site's OWN explicit `credentials` winning — the
187
+ * cross-account `Fn::GetStackOutput` read stays scoped to the producer's role.
188
+ *
189
+ * Clients built OUTSIDE `src/**` (cdk-local's own, the CDK app subprocess) see
190
+ * none of this; they read the `AWS_*` triple `applyRoleArnIfSet` also writes,
191
+ * which the SDK honours for them unless a profile is selected.
192
+ */
193
+ /**
194
+ * The profile `@smithy/shared-ini-file-loader`'s `getProfileName` falls back to
195
+ * when neither a client config nor `AWS_PROFILE` names one.
196
+ *
197
+ * Naming it EXPLICITLY is how {@link callerIdentityForOptOut} builds shape 3's
198
+ * chain: `credential-provider-node`'s first link short-circuits whenever
199
+ * `init.profile ?? process.env.AWS_PROFILE` is truthy, and every OTHER link it
200
+ * feeds (`fromSSO` / `fromIni` / `fromProcess`) resolves the same profile name
201
+ * it would have defaulted to, while `fromTokenFile` and the container / IMDS
202
+ * providers do not read a profile at all. So passing this is EXACTLY "the
203
+ * default chain minus its environment link" — the one thing that has to be
204
+ * subtracted, expressed through the SDK's own documented precedence rather than
205
+ * by re-spelling the chain (which would then go stale on an SDK bump).
206
+ */
207
+ const SDK_DEFAULT_PROFILE = "default";
208
+ /**
209
+ * The one warning {@link CHAIN_LOGGER} drops, matched on its distinctive line
210
+ * rather than on the whole text: the rest of the message is advisory prose an
211
+ * SDK bump is free to reword, while this line is the warning's identity.
212
+ */
213
+ const MULTIPLE_CREDENTIAL_SOURCES_WARNING = "Multiple credential sources detected";
214
+ /**
215
+ * Handed to shape 3's injected chain to drop exactly ONE message —
216
+ * `credential-provider-node`'s `Multiple credential sources detected` warning —
217
+ * while every other warning the chain emits still reaches the user.
218
+ *
219
+ * That warning fires on `profile set AND AWS_ACCESS_KEY_ID/SECRET set`, which
220
+ * is exactly shape 3's inputs, because this module NAMES `default` as the
221
+ * profile ({@link SDK_DEFAULT_PROFILE}) to subtract the env link. Its text
222
+ * ("Both AWS_PROFILE and the pair ... are set. This SDK will proceed with the
223
+ * AWS_PROFILE value.") is FALSE here: `AWS_PROFILE` is unset, the profile name
224
+ * is one cdkd passed, and the triple is one cdkd itself wrote. Left alone it
225
+ * goes to `console.warn`, outside cdkd's logger, on every shape-3 client.
226
+ *
227
+ * **Scoped to shape 3, deliberately.** Shape 1 (`AWS_PROFILE` or a `profile`
228
+ * key selected) returns `{}`, so the client builds the chain itself with no
229
+ * logger and the SDK still emits this warning to `console.warn`. That is left
230
+ * alone: there the message is TRUE — a profile really is selected and really
231
+ * does win — so only its closing advice is unhelpful, and silencing it would
232
+ * mean injecting a logger into every client config in the tree.
233
+ *
234
+ * **Why not a wholesale `warn: () => {}`.** Shape 3's own population is who
235
+ * loses real warnings: `@smithy/credential-provider-imds`'s
236
+ * `staticStabilityProvider` warns on `Credential renew failed:` and on
237
+ * extending an expiration, and `@aws-sdk/credential-provider-http`'s `fromHttp`
238
+ * warns when both the relative and full container-credential URIs (or both
239
+ * token spellings) are set. Both of those default to `console` when no logger
240
+ * is passed, so forwarding `warn` to the console restores their baseline
241
+ * exactly. `trace` / `debug` / `info` stay no-ops: `defaultProvider`
242
+ * reaches those through `logger?.` and therefore emits NOTHING without a
243
+ * logger, so forwarding them would newly print a per-link chain trace on every
244
+ * shape-3 run.
245
+ *
246
+ * **`error` is DEFENSIVE, and restores no baseline** — the claim above is true
247
+ * of `warn` alone. No link in the installed chain calls either spelling of it:
248
+ * across `@aws-sdk/credential-provider-*`, `@aws-sdk/token-providers` and
249
+ * `@smithy/credential-provider-imds` the logger methods invoked are
250
+ * `logger?.debug`, `logger?.warn` and `logger.warn`, with zero `logger.error` /
251
+ * `logger?.error` (measured 2026-09-17), and the imds `options?.logger ||
252
+ * console` default is therefore warn-only in practice. The arm is kept so that
253
+ * a link which starts raising one is not silenced by cdkd having supplied a
254
+ * logger. If such a link arrives, re-decide this arm rather than assuming it is
255
+ * still right: a new link will most likely reach the logger through
256
+ * `logger?.error`, which is the same optional-call shape that makes `info`
257
+ * silent without a logger — so forwarding it would print a line the SDK emits
258
+ * for nobody today, and the `info` argument above applies to it unchanged.
259
+ *
260
+ * **A dropped emission is permanent, and that is a bound, not a claim of
261
+ * harmlessness.** `multipleCredentialSourceWarningEmitted` is a module-global
262
+ * one-shot inside `credential-provider-node`, and the SDK sets it whether or
263
+ * not the logger printed anything — so a drop here silences any LATER
264
+ * emission in the process, including one from a chain cdk-local builds. What
265
+ * makes that acceptable is reachability rather than scope: a later legitimate
266
+ * emission needs a profile selected, and a selected profile is shape 1, where
267
+ * nothing is dropped. Only a caller that builds a shape-3 client and then
268
+ * selects a profile in the SAME process could lose one, and no cdkd path does:
269
+ * `buildProgram`'s `preAction` hook (`src/cli/program.ts`) mirrors `--profile`
270
+ * into `process.env.AWS_PROFILE` BEFORE any command action runs, so a
271
+ * `--profile` run is shape 1 at every site from its first client onward.
272
+ *
273
+ * **That hook is the whole argument, which is why it is named.** Only 2 of the
274
+ * 19 opt-out sites pass `profile` to this helper, so "a `--profile` run passes
275
+ * the key to every site" is NOT what makes the claim true — and moving the
276
+ * mirror into the individual commands, or dropping it because the SDK reads the
277
+ * flag anyway, would kill the reachability argument with nothing in THIS file's
278
+ * tests failing. Removing the hook outright does red three cases that assert it
279
+ * directly (`AWS_PROFILE` set before a nested command action runs, and two
280
+ * siblings), so the tree is not silent about the hook itself — it is silent
281
+ * about this argument depending on it.
282
+ *
283
+ * **A BOUND on both forwards** (go-to-k/cdkd#3003, recorded rather than fixed):
284
+ * `console.warn` / `console.error` bypass `ConsoleLogger.formatMessage`, so the
285
+ * extra arguments the chain passes — an SDK error object on
286
+ * `logger.warn("Credential renew failed: ", e)` — skip the C0 / C1 / bidi
287
+ * denylist every cdkd logger call inherits, and skip cdkd's level control too.
288
+ * It is NOT a regression: without a logger those links write to `console`
289
+ * themselves, so the baseline is the same unsanitized call. Routing through
290
+ * `getLogger()` is what the fix would need, and it collides with the import
291
+ * closure this file's own import block describes: this module is reachable from
292
+ * `aws-clients.ts`, so every relative import in it must carry the `.ts`
293
+ * spelling, and `src/utils/logger.ts` reaches `display-safe` /
294
+ * `live-renderer` / `stack-context` through `.js` specifiers — pulling the
295
+ * whole tree into the closure `vp run audit:coverage:check` resolves the way
296
+ * `node` does. The same constraint is why the whitespace-proxy refusal below
297
+ * throws a plain `Error` rather than a `CdkdError`.
298
+ *
299
+ * `constructor.name` must not be `NoOpLogger` or `credential-provider-node`
300
+ * routes the warning to `console.warn` instead of here — a plain object literal
301
+ * is what satisfies that.
302
+ *
303
+ * EXPORTED as a test seam only, like {@link resetAwsClientDefaults}. No
304
+ * `src/**` module reads it THROUGH this export — the one reader is
305
+ * `callerIdentityForOptOut` below, which hands it straight to `defaultProvider`.
306
+ * `defaultProvider` closes over it, so `error` —
307
+ * the arm no installed link calls — is reachable from a test by NO other route.
308
+ * Do not mutate it: every shape-3 chain in the process shares this one object.
309
+ */
310
+ const CHAIN_LOGGER = {
311
+ trace: (..._args) => {},
312
+ debug: (..._args) => {},
313
+ info: (..._args) => {},
314
+ warn: (...args) => {
315
+ if (args.some((arg) => typeof arg === "string" && arg.includes(MULTIPLE_CREDENTIAL_SOURCES_WARNING))) return;
316
+ console.warn(...args);
317
+ },
318
+ error: (...args) => {
319
+ console.error(...args);
320
+ }
321
+ };
322
+ /**
323
+ * The proxy variables, in the order `proxy-from-env` itself reads them.
324
+ *
325
+ * Only their PRESENCE is decided here. Which one applies to a given request —
326
+ * and whether `NO_PROXY` exempts it — is `getProxyForUrl`'s job, per request,
327
+ * inside {@link ProxyRoutingAgent}. Deciding it here instead would collapse
328
+ * `HTTP_PROXY` and `HTTPS_PROXY` into one answer and send `http://` traffic to
329
+ * an HTTPS proxy.
330
+ */
331
+ const PROXY_ENV_VARS = [
332
+ "HTTPS_PROXY",
333
+ "https_proxy",
334
+ "HTTP_PROXY",
335
+ "http_proxy",
336
+ "ALL_PROXY",
337
+ "all_proxy"
338
+ ];
339
+ let proxyConfigured;
340
+ /**
341
+ * The credentials `--role-arn` assumed for THIS process, or `undefined` when no
342
+ * role was assumed. Set once, at the command's entry, by `applyRoleArnIfSet`.
343
+ *
344
+ * Process-global for the same reason the `AWS_*` env vars it replaces were:
345
+ * `--role-arn` is a process-wide decision taken before any stack work starts,
346
+ * so there is nothing per-stack or per-concurrent-task to key it on. It is
347
+ * monotonic — set once and never unset in production — so a client built before
348
+ * it is set and one built after cannot disagree about which identity the user
349
+ * chose; only about whether the role had been assumed yet, and every command
350
+ * assumes it before constructing any other client. That is ENFORCED, not
351
+ * merely intended: {@link setAssumedRoleCredentials} refuses a second publish.
352
+ */
353
+ let assumedRoleCredentials;
354
+ /**
355
+ * The `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN` triple
356
+ * the process was started with, captured by `applyRoleArnIfSet` IMMEDIATELY
357
+ * BEFORE it overwrites those variables with the assumed role's.
358
+ *
359
+ * `undefined` means one of two different things, and the caller must not
360
+ * conflate them: no role has been assumed at all (ask
361
+ * {@link getAssumedRoleCredentials}), or a role was assumed and the caller had
362
+ * NO static credentials in its environment — the SSO / IMDS / container-role
363
+ * chains, where the triple was absent before the assume and there is nothing to
364
+ * restore. `src/utils/caller-credentials.ts` is the one place that reads the two
365
+ * together and turns them into an answer.
366
+ *
367
+ * Process-global for the same reason {@link assumedRoleCredentials} is: this is
368
+ * the state of the environment at a single process-wide decision point.
369
+ */
370
+ let preAssumeEnvCredentials;
371
+ /**
372
+ * The message BOTH ends of the single-publish invariant refuse with — this
373
+ * module's {@link setAssumedRoleCredentials} and `role-arn.ts`'s
374
+ * `applyRoleArnIfSet`, which trips first so that nothing is corrupted before
375
+ * the refusal (see that function's comment).
376
+ *
377
+ * Shared rather than spelled twice: the two were byte-identical copies, so
378
+ * rewording one would silently leave a user facing two different explanations
379
+ * of one invariant depending on which end caught them — and the test that
380
+ * matches the text would keep passing against the stale copy.
381
+ */
382
+ const SECOND_ROLE_PUBLISH_REFUSAL = "An assumed role has already been published for this process. `--role-arn` is a single process-wide decision taken once at a command entry; publishing a second role would leave clients built before and after it running as different identities.";
383
+ /**
384
+ * Publish the credentials `--role-arn` assumed, so every later SDK client
385
+ * built through {@link awsClientDefaults} runs as the role.
386
+ *
387
+ * Called by `applyRoleArnIfSet` AFTER the `sts:AssumeRole` hop completes — the
388
+ * base credentials (a profile, the env, an instance role) must still be the
389
+ * ones answering that hop.
390
+ *
391
+ * **A SECOND publish is REFUSED.** {@link assumedRoleCredentials}'s doc calls
392
+ * the value monotonic, and every consumer relies on it: a client built before a
393
+ * second publish and one built after would run as DIFFERENT identities inside
394
+ * one process, with nothing naming which. `applyRoleArnIfSet` is meant to run
395
+ * once at a command's entry, so a second call is a wiring defect — and the
396
+ * failure it produces without this refusal is silent and account-scoped, which
397
+ * is the class issue [#3130](https://github.com/go-to-k/cdkd/issues/3130)
398
+ * exists to close. `resetAwsClientDefaults()` is the only way back, and it is a
399
+ * TEST seam. A plain `Error` rather than `CdkdError` for this module's usual
400
+ * reason: importing the error module would add a relative `.js` import to the
401
+ * closure `scripts/audit-provider-coverage.ts` resolves literally.
402
+ *
403
+ * The bag is COPIED rather than stored by reference. The same object is handed
404
+ * to every client `awsClientDefaults` serves, so aliasing the caller's would
405
+ * let whoever still holds it rewrite the identity of clients already built —
406
+ * the reason `AwsClients.credentialConfig` clones for its own siblings.
407
+ */
408
+ function setAssumedRoleCredentials(credentials) {
409
+ if (assumedRoleCredentials !== void 0) throw new Error(SECOND_ROLE_PUBLISH_REFUSAL);
410
+ assumedRoleCredentials = {
411
+ accessKeyId: credentials.accessKeyId,
412
+ secretAccessKey: credentials.secretAccessKey,
413
+ sessionToken: credentials.sessionToken,
414
+ ...credentials.expiration && { expiration: credentials.expiration }
415
+ };
416
+ }
417
+ /**
418
+ * Record the caller's own pre-assume environment credentials, or `undefined`
419
+ * when the environment carried none.
420
+ *
421
+ * Called by `applyRoleArnIfSet` BEFORE it overwrites the `AWS_*` triple —
422
+ * afterwards the value is unrecoverable, and it is the only identity the
423
+ * `cdkd local` surface may hand to an emulated workload.
424
+ *
425
+ * COPIED rather than stored by reference, for {@link setAssumedRoleCredentials}'s
426
+ * reason and with more reach than it had before: since shape 2 of the
427
+ * `ignoreAssumedRole` opt-out reads this snapshot, it is the source of the
428
+ * identity EVERY opted-out client runs as, and {@link getPreAssumeEnvCredentials}
429
+ * hands it out. Aliasing the caller's object would let whoever still holds it
430
+ * rewrite that identity after the fact.
431
+ */
432
+ function setPreAssumeEnvCredentials(credentials) {
433
+ preAssumeEnvCredentials = credentials ? {
434
+ accessKeyId: credentials.accessKeyId,
435
+ secretAccessKey: credentials.secretAccessKey,
436
+ ...credentials.sessionToken !== void 0 && { sessionToken: credentials.sessionToken }
437
+ } : void 0;
438
+ }
439
+ /**
440
+ * The credentials recorded by {@link setPreAssumeEnvCredentials}, if any.
441
+ *
442
+ * Read by `src/utils/caller-credentials.ts` — the local surface's env channel —
443
+ * and by tests. Read it THROUGH that module rather than directly: `undefined`
444
+ * here is ambiguous on its own (see {@link preAssumeEnvCredentials}).
445
+ *
446
+ * The returned bag is this module's OWN copy, not the caller's — do not mutate
447
+ * it; shape 2 of the opt-out resolves every opted-out client's identity from
448
+ * the same object.
449
+ */
450
+ function getPreAssumeEnvCredentials() {
451
+ return preAssumeEnvCredentials;
452
+ }
453
+ /**
454
+ * The credentials published by {@link setAssumedRoleCredentials}, if any.
455
+ *
456
+ * ONE production consumer, and it asks a yes/no question rather than using the
457
+ * value: `applyRoleArnIfSet` reads it to refuse a second `--role-arn` BEFORE
458
+ * its `AssumeRole` hop, so that nothing is corrupted first. No client ever
459
+ * reads it — every one of those receives the credentials through
460
+ * {@link awsClientDefaults} — so this is not a way to "get the role" and must
461
+ * not become one; a site that wants the role's identity simply omits
462
+ * `ignoreAssumedRole`.
463
+ */
464
+ function getAssumedRoleCredentials() {
465
+ return assumedRoleCredentials;
466
+ }
467
+ function isProxyConfigured() {
468
+ if (proxyConfigured === void 0) {
469
+ let configured = false;
470
+ for (const name of PROXY_ENV_VARS) {
471
+ const value = process.env[name];
472
+ if (value === void 0 || value === "") continue;
473
+ if (value.trim() === "") throw new Error(`${name} is set to whitespace only. Set it to a proxy URL (e.g. http://proxy.example:8080) or unset it.`);
474
+ configured = true;
475
+ }
476
+ proxyConfigured = configured;
477
+ }
478
+ return proxyConfigured;
479
+ }
480
+ /**
481
+ * The identity a client that passed `ignoreAssumedRole` must run as, or
482
+ * `undefined` when the SDK's own chain already resolves the caller and nothing
483
+ * needs injecting.
484
+ *
485
+ * Only called once a role HAS been published — see
486
+ * {@link AwsClientDefaultsOptions.ignoreAssumedRole} for the three shapes and
487
+ * why shape 3 cannot be left to the default chain.
488
+ *
489
+ * `process.env` is READ here and never written. Clearing the triple around the
490
+ * client construction would be the obvious alternative and is wrong: cdkd
491
+ * builds clients concurrently (`--concurrency`, parallel stacks), so a window
492
+ * in which the triple is absent is a window another task resolves through.
493
+ *
494
+ * TWO BOUNDS, RECORDED RATHER THAN CLOSED
495
+ *
496
+ * 1. **A `[default]` profile that sources the environment defeats shape 3.**
497
+ * Subtracting the chain's own env link does not subtract `fromEnv` reached
498
+ * THROUGH another link, and `credential_source = Environment` in the
499
+ * `[default]` profile is exactly that — so the opt-out resolves the ROLE's
500
+ * triple again. Its sibling is `credential_process`: the ini format has no
501
+ * environment interpolation, so a templated `aws_access_key_id` is not a
502
+ * vector, but a helper process that reads `AWS_ACCESS_KEY_ID` out of the
503
+ * environment it inherits and echoes it back defeats the subtraction
504
+ * identically. It is a narrow case and it is not a regression: before this
505
+ * shape existed the same configuration simply resolved the role too. Closing
506
+ * it would mean neutering `process.env` around the resolution, which the
507
+ * paragraph above rules out on concurrency grounds.
508
+ * 2. **Shapes 2 and 3 inject an explicit `credentials`, which outranks a
509
+ * `profile` key a site sets AFTER the spread.** Shape 1 exists to hand the
510
+ * profile back to the SDK, but it only sees a profile passed through
511
+ * {@link AwsClientDefaultsOptions.profile} or `AWS_PROFILE` — a site that
512
+ * passes neither and then writes `profile: x` into its own client literal
513
+ * gets shape 2/3's credentials instead, silently. No site does that today
514
+ * (all 19 opt-out sites audited at PR #3223), and no fence catches it: the
515
+ * local-surface fence asks whether a site DECLARED an identity, not which
516
+ * keys its literal carries. Pass the profile to this helper, not only to the
517
+ * client.
518
+ */
519
+ function callerIdentityForOptOut(options, requestHandler) {
520
+ const profile = options.profile !== void 0 && options.profile !== "" ? options.profile : process.env["AWS_PROFILE"];
521
+ if (profile !== void 0 && profile !== "") return void 0;
522
+ const caller = preAssumeEnvCredentials;
523
+ if (caller) return {
524
+ accessKeyId: caller.accessKeyId,
525
+ secretAccessKey: caller.secretAccessKey,
526
+ ...caller.sessionToken && { sessionToken: caller.sessionToken }
527
+ };
528
+ return defaultProvider({
529
+ profile: SDK_DEFAULT_PROFILE,
530
+ logger: CHAIN_LOGGER,
531
+ ...requestHandler && { clientConfig: { requestHandler } }
532
+ });
533
+ }
534
+ /**
535
+ * Returns `{}` when no proxy is configured, no role has been assumed, and no
536
+ * opt-out is in play — which keeps that path byte-identical to what the SDK
537
+ * builds on its own and makes this a no-op for every existing user.
538
+ */
539
+ function awsClientDefaults(options = {}) {
540
+ const proxied = isProxyConfigured();
541
+ const optedOut = options.ignoreAssumedRole === true;
542
+ const assumed = optedOut ? void 0 : assumedRoleCredentials;
543
+ const optedOutOfPublishedRole = optedOut && assumedRoleCredentials !== void 0;
544
+ if (!proxied) {
545
+ if (assumed) return { credentials: assumed };
546
+ const caller = optedOutOfPublishedRole ? callerIdentityForOptOut(options, void 0) : void 0;
547
+ return caller ? { credentials: caller } : {};
548
+ }
549
+ const agent = new ProxyRoutingAgent();
550
+ const requestHandler = new NodeHttpHandler({
551
+ httpAgent: agent,
552
+ httpsAgent: agent
553
+ });
554
+ if (assumed) return {
555
+ requestHandler,
556
+ credentials: assumed
557
+ };
558
+ const caller = optedOutOfPublishedRole ? callerIdentityForOptOut(options, requestHandler) : void 0;
559
+ if (caller) return {
560
+ requestHandler,
561
+ credentials: caller
562
+ };
563
+ return {
564
+ requestHandler,
565
+ credentials: defaultProvider({
566
+ ...options.profile !== void 0 && options.profile !== "" && { profile: options.profile },
567
+ clientConfig: { requestHandler }
568
+ })
569
+ };
570
+ }
571
+
572
+ //#endregion
573
+ export { setAssumedRoleCredentials as a, getPreAssumeEnvCredentials as i, awsClientDefaults as n, setPreAssumeEnvCredentials as o, getAssumedRoleCredentials as r, SECOND_ROLE_PUBLISH_REFUSAL as t };
574
+ //# sourceMappingURL=aws-client-defaults-BoIxyCir.js.map