feat(aws): add vault expression support for credentials (#474) #41

Merged
stack72 merged 3 commits from feat/aws-vault-credentials-474 into main 2026-06-06 23:26:49 +00:00
Owner

Summary

  • Add optional sensitive global arguments (accessKeyId, secretAccessKey, sessionToken) and non-sensitive region to all AWS CloudControl models via the codegen pipeline
  • Explicit values take precedence over the AWS SDK default credential chain and environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN, AWS_REGION)
  • Users can wire credentials with vault.get(...) expressions in model YAML, mirroring the pattern from Hetzner Cloud (#471)

Codegen changes

  • codegen/aws/libGenerator.tsAwsCredentials interface, createClient() accepts optional credentials, all CRUD functions thread credentials
  • codegen/aws/extensionModelGenerator.ts — inject credential global args with collision guards, _buildCredentials helper, _credentialKeys set for filtering desiredState, credential threading in all methods
  • codegen/aws/pipeline.ts — add credential field names to newFieldNames for upgrade diffing
  • codegen/designs/aws.md — document authentication and credential global arguments

Generated output

  • All ~249 AWS services regenerated (1398 models) with credential global args in GlobalArgsSchema and InputsSchema
  • Sensitive fields use z.meta({ sensitive: true }) in both schemas; region is not sensitive
  • Upgrade entries correctly list Added: accessKeyId, secretAccessKey, sessionToken, region
  • CalVer bumped to 2026.06.06.1 across all affected services

Test plan

  • deno check main.ts — codegen type-checks
  • deno lint / deno fmt --check — codegen passes lint and format
  • Filtered regeneration (generate:aws ec2 s3 lambda) confirms targeted fix
  • Full unfiltered regeneration — 1398 models changed as expected
  • Second unfiltered run produces zero diff (idempotency verified)
  • Sample models (EC2, S3, Lambda) pass deno check, deno lint, deno fmt
  • Upgrade path verified: instance at published 2026.05.27.1 upgrades to 2026.06.06.1 via swamp extension source add — no version mismatch error

Known limitation

Hand-written list method enrichments (e.g. codegen/aws/enrichments/rds-dbcluster-list.enrich.ts) create their own SDK clients and do not receive vault-expression credentials. This can be addressed in a follow-up issue.

Closes #474

## Summary - Add optional sensitive global arguments (`accessKeyId`, `secretAccessKey`, `sessionToken`) and non-sensitive `region` to all AWS CloudControl models via the codegen pipeline - Explicit values take precedence over the AWS SDK default credential chain and environment variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, `AWS_REGION`) - Users can wire credentials with `vault.get(...)` expressions in model YAML, mirroring the pattern from Hetzner Cloud (#471) ### Codegen changes - `codegen/aws/libGenerator.ts` — `AwsCredentials` interface, `createClient()` accepts optional credentials, all CRUD functions thread credentials - `codegen/aws/extensionModelGenerator.ts` — inject credential global args with collision guards, `_buildCredentials` helper, `_credentialKeys` set for filtering desiredState, credential threading in all methods - `codegen/aws/pipeline.ts` — add credential field names to `newFieldNames` for upgrade diffing - `codegen/designs/aws.md` — document authentication and credential global arguments ### Generated output - All ~249 AWS services regenerated (1398 models) with credential global args in `GlobalArgsSchema` and `InputsSchema` - Sensitive fields use `z.meta({ sensitive: true })` in both schemas; `region` is not sensitive - Upgrade entries correctly list `Added: accessKeyId, secretAccessKey, sessionToken, region` - CalVer bumped to `2026.06.06.1` across all affected services ## Test plan - [x] `deno check main.ts` — codegen type-checks - [x] `deno lint` / `deno fmt --check` — codegen passes lint and format - [x] Filtered regeneration (`generate:aws ec2 s3 lambda`) confirms targeted fix - [x] Full unfiltered regeneration — 1398 models changed as expected - [x] Second unfiltered run produces zero diff (idempotency verified) - [x] Sample models (EC2, S3, Lambda) pass `deno check`, `deno lint`, `deno fmt` - [x] Upgrade path verified: instance at published `2026.05.27.1` upgrades to `2026.06.06.1` via `swamp extension source add` — no version mismatch error ### Known limitation Hand-written list method enrichments (e.g. `codegen/aws/enrichments/rds-dbcluster-list.enrich.ts`) create their own SDK clients and do not receive vault-expression credentials. This can be addressed in a follow-up issue. Closes #474
feat(aws): add vault expression support for credentials (#474)
Some checks failed
CI / cloudflare models - lockfiles up to date (pull_request) Successful in 1m6s
CI / gcp models - lockfiles up to date (pull_request) Successful in 1m16s
CI / codegen - check (pull_request) Successful in 1m10s
CI / aws models - lockfiles up to date (pull_request) Successful in 1m19s
CI / workflows/gcs-bootstrap - lockfile up to date (pull_request) Has been skipped
CI / workflows/s3-bootstrap - test (pull_request) Has been skipped
CI / workflows/s3-bootstrap - lint (pull_request) Has been skipped
CI / workflows/gcs-bootstrap - test (pull_request) Has been skipped
CI / workflows/s3-bootstrap - lockfile up to date (pull_request) Has been skipped
CI / cve/dirtyfrag - check (pull_request) Has been skipped
CI / cve/dirtyfrag - fmt (pull_request) Has been skipped
CI / cve/dirtyfrag - lint (pull_request) Has been skipped
CI / cve/dirtyfrag - test (pull_request) Has been skipped
CI / gcp models - sample check (pull_request) Successful in 1m32s
CI / cve/mini-shai-hulud - check (pull_request) Has been skipped
CI / cve/mini-shai-hulud - fmt (pull_request) Has been skipped
CI / cve/dirtyfrag - lockfile up to date (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lint (pull_request) Has been skipped
CI / cve/mini-shai-hulud - test (pull_request) Has been skipped
CI / CI Security Review (pull_request) Has been skipped
CI / model/digitalocean - check (pull_request) Successful in 59s
CI / codegen - lockfile up to date (pull_request) Successful in 57s
CI / codegen - lint (pull_request) Successful in 1m1s
CI / model/hetzner-cloud - check (pull_request) Successful in 1m5s
CI / codegen - fmt (pull_request) Failing after 1m3s
CI / aws models - sample check (pull_request) Successful in 1m24s
CI / Claude Code Review (pull_request) Has been skipped
CI / Adversarial Code Review (pull_request) Has been skipped
CI / Merge Gate (pull_request) Failing after 26s
22874478a0
Add optional sensitive global arguments (accessKeyId, secretAccessKey,
sessionToken) and a non-sensitive region argument to all AWS CloudControl
models so users can wire them with vault.get(...) expressions instead of
relying solely on the SDK default credential chain.

Mirrors the pattern delivered for Hetzner Cloud in #471: explicit values
take precedence over environment variables, sensitive fields use
z.meta({ sensitive: true }), credentials are filtered from desiredState
and threaded to every lib CRUD call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: format aws design doc markdown
Some checks failed
CI / cve/dirtyfrag - check (pull_request) Has been skipped
CI / cve/dirtyfrag - fmt (pull_request) Has been skipped
CI / workflows/s3-bootstrap - test (pull_request) Has been skipped
CI / cve/dirtyfrag - lint (pull_request) Has been skipped
CI / cve/dirtyfrag - test (pull_request) Has been skipped
CI / cve/mini-shai-hulud - check (pull_request) Has been skipped
CI / cve/mini-shai-hulud - fmt (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lint (pull_request) Has been skipped
CI / cve/mini-shai-hulud - test (pull_request) Has been skipped
CI / cve/dirtyfrag - lockfile up to date (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been skipped
CI / model/hetzner-cloud - check (pull_request) Successful in 52s
CI / aws models - lockfiles up to date (pull_request) Successful in 1m0s
CI / model/digitalocean - lockfile up to date (pull_request) Successful in 57s
CI / model/digitalocean - check (pull_request) Successful in 57s
CI / model/hetzner-cloud - lockfile up to date (pull_request) Successful in 1m5s
CI / cloudflare models - lockfiles up to date (pull_request) Successful in 1m6s
CI / gcp models - lockfiles up to date (pull_request) Successful in 1m9s
CI / aws models - sample check (pull_request) Successful in 1m21s
CI / Dependency Audit (pull_request) Successful in 4m12s
CI / codegen - check (pull_request) Successful in 1m9s
CI / cloudflare models - sample check (pull_request) Successful in 1m15s
CI / CI Security Review (pull_request) Has been skipped
CI / gcp models - sample check (pull_request) Successful in 1m35s
CI / codegen - fmt (pull_request) Successful in 50s
CI / codegen - lint (pull_request) Successful in 46s
CI / codegen - lockfile up to date (pull_request) Successful in 48s
CI / Adversarial Code Review (pull_request) Successful in 4m1s
CI / Claude Code Review (pull_request) Failing after 4m6s
CI / Merge Gate (pull_request) Failing after 39s
a033e18b18
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Adversarial Review

Medium

  1. _buildCredentials ignores the collision guard — codegen/aws/extensionModelGenerator.ts:305-316

    The collision guard at lines 186-219 correctly removes credential fields from injectedCredFields (and thus from _credentialKeys and the Zod schemas) when a domain property name collides. However, _buildCredentials (lines 305-316) is emitted identically for every resource — it always reads all four fields (g.accessKeyId, g.secretAccessKey, g.sessionToken, g.region) from globalArgs, regardless of which credential fields were actually injected.

    Breaking scenario: Suppose a CF resource had a camelCase domain property named region (unlikely with PascalCase conventions, but the guard exists to handle it). The collision guard would correctly:

    • Exclude region from _credentialKeys → the domain property flows into desiredState (correct).
    • Exclude region from the credential fields in GlobalArgsSchema → no duplicate Zod field (correct).

    But _buildCredentials would still read g.region, interpret the domain property value as an AWS region, and pass it to createClient. The user would get API calls routed to an invalid/wrong region.

    Suggested fix: Generate _buildCredentials body conditionally based on injectedCredFields, only reading fields that were actually injected:

    for (const f of injectedCredFields) {
      lines.push(`    ${f.name}: g.${f.name} as string | undefined,`);
    }
    // For non-injected fields, emit `undefined` explicitly
    for (const f of credentialFields) {
      if (!injectedCredFields.includes(f)) {
        lines.push(`    ${f.name}: undefined,`);
      }
    }
    
  2. Pipeline collision check uses a different data source than the generator — codegen/aws/pipeline.ts:360-379 vs codegen/aws/extensionModelGenerator.ts:186-190

    The pipeline checks Object.keys(domainProperties) (the raw CF schema property names) while the generator parses zodResult.inputSchemaBody with a regex (/^(\w+)\s*:/). These are derived from the same underlying CF schema so they should produce the same names, but they use fundamentally different mechanisms: one walks the schema object keys, the other parses generated Zod code with a regex. If the Zod generator ever renames or transforms property names, these two checks would diverge silently, causing the upgrade differ to include credential fields the generator actually excluded (or vice versa).

    Suggested fix: Extract the collision check into a shared pure function that both pipeline.ts and extensionModelGenerator.ts call with the same input (e.g., Object.keys(domainProperties)), rather than having two independent implementations.

Low

  1. Partial credentials silently fall through — codegen/aws/libGenerator.ts:32-37

    createClient checks if (credentials?.accessKeyId && credentials?.secretAccessKey) before setting explicit credentials. If a user provides accessKeyId without secretAccessKey (e.g., a typo in the vault expression that only wires one field), the explicit credentials are silently ignored and the SDK falls through to the default chain. The user would unknowingly authenticate as the wrong identity (the EC2 instance role, for example) rather than getting a clear error.

    This is unlikely to cause data loss (the wrong identity would likely lack permissions), but a console.warn for partial credentials would help debugging.

  2. list method does not pass credentials — codegen/aws/extensionModelGenerator.ts:677-716

    The list method enrichment doesn't receive credentials. The design doc acknowledges this as a known limitation, so this is informational. Users relying on vault-expression credentials who also use list methods would silently fall back to env-var authentication.

Verdict

PASS — The core credential injection logic is sound and well-structured. The _buildCredentials collision asymmetry (Medium #1) is a real logic error but cannot trigger in practice with current CloudFormation PascalCase conventions. The pipeline/generator divergent collision checks (Medium #2) are a maintainability concern. Neither blocks this merge.

## Adversarial Review ### Medium 1. **`_buildCredentials` ignores the collision guard — `codegen/aws/extensionModelGenerator.ts:305-316`** The collision guard at lines 186-219 correctly removes credential fields from `injectedCredFields` (and thus from `_credentialKeys` and the Zod schemas) when a domain property name collides. However, `_buildCredentials` (lines 305-316) is emitted identically for every resource — it always reads all four fields (`g.accessKeyId`, `g.secretAccessKey`, `g.sessionToken`, `g.region`) from `globalArgs`, regardless of which credential fields were actually injected. **Breaking scenario**: Suppose a CF resource had a camelCase domain property named `region` (unlikely with PascalCase conventions, but the guard exists to handle it). The collision guard would correctly: - Exclude `region` from `_credentialKeys` → the domain property flows into `desiredState` (correct). - Exclude `region` from the credential fields in `GlobalArgsSchema` → no duplicate Zod field (correct). But `_buildCredentials` would still read `g.region`, interpret the domain property value as an AWS region, and pass it to `createClient`. The user would get API calls routed to an invalid/wrong region. **Suggested fix**: Generate `_buildCredentials` body conditionally based on `injectedCredFields`, only reading fields that were actually injected: ```ts for (const f of injectedCredFields) { lines.push(` ${f.name}: g.${f.name} as string | undefined,`); } // For non-injected fields, emit `undefined` explicitly for (const f of credentialFields) { if (!injectedCredFields.includes(f)) { lines.push(` ${f.name}: undefined,`); } } ``` 2. **Pipeline collision check uses a different data source than the generator — `codegen/aws/pipeline.ts:360-379` vs `codegen/aws/extensionModelGenerator.ts:186-190`** The pipeline checks `Object.keys(domainProperties)` (the raw CF schema property names) while the generator parses `zodResult.inputSchemaBody` with a regex (`/^(\w+)\s*:/`). These are derived from the same underlying CF schema so they *should* produce the same names, but they use fundamentally different mechanisms: one walks the schema object keys, the other parses generated Zod code with a regex. If the Zod generator ever renames or transforms property names, these two checks would diverge silently, causing the upgrade differ to include credential fields the generator actually excluded (or vice versa). **Suggested fix**: Extract the collision check into a shared pure function that both pipeline.ts and extensionModelGenerator.ts call with the same input (e.g., `Object.keys(domainProperties)`), rather than having two independent implementations. ### Low 1. **Partial credentials silently fall through — `codegen/aws/libGenerator.ts:32-37`** `createClient` checks `if (credentials?.accessKeyId && credentials?.secretAccessKey)` before setting explicit credentials. If a user provides `accessKeyId` without `secretAccessKey` (e.g., a typo in the vault expression that only wires one field), the explicit credentials are silently ignored and the SDK falls through to the default chain. The user would unknowingly authenticate as the wrong identity (the EC2 instance role, for example) rather than getting a clear error. This is unlikely to cause data loss (the wrong identity would likely lack permissions), but a `console.warn` for partial credentials would help debugging. 2. **`list` method does not pass credentials — `codegen/aws/extensionModelGenerator.ts:677-716`** The list method enrichment doesn't receive credentials. The design doc acknowledges this as a known limitation, so this is informational. Users relying on vault-expression credentials who also use list methods would silently fall back to env-var authentication. ### Verdict **PASS** — The core credential injection logic is sound and well-structured. The `_buildCredentials` collision asymmetry (Medium #1) is a real logic error but cannot trigger in practice with current CloudFormation PascalCase conventions. The pipeline/generator divergent collision checks (Medium #2) are a maintainability concern. Neither blocks this merge.
Author
Owner

Code Review

Blocking Issues

  1. Stale snapshot tests for extensionModelGeneratorcodegen/aws/__snapshots__/extensionModelGenerator_test.ts.snap does not reflect the credential injection feature present in the current extensionModelGenerator.ts. The snapshots lack:

    • accessKeyId, secretAccessKey, sessionToken, region fields in GlobalArgsSchema and InputsSchema
    • import type { AwsCredentials } from "./_lib/aws.ts";
    • const _credentialKeys = new Set(...) and function _buildCredentials(...)
    • credentials argument passed to createResource, readResource, updateResource, deleteResource calls in generated methods

    Running deno test in codegen/aws/ will produce snapshot mismatches for all 7 snapshot test cases. Snapshots need to be regenerated with deno test --update-snapshots.

Suggestions

  1. Stale file path references in codegen/designs/aws.md — Section 9 refers to src/codegen/zodGenerator.ts and section 10 to src/pipeline/version.ts. The actual paths are codegen/shared/zodGenerator.ts and codegen/shared/version.ts respectively (confirmed by the imports in pipeline.ts).

  2. Redundant const primaryId shadowinggenerateAwsExtensionModel declares const primaryId at the function scope (line 296) and again inside the if (input.listMethod) block (line 679), computing the same value. The inner declaration can be removed since it's already in scope.

## Code Review ### Blocking Issues 1. **Stale snapshot tests for `extensionModelGenerator`** — `codegen/aws/__snapshots__/extensionModelGenerator_test.ts.snap` does not reflect the credential injection feature present in the current `extensionModelGenerator.ts`. The snapshots lack: - `accessKeyId`, `secretAccessKey`, `sessionToken`, `region` fields in `GlobalArgsSchema` and `InputsSchema` - `import type { AwsCredentials } from "./_lib/aws.ts";` - `const _credentialKeys = new Set(...)` and `function _buildCredentials(...)` - `credentials` argument passed to `createResource`, `readResource`, `updateResource`, `deleteResource` calls in generated methods Running `deno test` in `codegen/aws/` will produce snapshot mismatches for all 7 snapshot test cases. Snapshots need to be regenerated with `deno test --update-snapshots`. ### Suggestions 1. **Stale file path references in `codegen/designs/aws.md`** — Section 9 refers to `src/codegen/zodGenerator.ts` and section 10 to `src/pipeline/version.ts`. The actual paths are `codegen/shared/zodGenerator.ts` and `codegen/shared/version.ts` respectively (confirmed by the imports in `pipeline.ts`). 2. **Redundant `const primaryId` shadowing** — `generateAwsExtensionModel` declares `const primaryId` at the function scope (line 296) and again inside the `if (input.listMethod)` block (line 679), computing the same value. The inner declaration can be removed since it's already in scope.
fix: address review findings for credential injection
All checks were successful
CI / cve/dirtyfrag - check (pull_request) Has been skipped
CI / workflows/s3-bootstrap - test (pull_request) Has been skipped
CI / Dependency Audit (pull_request) Successful in 4m15s
CI / cve/dirtyfrag - fmt (pull_request) Has been skipped
CI / cve/dirtyfrag - lint (pull_request) Has been skipped
CI / cve/dirtyfrag - test (pull_request) Has been skipped
CI / cve/mini-shai-hulud - fmt (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lint (pull_request) Has been skipped
CI / cve/mini-shai-hulud - test (pull_request) Has been skipped
CI / cve/mini-shai-hulud - check (pull_request) Has been skipped
CI / cve/dirtyfrag - lockfile up to date (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been skipped
CI / model/hetzner-cloud - check (pull_request) Successful in 1m2s
CI / model/digitalocean - lockfile up to date (pull_request) Successful in 1m11s
CI / aws models - lockfiles up to date (pull_request) Successful in 1m15s
CI / model/digitalocean - check (pull_request) Successful in 1m8s
CI / model/hetzner-cloud - lockfile up to date (pull_request) Successful in 1m20s
CI / cloudflare models - lockfiles up to date (pull_request) Successful in 1m14s
CI / gcp models - lockfiles up to date (pull_request) Successful in 1m17s
CI / codegen - fmt (pull_request) Successful in 1m11s
CI / codegen - check (pull_request) Successful in 1m14s
CI / cloudflare models - sample check (pull_request) Successful in 1m27s
CI / aws models - sample check (pull_request) Successful in 1m33s
CI / gcp models - sample check (pull_request) Successful in 1m41s
CI / CI Security Review (pull_request) Has been skipped
CI / codegen - lint (pull_request) Successful in 52s
CI / codegen - lockfile up to date (pull_request) Successful in 59s
CI / Claude Code Review (pull_request) Successful in 3m0s
CI / Adversarial Code Review (pull_request) Successful in 2m53s
CI / Merge Gate (pull_request) Successful in 24s
787bb8cb24
- Fix _buildCredentials to respect collision guard: only reads fields
  that were actually injected, emits `undefined` for collision-guarded
  fields instead of reading domain properties as credentials
- Extract shared collision check: add getInjectedCredentialFields() and
  AWS_CREDENTIAL_FIELD_NAMES so pipeline and generator use the same
  data source (domainPropertyNames) instead of independent mechanisms
- Add console.warn for partial credentials (accessKeyId without
  secretAccessKey or vice versa)
- Remove redundant const primaryId shadowing in list method block
- Fix stale file paths in aws.md (src/codegen/ → codegen/shared/)
- Update snapshot tests for credential injection feature
- Add domainPropertyNames to AwsExtensionModelInput interface

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Code Review

Blocking Issues

None.

Suggestions

  1. No test for credential collision case (extensionModelGenerator_test.ts): There's no snapshot or unit test covering the case where a CF domain property name collides with a credential field name (e.g., a hypothetical resource with an accessKeyId property). The collision guard in generateAwsExtensionModel (lines 215–217) would silently omit that credential field; a test would document and protect that behavior.

  2. getInjectedCredentialFields has no direct unit test (extensionModelGenerator.ts:812–817): The function is only exercised indirectly through the generator snapshots. A direct test (similar to the resolveNamingField unit tests) would make its contract explicit and protect against regression when the AWS_CREDENTIAL_FIELD_NAMES constant is extended.

  3. enrichState uses StateData before its type declaration (snapshot line ~1225 and generated enrichment output): The enrichment body is emitted before StateSchema / type StateData. TypeScript resolves type aliases throughout the entire file scope, so this compiles correctly, but the ordering is subtly surprising. The design doc doesn't call this out. No action required — noting for awareness.

  4. Polling timeout in pollOperationStatus (libGenerator.ts:111): The poll loop runs up to 60 iterations with exponential backoff capped at 90 s, giving a potential worst-case wait of ~90 minutes. This is intentional per the design doc, but there's no configurable override for the timeout. Not a bug — mentioning in case operator experience reveals a need to tune it.

## Code Review ### Blocking Issues None. ### Suggestions 1. **No test for credential collision case** (`extensionModelGenerator_test.ts`): There's no snapshot or unit test covering the case where a CF domain property name collides with a credential field name (e.g., a hypothetical resource with an `accessKeyId` property). The collision guard in `generateAwsExtensionModel` (lines 215–217) would silently omit that credential field; a test would document and protect that behavior. 2. **`getInjectedCredentialFields` has no direct unit test** (`extensionModelGenerator.ts:812–817`): The function is only exercised indirectly through the generator snapshots. A direct test (similar to the `resolveNamingField` unit tests) would make its contract explicit and protect against regression when the `AWS_CREDENTIAL_FIELD_NAMES` constant is extended. 3. **`enrichState` uses `StateData` before its `type` declaration** (snapshot line ~1225 and generated enrichment output): The enrichment body is emitted before `StateSchema` / `type StateData`. TypeScript resolves type aliases throughout the entire file scope, so this compiles correctly, but the ordering is subtly surprising. The design doc doesn't call this out. No action required — noting for awareness. 4. **Polling timeout in `pollOperationStatus`** (`libGenerator.ts:111`): The poll loop runs up to 60 iterations with exponential backoff capped at 90 s, giving a potential worst-case wait of ~90 minutes. This is intentional per the design doc, but there's no configurable override for the timeout. Not a bug — mentioning in case operator experience reveals a need to tune it.
Author
Owner

Adversarial Review

Medium

  1. codegen/aws/libGenerator.ts:30 (generated _lib/aws.ts) — config typed as Record<string, unknown> bypasses CloudControlClient type checking

    The createClient function constructs config as Record<string, unknown> and passes it to new CloudControlClient(config). This silently bypasses TypeScript's compile-time check for invalid fields. If a typo is introduced (e.g., credential instead of credentials), the SDK will silently ignore the malformed config and fall back to the default credential chain — the user's vault-provided credentials will not be used, and the operation will succeed or fail against the wrong AWS account.

    Breaking scenario: A future edit accidentally sets config.credential = { ... } (singular). No type error, no runtime error — operations silently run under the wrong identity.

    Suggested fix: Use CloudControlClientConfig from the SDK to type config, or construct the config object inline with the new CloudControlClient() call so the type is checked.

  2. codegen/aws/libGenerator.ts:36-43 (generated _lib/aws.ts) — partial credentials silently degrade to default chain

    If a user provides accessKeyId in their model YAML but forgets secretAccessKey, the code logs a console.warn and falls back to the default credential chain. The operation proceeds under a potentially different AWS identity than intended.

    Breaking scenario: User sets accessKeyId: ${{ vault.get(v, key) }} but secretAccessKey vault reference resolves to undefined due to a typo. The warn goes to console (easy to miss in automated runs), and the resource is created in the wrong account. This is a security-relevant silent degradation.

    Suggested fix: Consider throwing an error instead of warning when credentials are partially provided. The user clearly intended explicit credentials — silently falling back defeats the purpose.

  3. codegen/aws/extensionModelGenerator.ts:297-299_credentialKeys set is computed from injectedCredFields, which may be a subset of all credential field names if collisions exist

    If a CF resource has a domain property named region (unlikely but possible — the collision guard filters it), then region won't be in _credentialKeys. In the create and update methods, the for (const [key, value] of Object.entries(g)) loop will include region in desiredState — but that's actually correct since in a collision scenario, region IS a domain property. The _buildCredentials function correctly sets the colliding field to undefined. This is handled correctly, noting for completeness.

  4. codegen/aws/enrichments/rds-dbcluster.enrich.ts:25-26 — enrichment creates its own RDSClient ignoring vault credentials (acknowledged known limitation)

    The enrichment function enrichState creates its own RDSClient using only Deno.env.get("AWS_REGION"). When vault-provided credentials are used via globalArgs, this enrichment will use a different credential chain than the CloudControl operations. The get and sync methods call readResource (with credentials) and then enrichState (without credentials), so the enrichment may fail or read from a different account.

    The design doc explicitly calls this out as a known limitation, so not blocking, but worth noting: a user providing explicit credentials who uses get or sync on an RDS cluster will get a confusing partial failure if the default chain credentials don't have rds:DescribeDBClusters permission.

Low

  1. codegen/aws/extensionModelGenerator.ts:228 — credential field descriptions contain unescaped double quotes risk

    The desc strings are injected via "${f.desc}" into generated .describe(...) calls. Currently these strings are static and safe, but if a future edit adds a double quote in a description, it would produce invalid generated TypeScript. A template literal or JSON.stringify would be more robust.

  2. codegen/aws/extensionModelGenerator.ts — no test for the domain property collision guard

    All existing test cases pass domainPropertyNames that don't collide with ["accessKeyId", "secretAccessKey", "sessionToken", "region"]. There's no test verifying the collision guard works — e.g., a resource with a domain property named region should not have region injected as a credential field. The probability of a real collision is extremely low (CF uses PascalCase), but the guard exists, so it should have a test.

  3. codegen/aws/libGenerator.ts:30createClient creates a new SDK client on every CRUD call

    Each create, read, update, and delete invocation constructs a fresh CloudControlClient. This is the existing pattern (pre-PR), but with credentials now flowing through, it's worth noting that connection pooling is not reused across operations in a single model invocation (e.g., create calls both CreateResourceCommand and then GetResourceCommand on the same client, which is fine — but update calls readResource + updateResource which create two separate clients).

Verdict

PASS — The core credential injection mechanism is well-designed with proper collision guards, sensitive field marking, and clean separation between auth config and domain state. The partial-credentials silent fallback (Medium #2) is the most concerning behavior, but it's a warning-level issue, not a data-loss or security-vulnerability scenario in isolation since the AWS SDK itself will reject requests with invalid default-chain credentials. The enrichment limitation is explicitly documented. No blocking issues found.

## Adversarial Review ### Medium 1. **`codegen/aws/libGenerator.ts:30` (generated `_lib/aws.ts`) — `config` typed as `Record<string, unknown>` bypasses CloudControlClient type checking** The `createClient` function constructs `config` as `Record<string, unknown>` and passes it to `new CloudControlClient(config)`. This silently bypasses TypeScript's compile-time check for invalid fields. If a typo is introduced (e.g., `credential` instead of `credentials`), the SDK will silently ignore the malformed config and fall back to the default credential chain — the user's vault-provided credentials will not be used, and the operation will succeed or fail against the *wrong* AWS account. **Breaking scenario**: A future edit accidentally sets `config.credential = { ... }` (singular). No type error, no runtime error — operations silently run under the wrong identity. **Suggested fix**: Use `CloudControlClientConfig` from the SDK to type `config`, or construct the config object inline with the `new CloudControlClient()` call so the type is checked. 2. **`codegen/aws/libGenerator.ts:36-43` (generated `_lib/aws.ts`) — partial credentials silently degrade to default chain** If a user provides `accessKeyId` in their model YAML but forgets `secretAccessKey`, the code logs a `console.warn` and falls back to the default credential chain. The operation proceeds under a potentially *different* AWS identity than intended. **Breaking scenario**: User sets `accessKeyId: ${{ vault.get(v, key) }}` but `secretAccessKey` vault reference resolves to `undefined` due to a typo. The warn goes to console (easy to miss in automated runs), and the resource is created in the *wrong* account. This is a security-relevant silent degradation. **Suggested fix**: Consider throwing an error instead of warning when credentials are partially provided. The user clearly intended explicit credentials — silently falling back defeats the purpose. 3. **`codegen/aws/extensionModelGenerator.ts:297-299` — `_credentialKeys` set is computed from `injectedCredFields`, which may be a subset of all credential field names if collisions exist** If a CF resource has a domain property named `region` (unlikely but possible — the collision guard filters it), then `region` won't be in `_credentialKeys`. In the `create` and `update` methods, the `for (const [key, value] of Object.entries(g))` loop will include `region` in `desiredState` — but that's actually correct since in a collision scenario, `region` IS a domain property. The `_buildCredentials` function correctly sets the colliding field to `undefined`. This is handled correctly, noting for completeness. 4. **`codegen/aws/enrichments/rds-dbcluster.enrich.ts:25-26` — enrichment creates its own `RDSClient` ignoring vault credentials (acknowledged known limitation)** The enrichment function `enrichState` creates its own `RDSClient` using only `Deno.env.get("AWS_REGION")`. When vault-provided credentials are used via `globalArgs`, this enrichment will use a *different* credential chain than the CloudControl operations. The `get` and `sync` methods call `readResource` (with credentials) and then `enrichState` (without credentials), so the enrichment may fail or read from a different account. The design doc explicitly calls this out as a known limitation, so not blocking, but worth noting: a user providing explicit credentials who uses `get` or `sync` on an RDS cluster will get a confusing partial failure if the default chain credentials don't have `rds:DescribeDBClusters` permission. ### Low 1. **`codegen/aws/extensionModelGenerator.ts:228` — credential field descriptions contain unescaped double quotes risk** The `desc` strings are injected via `"${f.desc}"` into generated `.describe(...)` calls. Currently these strings are static and safe, but if a future edit adds a double quote in a description, it would produce invalid generated TypeScript. A template literal or JSON.stringify would be more robust. 2. **`codegen/aws/extensionModelGenerator.ts` — no test for the domain property collision guard** All existing test cases pass `domainPropertyNames` that don't collide with `["accessKeyId", "secretAccessKey", "sessionToken", "region"]`. There's no test verifying the collision guard works — e.g., a resource with a domain property named `region` should not have `region` injected as a credential field. The probability of a real collision is extremely low (CF uses PascalCase), but the guard exists, so it should have a test. 3. **`codegen/aws/libGenerator.ts:30` — `createClient` creates a new SDK client on every CRUD call** Each `create`, `read`, `update`, and `delete` invocation constructs a fresh `CloudControlClient`. This is the existing pattern (pre-PR), but with credentials now flowing through, it's worth noting that connection pooling is not reused across operations in a single model invocation (e.g., `create` calls both `CreateResourceCommand` and then `GetResourceCommand` on the same client, which is fine — but `update` calls `readResource` + `updateResource` which create two separate clients). ### Verdict **PASS** — The core credential injection mechanism is well-designed with proper collision guards, sensitive field marking, and clean separation between auth config and domain state. The partial-credentials silent fallback (Medium #2) is the most concerning behavior, but it's a warning-level issue, not a data-loss or security-vulnerability scenario in isolation since the AWS SDK itself will reject requests with invalid default-chain credentials. The enrichment limitation is explicitly documented. No blocking issues found.
stack72 deleted branch feat/aws-vault-credentials-474 2026-06-06 23:26:51 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
swamp-club/swamp-extensions!41
No description provided.