feat(aws): consolidate credential handling into shared module #224

Merged
stack72 merged 4 commits from swampbot/issue-1804-aws-credential-consolidation into main 2026-08-25 00:35:23 +00:00
Owner

Summary

Closes #1804 — consolidates duplicated AWS credential handling across five independent implementations into a single canonical source.

Before: The S3 datastore had all three credential features (preflight, IMDS skip, SSO error hints). The vault had a hand-copied error hint. The three codegen enrichments, the s3-bootstrap workflow, and 289 generated models had none. One expired SSO token produced three different behaviors.

After: A canonical source at codegen/shared/awsCredentials.ts generates credential utilities into every consumer:

  • Hand-written extensions get copies via deno task generate:aws-credentials
  • Generated models get the code inlined via libGenerator.ts
  • Enrichments get inline IMDS skip (they run at codegen time only)
  • Region parser now follows source_profile chains and reads sso-session blocks for sso_region
  • formatAwsCredentialHint takes a context parameter ("Datastore"/"Vault"/"Model") so each consumer provides its own label

New behaviors:

  • Vault gains IMDS skip and preflight check (fast-fail on expired creds instead of stalling)
  • Generated models gain IMDS skip, credential error hints in withRetry, and SSO-aware region resolution
  • Enrichments gain IMDS skip
  • Workflow gains IMDS skip

Files changed

  • codegen/shared/awsCredentials.ts — canonical credential utilities generator (single source of truth)
  • codegen/scripts/generate-aws-credentials.ts — task script
  • codegen/aws/libGenerator.ts — template updated with credential code + region parser SSO fix
  • codegen/aws/enrichments/ (4 files) — IMDS skip
  • datastore/s3/.../s3_client.ts — imports from generated module, ~80 lines removed
  • vault/aws-sm/.../aws_sm_errors.ts — imports from generated module, ~80 lines removed
  • vault/aws-sm/.../aws_sm.ts — IMDS skip + preflight added
  • workflows/s3-bootstrap/.../provisioner.ts — IMDS skip added
  • model/aws/*/ (280 services × 2 files) — regenerated with credential utilities
  • CLAUDE.md — documented new task and canonical source

Test plan

  • S3 datastore: 247 tests pass (including preflight timeout, credential classification, format hints)
  • AWS-SM vault: 67 tests pass (including error wrapping, credential hints)
  • Codegen: type-checks clean (deno check main.ts)
  • Generated models: type-check + lint + format clean (spot-checked ec2, s3)
  • Idempotency: second generate:aws run produces 0 diffs
  • Idempotency: second generate:aws-credentials run produces 0 diffs
  • All quality gates pass (check, lint, fmt) for datastore, vault, workflow, codegen

🤖 Generated with Claude Code

## Summary Closes #1804 — consolidates duplicated AWS credential handling across five independent implementations into a single canonical source. **Before:** The S3 datastore had all three credential features (preflight, IMDS skip, SSO error hints). The vault had a hand-copied error hint. The three codegen enrichments, the s3-bootstrap workflow, and 289 generated models had none. One expired SSO token produced three different behaviors. **After:** A canonical source at `codegen/shared/awsCredentials.ts` generates credential utilities into every consumer: - Hand-written extensions get copies via `deno task generate:aws-credentials` - Generated models get the code inlined via `libGenerator.ts` - Enrichments get inline IMDS skip (they run at codegen time only) - Region parser now follows `source_profile` chains and reads `sso-session` blocks for `sso_region` - `formatAwsCredentialHint` takes a `context` parameter ("Datastore"/"Vault"/"Model") so each consumer provides its own label **New behaviors:** - Vault gains IMDS skip and preflight check (fast-fail on expired creds instead of stalling) - Generated models gain IMDS skip, credential error hints in `withRetry`, and SSO-aware region resolution - Enrichments gain IMDS skip - Workflow gains IMDS skip ## Files changed - `codegen/shared/awsCredentials.ts` — canonical credential utilities generator (single source of truth) - `codegen/scripts/generate-aws-credentials.ts` — task script - `codegen/aws/libGenerator.ts` — template updated with credential code + region parser SSO fix - `codegen/aws/enrichments/` (4 files) — IMDS skip - `datastore/s3/.../s3_client.ts` — imports from generated module, ~80 lines removed - `vault/aws-sm/.../aws_sm_errors.ts` — imports from generated module, ~80 lines removed - `vault/aws-sm/.../aws_sm.ts` — IMDS skip + preflight added - `workflows/s3-bootstrap/.../provisioner.ts` — IMDS skip added - `model/aws/*/` (280 services × 2 files) — regenerated with credential utilities - `CLAUDE.md` — documented new task and canonical source ## Test plan - [x] S3 datastore: 247 tests pass (including preflight timeout, credential classification, format hints) - [x] AWS-SM vault: 67 tests pass (including error wrapping, credential hints) - [x] Codegen: type-checks clean (`deno check main.ts`) - [x] Generated models: type-check + lint + format clean (spot-checked ec2, s3) - [x] Idempotency: second `generate:aws` run produces 0 diffs - [x] Idempotency: second `generate:aws-credentials` run produces 0 diffs - [x] All quality gates pass (check, lint, fmt) for datastore, vault, workflow, codegen 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(aws): consolidate credential handling into shared module (#1804)
Some checks failed
CI: Vaults / vault/gcp-sm - test (pull_request) Has started running
CI: Datastores / Gate: Datastores (pull_request) Has been cancelled
CI: Vaults / vault/1password - lockfile up to date (pull_request) Has started running
CI / Dependency Audit (pull_request) Has been cancelled
CI / Actions Audit (pull_request) Has been cancelled
CI / Gate: Audit (pull_request) Has been cancelled
CI: Vaults / vault/azure-kv - lockfile up to date (pull_request) Has started running
CI: Vaults / vault/gcp-sm - lockfile up to date (pull_request) Has started running
CI: Extensions / workflows/s3-bootstrap - check (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - fmt (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - fmt (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - lint (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - lint (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - test (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - test (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - lockfile up to date (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - lockfile up to date (pull_request) Has been cancelled
CI: Extensions / Gate: Extensions (pull_request) Has been cancelled
CI: Vaults / vault/1password - lint (pull_request) Successful in 1m48s
CI: Vaults / vault/aws-sm - lint (pull_request) Successful in 1m41s
CI: Vaults / vault/aws-sm - test (pull_request) Successful in 1m11s
CI: Vaults / vault/aws-sm - lockfile up to date (pull_request) Successful in 1m15s
CI: Vaults / vault/gcp-sm - lint (pull_request) Successful in 1m49s
CI: Vaults / vault/azure-kv - test (pull_request) Successful in 1m8s
CI: Models / Gate: Models (pull_request) Has been cancelled
CI: Reviews / Claude Code Review (pull_request) Has been cancelled
CI: Reviews / Adversarial Code Review (pull_request) Has been cancelled
CI: Reviews / Gate: Reviews (pull_request) Has been cancelled
CI: Vaults / Gate: Vaults (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - check (pull_request) Successful in 3m10s
a4d6af079f
Extract duplicated AWS credential handling (IMDS skip, SSO error hints,
preflight check) from five independent implementations into a canonical
source at codegen/shared/awsCredentials.ts.

- Hand-written extensions (s3-datastore, aws-sm vault, s3-bootstrap
  workflow) receive generated copies via `deno task generate:aws-credentials`
- Generated models (281 services) get the credential utilities inlined
  via libGenerator.ts — no runtime dependency needed
- Codegen enrichments get inline IMDS skip
- Region parser now follows source_profile chains and reads sso-session
  blocks for sso_region
- formatAwsCredentialHint takes a context parameter so each consumer
  provides its own label (Datastore/Vault/Model)
- Vault gains IMDS skip and preflight check it was missing

Closes #1804

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: format libGenerator.ts for deno fmt
Some checks failed
CI: Vaults / vault/1password - lockfile up to date (pull_request) Has started running
CI: Datastores / Gate: Datastores (pull_request) Has been cancelled
CI: Vaults / vault/aws-sm - lockfile up to date (pull_request) Has started running
CI: Vaults / vault/gcp-sm - fmt (pull_request) Successful in 2m3s
CI: Vaults / vault/gcp-sm - lint (pull_request) Successful in 1m13s
CI: Vaults / vault/1password - test (pull_request) Successful in 1m21s
CI: Vaults / vault/azure-kv - lockfile up to date (pull_request) Has started running
CI: Extensions / workflows/gcs-bootstrap - check (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - check (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - fmt (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - fmt (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - lint (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - lint (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - test (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - test (pull_request) Has been cancelled
CI: Extensions / workflows/gcs-bootstrap - lockfile up to date (pull_request) Has been cancelled
CI: Extensions / workflows/s3-bootstrap - lockfile up to date (pull_request) Has been cancelled
CI: Extensions / Gate: Extensions (pull_request) Has been cancelled
CI: Vaults / vault/azure-kv - lint (pull_request) Successful in 1m58s
CI: Vaults / vault/gcp-sm - lockfile up to date (pull_request) Has started running
CI: Vaults / vault/aws-sm - lint (pull_request) Successful in 2m9s
CI: Vaults / vault/gcp-sm - test (pull_request) Successful in 1m13s
CI: Models / Gate: Models (pull_request) Has been cancelled
CI: Reviews / Claude Code Review (pull_request) Has been cancelled
CI: Reviews / Adversarial Code Review (pull_request) Has been cancelled
CI: Reviews / Gate: Reviews (pull_request) Has been cancelled
CI: Vaults / Gate: Vaults (pull_request) Has been cancelled
CI / Dependency Audit (pull_request) Has been cancelled
CI / Actions Audit (pull_request) Has been cancelled
CI / Gate: Audit (pull_request) Has been cancelled
398f1f135c
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix: format awsCredentials.ts for deno fmt
Some checks failed
CI: Vaults / vault/gcp-sm - fmt (pull_request) Successful in 2m10s
CI: Vaults / vault/azure-kv - test (pull_request) Successful in 47s
CI: Vaults / vault/1password - lockfile up to date (pull_request) Successful in 53s
CI: Vaults / vault/azure-kv - lockfile up to date (pull_request) Successful in 1m2s
CI / Actions Audit (pull_request) Successful in 56s
CI: Vaults / vault/gcp-sm - lint (pull_request) Successful in 2m38s
CI: Datastores / Gate: Datastores (pull_request) Successful in 0s
CI: Vaults / vault/1password - test (pull_request) Successful in 3m7s
CI: Vaults / vault/aws-sm - lockfile up to date (pull_request) Successful in 2m19s
CI: Vaults / vault/aws-sm - test (pull_request) Successful in 3m20s
CI: Vaults / vault/gcp-sm - lockfile up to date (pull_request) Successful in 2m18s
CI: Vaults / vault/gcp-sm - test (pull_request) Successful in 2m53s
CI: Extensions / workflows/gcs-bootstrap - check (pull_request) Successful in 53s
CI: Extensions / workflows/s3-bootstrap - check (pull_request) Successful in 47s
CI: Extensions / workflows/gcs-bootstrap - fmt (pull_request) Successful in 43s
CI: Extensions / workflows/s3-bootstrap - fmt (pull_request) Successful in 1m17s
CI: Models / Gate: Models (pull_request) Successful in 10s
CI: Extensions / workflows/gcs-bootstrap - lockfile up to date (pull_request) Successful in 40s
CI: Extensions / workflows/gcs-bootstrap - lint (pull_request) Successful in 1m46s
CI: Extensions / workflows/s3-bootstrap - lint (pull_request) Successful in 1m37s
CI: Extensions / workflows/s3-bootstrap - lockfile up to date (pull_request) Successful in 1m42s
CI: Extensions / workflows/gcs-bootstrap - test (pull_request) Successful in 1m58s
CI: Vaults / Gate: Vaults (pull_request) Successful in 1s
CI: Extensions / workflows/s3-bootstrap - test (pull_request) Successful in 2m8s
CI: Extensions / Gate: Extensions (pull_request) Successful in 1s
CI / Dependency Audit (pull_request) Successful in 7m42s
CI / Gate: Audit (pull_request) Successful in 1s
CI: Reviews / Adversarial Code Review (pull_request) Successful in 5m52s
CI: Reviews / Claude Code Review (pull_request) Failing after 6m57s
CI: Reviews / Gate: Reviews (pull_request) Failing after 0s
094e67c6e1
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Adversarial Review

Medium

  1. s3_client.ts:509-527 — Non-timeout errors from preflightCredentials are mislabeled as TimeoutError

    The catch block in the S3Client preflightCredentials method wraps any non-S3OperationError as a new S3OperationError with hardcoded name "TimeoutError". If the shared preflightCredentials function ever throws a non-timeout error (e.g., a TypeError from a bad argument, or a future code path change that throws before the probe starts), it would be misclassified as a timeout.

    In practice this is currently safe because headBucket always wraps errors via run() then wrapError() then S3OperationError, and the only other rejection source is the timeout Promise. But the assumption is implicit and fragile — a future change to the shared preflightCredentials function could introduce a non-timeout rejection path that gets mislabeled.

    Suggested fix: Derive the error name from the actual error rather than hardcoding "TimeoutError". For example, check err.message for the "Credential preflight timed out" substring to distinguish timeouts from other failures.

  2. aws_sm.ts:272-282 — runPreflight() is unreachable through the typed public API

    runPreflight() is defined on AwsSmVaultProvider, but createProvider() (line 748) returns VaultProvider & VaultDeleteProvider, and neither interface includes runPreflight. External callers that receive the provider through the typed API cannot call it without a cast. If the swamp framework uses convention-based runtime discovery (e.g., checking for the method name at runtime), this is fine. If it relies on the type system, the method is dead code and the preflightCredentials import is unused.

    Suggested fix: Either add runPreflight to an interface (perhaps a new VaultPreflightProvider) and update createProvider's return type, or document that the framework calls it by convention.

Low

  1. Breaking change to formatAwsCredentialHint re-exported signature

    formatAwsCredentialHint gained a required third parameter (context: string). Both s3_client.ts and aws_sm_errors.ts re-export this function. Any downstream consumer that imported it with the old 2-argument signature will get a TypeScript compilation error. This is caught at compile time (strict mode), so it will not produce runtime bugs, but it is a breaking API change on exported symbols.

  2. Enrichment files inline the IMDS check instead of importing disableImdsIfOffEc2()

    The four enrichment files (bedrock-knowledgebase/methods.ts:16-22, cfn-stackset/methods.ts:15-21, rds-dbcluster/enrich.ts:25-31, rds-dbcluster/list.enrich.ts:12-18) each inline the 7-line IMDS environment check rather than importing the shared function. This is because disableImdsIfOffEc2() is generated as non-exported in the model aws.ts and there is no shared enrichment-accessible module. The behavior is correct (the inline code is identical), but it means future changes to the IMDS logic need to be applied in 4+ additional places beyond the canonical source.

Verdict

PASS — This is a well-structured consolidation. The single-source-of-truth pattern for credential utilities is sound, the codegen template produces correct output, all call sites are updated, and the test coverage is thorough (including regression guards for edge cases like BucketRegionMismatch and _CredentialsProviderError cause-chain stripping). The medium findings are worth addressing but neither represents a production failure path today.

## Adversarial Review ### Medium 1. **s3_client.ts:509-527 — Non-timeout errors from preflightCredentials are mislabeled as TimeoutError** The catch block in the S3Client preflightCredentials method wraps any non-S3OperationError as a new S3OperationError with hardcoded name "TimeoutError". If the shared preflightCredentials function ever throws a non-timeout error (e.g., a TypeError from a bad argument, or a future code path change that throws before the probe starts), it would be misclassified as a timeout. In practice this is currently safe because headBucket always wraps errors via run() then wrapError() then S3OperationError, and the only other rejection source is the timeout Promise. But the assumption is implicit and fragile — a future change to the shared preflightCredentials function could introduce a non-timeout rejection path that gets mislabeled. **Suggested fix:** Derive the error name from the actual error rather than hardcoding "TimeoutError". For example, check err.message for the "Credential preflight timed out" substring to distinguish timeouts from other failures. 2. **aws_sm.ts:272-282 — runPreflight() is unreachable through the typed public API** runPreflight() is defined on AwsSmVaultProvider, but createProvider() (line 748) returns VaultProvider & VaultDeleteProvider, and neither interface includes runPreflight. External callers that receive the provider through the typed API cannot call it without a cast. If the swamp framework uses convention-based runtime discovery (e.g., checking for the method name at runtime), this is fine. If it relies on the type system, the method is dead code and the preflightCredentials import is unused. **Suggested fix:** Either add runPreflight to an interface (perhaps a new VaultPreflightProvider) and update createProvider's return type, or document that the framework calls it by convention. ### Low 1. **Breaking change to formatAwsCredentialHint re-exported signature** formatAwsCredentialHint gained a required third parameter (context: string). Both s3_client.ts and aws_sm_errors.ts re-export this function. Any downstream consumer that imported it with the old 2-argument signature will get a TypeScript compilation error. This is caught at compile time (strict mode), so it will not produce runtime bugs, but it is a breaking API change on exported symbols. 2. **Enrichment files inline the IMDS check instead of importing disableImdsIfOffEc2()** The four enrichment files (bedrock-knowledgebase/methods.ts:16-22, cfn-stackset/methods.ts:15-21, rds-dbcluster/enrich.ts:25-31, rds-dbcluster/list.enrich.ts:12-18) each inline the 7-line IMDS environment check rather than importing the shared function. This is because disableImdsIfOffEc2() is generated as non-exported in the model aws.ts and there is no shared enrichment-accessible module. The behavior is correct (the inline code is identical), but it means future changes to the IMDS logic need to be applied in 4+ additional places beyond the canonical source. ### Verdict **PASS** — This is a well-structured consolidation. The single-source-of-truth pattern for credential utilities is sound, the codegen template produces correct output, all call sites are updated, and the test coverage is thorough (including regression guards for edge cases like BucketRegionMismatch and _CredentialsProviderError cause-chain stripping). The medium findings are worth addressing but neither represents a production failure path today.
Author
Owner

Code Review

Blocking Issues

  1. runPreflight() is new functionality with no tests (vault/aws-sm/extensions/vaults/aws_sm.ts).

    The runPreflight() method is newly added in this PR (confirmed by diff). Neither aws_sm_test.ts nor aws_sm_errors_test.ts contains any test for it. The method's logic is not trivial: it probes via DescribeSecretCommand with a sentinel secret ID and swallows ResourceNotFoundException while propagating everything else. If the swallow logic is misconfigured (wrong error name, wrong error shape from a new SDK version), runPreflight() could silently succeed when credentials are invalid.

    CLAUDE.md explicitly requires: "New functionality in vault/ or datastore/ extensions should have corresponding tests." A mock-server test (using Deno.serve({ port: 0 })) that covers at least (a) success on ResourceNotFoundException, (b) propagation of a credential error like AccessDeniedException, and (c) the 3 000 ms timeout from preflightCredentials is needed.


Suggestions

  1. Typo in codegen/scripts/generate-aws-credentials.ts line 4: codgenDir should be codegenDir. The variable is used correctly on line 5 but the misspelling makes it inconsistent with the codegen terminology used everywhere else.

  2. Enrichment files still inline the IMDS disable guard (bedrock-knowledgebase/methods.ts, cfn-stackset/methods.ts, rds-dbcluster/enrich.ts, rds-dbcluster/list.enrich.ts). These files added the 5-line AWS_EC2_METADATA_DISABLED check inline rather than calling disableImdsIfOffEc2(). Given that enrichment files can't import from codegen/shared/ at runtime, and disableImdsIfOffEc2 is non-exported in the generated _lib/aws.ts, this appears to be an unavoidable architectural constraint. A brief comment acknowledging the intentional duplication (e.g. // disableImdsIfOffEc2 inlined — enrichments can't import from shared runtime modules) would help future readers not chase a phantom refactor.

  3. withMockServer in s3_client_test.ts leaks AWS_EC2_METADATA_DISABLED: S3Client's constructor calls disableImdsIfOffEc2(), which sets AWS_EC2_METADATA_DISABLED=true if absent. withMockServer saves and restores AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY but not AWS_EC2_METADATA_DISABLED. CLAUDE.md requires restoring all env vars in finally. In practice this leak is benign (all tests benefit from IMDS being off) and pre-exists this PR, but it's worth a note for completeness.

  4. Missing inline sanitizeResources comment on the first test (s3_client_test.ts lines 93–113, "getObject: surfaces ETag from GetObject response"): it has sanitizeResources: false with no inline comment explaining why, unlike the immediately following test which includes the SDK connection-pool rationale. Minor inconsistency with the project convention.

## Code Review ### Blocking Issues 1. **`runPreflight()` is new functionality with no tests** (`vault/aws-sm/extensions/vaults/aws_sm.ts`). The `runPreflight()` method is newly added in this PR (confirmed by diff). Neither `aws_sm_test.ts` nor `aws_sm_errors_test.ts` contains any test for it. The method's logic is not trivial: it probes via `DescribeSecretCommand` with a sentinel secret ID and swallows `ResourceNotFoundException` while propagating everything else. If the swallow logic is misconfigured (wrong error name, wrong error shape from a new SDK version), `runPreflight()` could silently succeed when credentials are invalid. CLAUDE.md explicitly requires: _"New functionality in vault/ or datastore/ extensions should have corresponding tests."_ A mock-server test (using `Deno.serve({ port: 0 })`) that covers at least (a) success on `ResourceNotFoundException`, (b) propagation of a credential error like `AccessDeniedException`, and (c) the 3 000 ms timeout from `preflightCredentials` is needed. --- ### Suggestions 1. **Typo in `codegen/scripts/generate-aws-credentials.ts` line 4**: `codgenDir` should be `codegenDir`. The variable is used correctly on line 5 but the misspelling makes it inconsistent with the `codegen` terminology used everywhere else. 2. **Enrichment files still inline the IMDS disable guard** (`bedrock-knowledgebase/methods.ts`, `cfn-stackset/methods.ts`, `rds-dbcluster/enrich.ts`, `rds-dbcluster/list.enrich.ts`). These files added the 5-line `AWS_EC2_METADATA_DISABLED` check inline rather than calling `disableImdsIfOffEc2()`. Given that enrichment files can't import from `codegen/shared/` at runtime, and `disableImdsIfOffEc2` is non-exported in the generated `_lib/aws.ts`, this appears to be an unavoidable architectural constraint. A brief comment acknowledging the intentional duplication (e.g. `// disableImdsIfOffEc2 inlined — enrichments can't import from shared runtime modules`) would help future readers not chase a phantom refactor. 3. **`withMockServer` in `s3_client_test.ts` leaks `AWS_EC2_METADATA_DISABLED`**: `S3Client`'s constructor calls `disableImdsIfOffEc2()`, which sets `AWS_EC2_METADATA_DISABLED=true` if absent. `withMockServer` saves and restores `AWS_ACCESS_KEY_ID`/`AWS_SECRET_ACCESS_KEY` but not `AWS_EC2_METADATA_DISABLED`. CLAUDE.md requires restoring all env vars in `finally`. In practice this leak is benign (all tests benefit from IMDS being off) and pre-exists this PR, but it's worth a note for completeness. 4. **Missing inline `sanitizeResources` comment on the first test** (`s3_client_test.ts` lines 93–113, `"getObject: surfaces ETag from GetObject response"`): it has `sanitizeResources: false` with no inline comment explaining why, unlike the immediately following test which includes the SDK connection-pool rationale. Minor inconsistency with the project convention.
fix: address review feedback on credential consolidation
All checks were successful
CI: Vaults / vault/1password - test (pull_request) Successful in 1m16s
CI: Vaults / vault/azure-kv - lint (pull_request) Successful in 1m26s
CI: Vaults / vault/gcp-sm - lint (pull_request) Successful in 1m21s
CI: Vaults / vault/azure-kv - test (pull_request) Successful in 59s
CI: Vaults / vault/aws-sm - test (pull_request) Successful in 1m36s
CI: Vaults / vault/1password - lockfile up to date (pull_request) Successful in 1m13s
CI: Vaults / vault/gcp-sm - test (pull_request) Successful in 1m25s
CI: Vaults / vault/azure-kv - lockfile up to date (pull_request) Successful in 46s
CI: Vaults / vault/aws-sm - lockfile up to date (pull_request) Successful in 1m12s
CI: Extensions / workflows/s3-bootstrap - check (pull_request) Successful in 1m2s
CI: Vaults / vault/gcp-sm - lockfile up to date (pull_request) Successful in 1m28s
CI / Actions Audit (pull_request) Successful in 1m36s
CI: Extensions / workflows/gcs-bootstrap - fmt (pull_request) Successful in 1m30s
CI: Datastores / Gate: Datastores (pull_request) Successful in 2s
CI: Extensions / workflows/gcs-bootstrap - test (pull_request) Successful in 50s
CI: Extensions / workflows/s3-bootstrap - fmt (pull_request) Successful in 1m27s
CI: Extensions / workflows/gcs-bootstrap - lint (pull_request) Successful in 1m1s
CI: Extensions / workflows/gcs-bootstrap - check (pull_request) Successful in 1m42s
CI: Extensions / workflows/s3-bootstrap - lint (pull_request) Successful in 1m13s
CI: Extensions / workflows/gcs-bootstrap - lockfile up to date (pull_request) Successful in 45s
CI: Extensions / workflows/s3-bootstrap - lockfile up to date (pull_request) Successful in 1m31s
CI: Extensions / workflows/s3-bootstrap - test (pull_request) Successful in 1m32s
CI: Models / Gate: Models (pull_request) Successful in 0s
CI: Vaults / Gate: Vaults (pull_request) Successful in 1s
CI: Extensions / Gate: Extensions (pull_request) Successful in 0s
CI / Dependency Audit (pull_request) Successful in 4m19s
CI / Gate: Audit (pull_request) Successful in 0s
CI: Reviews / Adversarial Code Review (pull_request) Successful in 3m29s
CI: Reviews / Claude Code Review (pull_request) Successful in 6m37s
CI: Reviews / Gate: Reviews (pull_request) Successful in 0s
52df0d10dd
- Remove unreachable runPreflight() from vault (dead code — not exposed
  through any typed interface; IMDS skip and error hints are the
  substantive vault improvements)
- Fix TimeoutError mislabeling in S3 preflightCredentials — derive error
  name from actual error instead of hardcoding
- Fix typo: codgenDir → codegenDir
- Add comments to enrichment IMDS inline blocks explaining why they
  can't import from the shared module

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

Adversarial Review

Critical / High

No critical or high severity issues found.

Medium

  1. datastore/s3/extensions/datastores/_lib/s3_client.ts:517 — Timeout detection via string matching is fragile

    The preflightCredentials wrapping logic detects timeouts by checking msg.includes("Credential preflight timed out"). If the message text in awsCredentials.ts is ever changed (it's a code-generated template), this string match silently breaks and the error gets wrapped with the wrong name (the probe error's .name instead of "TimeoutError").

    Breaking example: Someone edits awsCredentials.ts to say "Credential check timed out after…" — the S3 client now wraps the timeout as a generic error instead of TimeoutError, and callers that match on error.name === "TimeoutError" misclassify it.

    Suggested fix: Rather than string-matching the message, use a sentinel — either a custom error class (class PreflightTimeoutError extends Error) in aws_credentials.ts that the S3 client can instanceof-check, or set .name = "TimeoutError" on the error thrown in preflightCredentials so the catch site can check err.name === "TimeoutError" instead of parsing the message.

  2. codegen/aws/libGenerator.ts:95 (generated output) — source_profile cycle doesn't follow source_profile through the region chain, only through the same profile-level region lookup

    The resolveRegion function's source_profile loop sets current = section.get("source_profile") ?? "". When source_profile is absent, current becomes "", which is falsy, so the while (current && !visited.has(current)) loop exits. However, an empty string "" would also match visited.has("") as false on first pass — but the while check on current being falsy catches it first, so no infinite loop. This is correct but the empty-string fallback is a subtle implicit sentinel.

    Suggested fix: Use undefined instead of "" for the no-profile case: current = section.get("source_profile") — the Map.get returns undefined when absent, and while (current && ...) handles undefined identically to "". The ?? "" is dead weight.

Low

  1. codegen/aws/enrichments/rds-dbcluster/enrich.ts:25-32 — IMDS disable inside try block that catches broadly

    The IMDS-disable block is placed inside the existing try that catches (err: unknown) at line 46. If Deno.env.get or Deno.env.set threw (e.g., a permissions error in a sandboxed Deno), it would be silently swallowed by the generic catch, and the caller would get back the unenriched state with no indication that the credential setup failed. In practice, Deno.env.get/set don't throw under normal Deno permissions, so this is theoretical.

  2. codegen/shared/awsCredentials.ts:123 — Escaped unicode in generated template

    The template uses \\u2014 (literal backslash-u-2014) for the em-dash in the generated preflight timeout message. This is intentional (it becomes in the generated output, which JavaScript interprets as at runtime). Correct, but worth noting that if someone naively reads the template they might mistake it for a double-escape bug. No action needed.

  3. codegen/scripts/generate-aws-credentials.ts — No includePreflight passed explicitly

    The script calls generateAwsCredentialSource({ exported: true, includeFileHeader: true }) without specifying includePreflight. This defaults to true (line 20 in awsCredentials.ts: opts.includePreflight !== false). The hand-written extensions (S3 datastore, vault, workflows) get preflightCredentials included, which is correct. But the default-true convention is inverted from the exported default (also true via opts.exported !== false), so both happen to work, but it's easy to misread includePreflight as "opt-in" when it's actually "opt-out." No bug, just a readability note.

Verdict

PASS — This is a clean consolidation refactor. The credential utilities are correctly extracted into a single canonical source (codegen/shared/awsCredentials.ts) and distributed to hand-written extensions via codegen script and to generated models via libGenerator.ts. All call sites have been updated to the new 3-argument formatAwsCredentialHint signature. Re-exports from s3_client.ts and aws_sm_errors.ts preserve backward compatibility for existing importers. The preflightCredentials wrapping in s3_client.ts correctly handles both timeout and probe-failure paths. Tests have been updated to match the new signatures. The enrichment files correctly inline the IMDS-disable logic with an explanatory comment. The medium-severity string-matching concern (item 1) is a maintainability risk but not a correctness bug in the current state.

## Adversarial Review ### Critical / High No critical or high severity issues found. ### Medium 1. **`datastore/s3/extensions/datastores/_lib/s3_client.ts:517` — Timeout detection via string matching is fragile** The `preflightCredentials` wrapping logic detects timeouts by checking `msg.includes("Credential preflight timed out")`. If the message text in `awsCredentials.ts` is ever changed (it's a code-generated template), this string match silently breaks and the error gets wrapped with the wrong `name` (the probe error's `.name` instead of `"TimeoutError"`). **Breaking example:** Someone edits `awsCredentials.ts` to say `"Credential check timed out after…"` — the S3 client now wraps the timeout as a generic error instead of `TimeoutError`, and callers that match on `error.name === "TimeoutError"` misclassify it. **Suggested fix:** Rather than string-matching the message, use a sentinel — either a custom error class (`class PreflightTimeoutError extends Error`) in `aws_credentials.ts` that the S3 client can `instanceof`-check, or set `.name = "TimeoutError"` on the error thrown in `preflightCredentials` so the catch site can check `err.name === "TimeoutError"` instead of parsing the message. 2. **`codegen/aws/libGenerator.ts:95` (generated output) — `source_profile` cycle doesn't follow `source_profile` through the region chain, only through the same profile-level region lookup** The `resolveRegion` function's `source_profile` loop sets `current = section.get("source_profile") ?? ""`. When `source_profile` is absent, `current` becomes `""`, which is falsy, so the `while (current && !visited.has(current))` loop exits. However, an empty string `""` would also match `visited.has("")` as `false` on first pass — but the `while` check on `current` being falsy catches it first, so no infinite loop. This is correct but the empty-string fallback is a subtle implicit sentinel. **Suggested fix:** Use `undefined` instead of `""` for the no-profile case: `current = section.get("source_profile")` — the `Map.get` returns `undefined` when absent, and `while (current && ...)` handles `undefined` identically to `""`. The `?? ""` is dead weight. ### Low 1. **`codegen/aws/enrichments/rds-dbcluster/enrich.ts:25-32` — IMDS disable inside `try` block that catches broadly** The IMDS-disable block is placed inside the existing `try` that catches `(err: unknown)` at line 46. If `Deno.env.get` or `Deno.env.set` threw (e.g., a permissions error in a sandboxed Deno), it would be silently swallowed by the generic catch, and the caller would get back the unenriched `state` with no indication that the credential setup failed. In practice, `Deno.env.get/set` don't throw under normal Deno permissions, so this is theoretical. 2. **`codegen/shared/awsCredentials.ts:123` — Escaped unicode in generated template** The template uses `\\u2014` (literal backslash-u-2014) for the em-dash in the generated preflight timeout message. This is intentional (it becomes `—` in the generated output, which JavaScript interprets as `—` at runtime). Correct, but worth noting that if someone naively reads the template they might mistake it for a double-escape bug. No action needed. 3. **`codegen/scripts/generate-aws-credentials.ts` — No `includePreflight` passed explicitly** The script calls `generateAwsCredentialSource({ exported: true, includeFileHeader: true })` without specifying `includePreflight`. This defaults to `true` (line 20 in `awsCredentials.ts`: `opts.includePreflight !== false`). The hand-written extensions (S3 datastore, vault, workflows) get `preflightCredentials` included, which is correct. But the default-true convention is inverted from the `exported` default (also true via `opts.exported !== false`), so both happen to work, but it's easy to misread `includePreflight` as "opt-in" when it's actually "opt-out." No bug, just a readability note. ### Verdict **PASS** — This is a clean consolidation refactor. The credential utilities are correctly extracted into a single canonical source (`codegen/shared/awsCredentials.ts`) and distributed to hand-written extensions via codegen script and to generated models via `libGenerator.ts`. All call sites have been updated to the new 3-argument `formatAwsCredentialHint` signature. Re-exports from `s3_client.ts` and `aws_sm_errors.ts` preserve backward compatibility for existing importers. The `preflightCredentials` wrapping in `s3_client.ts` correctly handles both timeout and probe-failure paths. Tests have been updated to match the new signatures. The enrichment files correctly inline the IMDS-disable logic with an explanatory comment. The medium-severity string-matching concern (item 1) is a maintainability risk but not a correctness bug in the current state.
Author
Owner

Code Review

This PR consolidates AWS credential utilities (classifyAwsCredentialError, deriveAwsErrorCode, formatAwsCredentialHint, disableImdsIfOffEc2, preflightCredentials) into a single canonical source (codegen/shared/awsCredentials.ts) and distributes generated copies to hand-written extension directories. It also upgrades resolveRegion in libGenerator.ts with SSO session and source_profile chain support.

Blocking Issues

None.

Suggestions

  1. s3_client_test.ts — first test missing sanitizeResources: false explanation comment. The test "getObject: surfaces ETag from GetObject response" (line 93) has sanitizeResources: false but the block comment above it explains the test's purpose (TOCTOU fix), not why resource sanitization must be suppressed. All other tests in the file have the inline comment // The SDK's NodeHttpHandler keeps an HTTP agent pool alive across requests; resource sanitization would flag those as leaks… inside the test object. CLAUDE.md requires the comment to explain why. A one-liner like the pattern used in the other tests would close the gap.

  2. IMDS-disable logic is inlined in four enrichment files. bedrock-knowledgebase/methods.ts, cfn-stackset/methods.ts, rds-dbcluster/enrich.ts, and rds-dbcluster/list.enrich.ts each contain identical 8-line IMDS disable blocks (each with a comment explaining why). The comment is correct — enrichments run at codegen time and can't share the generated aws_credentials.ts. A tiny codegen/shared/awsEnrichUtils.ts exporting a callable disableImdsIfOffEc2 (distinct from the string-template version in awsCredentials.ts) would let all four files call a single import rather than duplicate the logic. Not required now, but worth tracking if a fifth enrichment lands.

  3. codegen/deno.json generate:aws-credentials task — no --allow-env. Other codegen run tasks include --allow-env. The new task omits it:

    "generate:aws-credentials": "deno run --allow-read --allow-write scripts/generate-aws-credentials.ts"
    

    The script and its transitive imports appear not to read any env vars (copyright header is a static string, the credential source is a template), so this is likely intentional. If generateCopyrightHeader() ever gains an env-var read the task will fail with an opaque permission error. Worth a quick check / doc comment confirming the omission is deliberate.

## Code Review This PR consolidates AWS credential utilities (`classifyAwsCredentialError`, `deriveAwsErrorCode`, `formatAwsCredentialHint`, `disableImdsIfOffEc2`, `preflightCredentials`) into a single canonical source (`codegen/shared/awsCredentials.ts`) and distributes generated copies to hand-written extension directories. It also upgrades `resolveRegion` in `libGenerator.ts` with SSO session and `source_profile` chain support. ### Blocking Issues None. ### Suggestions 1. **`s3_client_test.ts` — first test missing `sanitizeResources: false` explanation comment.** The test "getObject: surfaces ETag from GetObject response" (line 93) has `sanitizeResources: false` but the block comment above it explains the test's purpose (TOCTOU fix), not why resource sanitization must be suppressed. All other tests in the file have the inline comment `// The SDK's NodeHttpHandler keeps an HTTP agent pool alive across requests; resource sanitization would flag those as leaks…` inside the test object. CLAUDE.md requires the comment to explain *why*. A one-liner like the pattern used in the other tests would close the gap. 2. **IMDS-disable logic is inlined in four enrichment files.** `bedrock-knowledgebase/methods.ts`, `cfn-stackset/methods.ts`, `rds-dbcluster/enrich.ts`, and `rds-dbcluster/list.enrich.ts` each contain identical 8-line IMDS disable blocks (each with a comment explaining why). The comment is correct — enrichments run at codegen time and can't share the generated `aws_credentials.ts`. A tiny `codegen/shared/awsEnrichUtils.ts` exporting a callable `disableImdsIfOffEc2` (distinct from the string-template version in `awsCredentials.ts`) would let all four files call a single import rather than duplicate the logic. Not required now, but worth tracking if a fifth enrichment lands. 3. **`codegen/deno.json` `generate:aws-credentials` task — no `--allow-env`.** Other codegen run tasks include `--allow-env`. The new task omits it: ```json "generate:aws-credentials": "deno run --allow-read --allow-write scripts/generate-aws-credentials.ts" ``` The script and its transitive imports appear not to read any env vars (copyright header is a static string, the credential source is a template), so this is likely intentional. If `generateCopyrightHeader()` ever gains an env-var read the task will fail with an opaque permission error. Worth a quick check / doc comment confirming the omission is deliberate.
stack72 deleted branch swampbot/issue-1804-aws-credential-consolidation 2026-08-25 00:35:26 +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!224
No description provided.