Skip to content

Latest commit

 

History

History
275 lines (171 loc) · 20.5 KB

File metadata and controls

275 lines (171 loc) · 20.5 KB

v1.4.0 Security Advisory Registration Checklist

This checklist prepares the GHSA registrations for the v1.4.0 package release. Publish the advisories only after the v1.4.0 release workflow has completed and the patched npm tarballs are visible. Drafts can be created at any time; publication is the outward-facing step that must be gated on the patched tarballs actually being installable.

GitHub's repository security advisory API is documented at https://docs.github.com/en/enterprise-cloud@latest/rest/security-advisories/repository-advisories. Check the current schema before scripting advisory creation; the table below is the maintainer source of truth for titles, affected package ranges, patched versions, and draft body text.

Advisory Candidates

The candidate set is the six findings from the 2026-05-19 Codex export that affect downstream consumers of the published library tarballs. Repo-internal findings (workflow gating, e2e sweep, drift classifier internals) are documented in CHANGELOG.md's ### Security section but do not warrant GHSAs — they don't affect anyone who only installs the npm package.

# Source Draft title Affected product Vulnerable range Patched Severity CWE
1 Codex 2026-05-19, PR #178 IAM-role policy checks bypassed when the role trusts multiple OIDC providers @hulumi/policies < 1.4.0 1.4.0 High CWE-697
2 Codex 2026-05-19, PR #178 Policy packs bypassed by a forged Pulumi-URN logical name @hulumi/policies < 1.4.0 1.4.0 High CWE-693
3 Codex 2026-05-19, PR #178 HULUMI-H5 bypass via decoy sibling resources targeting a different bucket @hulumi/policies < 1.4.0 1.4.0 High CWE-284
4 Codex 2026-05-19, PR #178 AccountFoundation audit-delivery S3 bucket could be silently weakened @hulumi/baseline < 1.4.0 1.4.0 High CWE-1059
5 Codex 2026-05-19, PR #178 Drift classifier fails open on adapter errors and over-promotes Mixed verdicts @hulumi/drift < 1.4.0 1.4.0 Medium CWE-755
6 Codex 2026-05-19, PR #178 AccountFoundation reuse paths silently downgrade GuardDuty / Security Hub posture @hulumi/baseline < 1.4.0 1.4.0 Medium CWE-693

Per-advisory draft bodies

Each block below is the body text to paste into the GitHub Security Advisory form. Keep it consistent with the CHANGELOG ### Security section so the cross-reference is unambiguous.

GHSA-1: IAM-role policy checks bypassed when the role trusts multiple OIDC providers

Affected: @hulumi/policies < 1.4.0Fixed in: 1.4.0Severity: High — CWE-697 (Incorrect Comparison)

Summary

AWS IAM trust policies can list more than one federated identity provider — for example, a role that accepts BOTH GitHub Actions OIDC and Google's OIDC. The G_OIDC_1 and G_OIDC_2 policy rules are supposed to flag IAM roles whose GitHub-OIDC trust is too permissive (e.g. wildcard sub: conditions that would let any branch or any pull request assume the role).

The bug: when the role's Principal.Federated field was a JSON array of multiple providers, the rules failed to recognise that GitHub Actions was one of them. The providers list was coerced into a single comma-joined string, the matcher only looked at the start, and the GitHub OIDC hostname was lost in the join. Both rules concluded "this isn't a GitHub-OIDC role" and skipped the wildcard check.

Impact

A trust policy that listed the real GitHub OIDC provider ARN alongside any second provider would slip past both detectors. Consumers using HulumiHardeningPack or HulumiGithubHardeningPack could ship an IAM role with wildcard sub: conditions (allowing untrusted PRs from forks to assume the role) while their policy validation reported the stack as compliant. The G_OIDC_2 detector also failed to mark such roles for the cluster-admin / AdministratorAccess blast-radius check.

Patches

Upgrade to @hulumi/policies@1.4.0. The shared GitHub-OIDC-provider matcher now correctly walks lists of providers — if any element of the list is the real GitHub OIDC ARN, the role is treated as GitHub-OIDC-assumable and the wildcard / blast-radius checks apply.

Workarounds

None reliable — upgrade is the fix.

References

  • PR #178 (Cluster A); regression tests at packages/policies/tests/github/{g-oidc-2,github-oidc-issuer}.test.ts.

GHSA-2: Policy packs bypassed by a forged Pulumi-URN logical name

Affected: @hulumi/policies < 1.4.0Fixed in: 1.4.0Severity: High — CWE-693 (Protection Mechanism Failure)

Summary

Pulumi gives every cloud resource a structured URN that includes the resource's type chain (hulumi:baseline:aws:SecureBucket$aws:s3/bucketV2:BucketV2) and the logical name the developer freely chose (anything after the final ::). Several Hulumi policy rules used the URN to grant exemptions — for example, "if this raw bucket is a child of SecureBucket, skip the raw-bucket rule because the parent component handles hardening."

The bug: the rules looked for a substring like hulumi:baseline:aws:SecureBucket$ anywhere in the URN. That substring can also appear in the developer-controlled logical-name portion. A developer (or compromised PR) could simply name a raw resource so its logical name carried the trusted substring, and every rule that used this check would treat the resource as if it were inside the trusted parent and skip its hardening check.

Codex reported this for DEPLOY_GOV_1; the same anti-pattern existed in five more packs (unreported but identically exploitable): AWS H4/H5 sibling lookups, GitHub H1, GitHub H2, Cloudflare CF_DNS_1, Cloudflare CF_DNSSEC_1, and (advisory-level) CIS v5 §2.1.1 + §2.1.5.

Impact

Consumers using @hulumi/policies could ship raw aws:s3:Bucket, github:Repository, cloudflare:Zone, cloudflare:DnsRecord, and similar resources that bypassed mandatory hardening checks by naming themselves with a trusted substring. Every affected rule appeared to pass while the resource had none of the expected defaults.

Patches

Upgrade to @hulumi/policies@1.4.0. A new shared helper at packages/policies/src/urn.ts parses Pulumi URNs structurally and only looks for the trusted parent-type token inside the URN's type-chain segment — never inside the developer-controlled logical name. All six prior call sites have been migrated to it.

Workarounds

None reliable — a local lint that rejects logical names containing $ would catch the trivial form of the spoof but not crafted variants.

References

  • PR #178 (Cluster B); the URN-anchoring refactor and per-pack spoof-vector regression tests in packages/policies/tests/.

GHSA-3: HULUMI-H5 bypass via decoy sibling resources targeting a different bucket

Affected: @hulumi/policies < 1.4.0Fixed in: 1.4.0Severity: High — CWE-284 (Improper Access Control)

Summary

HULUMI-H1 forbids raw aws:s3:Bucket outside of Hulumi's SecureBucket component, with one exemption: a raw bucket that's a child of a SecureBucket is allowed because the component is responsible for the hardening. HULUMI-H5 is the defence-in-depth check that closes the H1 exemption — for any raw bucket claiming it, H5 verifies the five hardening sibling resources a real SecureBucket always emits (public-access block, SSE-KMS, ownership controls, versioning, TLS-only bucket policy) are actually present.

The bug: H5 only checked the siblings' types. It never verified that those siblings actually applied to the bucket being exempted. A consumer (or compromised PR) could pair an unhardened raw bucket with five hardening sibling resources whose bucket property pointed at a completely different bucket, and H5 would report no violation while the actual bucket shipped with zero hardened defaults.

Impact

Consumers using HulumiHardeningPack could ship a raw S3 bucket with no public-access block, no SSE-KMS, no ownership controls, no versioning, and no TLS-only bucket policy — while the policy pack reported the stack as compliant.

Patches

Upgrade to @hulumi/policies@1.4.0. The H5 sibling check now requires both (a) the sibling to share the same parent SecureBucket instance via the anchored URN helper from GHSA-2, AND (b) the sibling's bucket property — or, for the bucket policy, its Resource ARN list — to reference the exempted bucket explicitly. Five decoy siblings pointing at a different bucket no longer count.

Workarounds

None — the exemption itself is the mechanism, so the value-binding check is the only fix.

References

  • PR #178 (Cluster B); decoy-sibling regression cases in packages/policies/tests/hulumi-hardening-pack.test.ts. Supersedes PR #175, which had addressed the value-binding half but on a stale base.

GHSA-4: AccountFoundation audit-delivery S3 bucket could be silently weakened

Affected: @hulumi/baseline < 1.4.0Fixed in: 1.4.0Severity: High — CWE-1059 (Insufficient Technical Documentation / Behavioral Inconsistency)

Summary

The S3 bucket that AccountFoundation creates to receive CloudTrail and AWS Config audit logs is meant to be tamper-resistant — if someone with delete access can erase from it, the forensic trail is gone. There were three independent ways the protection could be silently weakened:

  1. No Write-Once-Read-Many on the startup-hardened audit bucket. The startup-hardened tier hard-coded objectLock: false on the audit bucket. (The reason was real — bucket-wide Object Lock blocks an AWS Config write-then-delete probe — but the fix was a sledgehammer that disabled WORM for all objects, not just the probe key.)
  2. forceDestroy was forwarded to the audit bucket. Nothing prevented a downstream stack from setting logBucketForceDestroy: true, which made pulumi destroy purge every audit-log object on teardown.
  3. Sandbox tier dropped everything. Sandbox-tier AccountFoundation created its audit bucket with tier: "sandbox", which skipped Object Lock, server access logging, AND the CloudTrail-Lake EventDataStore (the independent immutable mirror) — leaving sandbox accounts with no audit immutability at all.

Impact

Consumers using AccountFoundation could ship an AWS account whose CloudTrail / Config audit logs were deletable by any S3-delete-capable principal — while believing the startup-hardened tier guaranteed tamper-resistance. Sandbox-tier deployments had no audit immutability at all (defects 1 and 3 compounded).

Patches

Upgrade to @hulumi/baseline@1.4.0. A single invariant in SecureBucket now fires whenever the bucket actually backs CloudTrail/Config delivery (i.e. awsServiceLogDelivery.cloudTrail === true || .config === true):

  • refuses forceDestroy: true on the startup-hardened tier;
  • emits the CloudTrail-Lake EventDataStore regardless of parent tier (so sandbox accounts regain immutable audit capture);
  • adds a deny-s3:DeleteObject* bucket-policy statement scoped to the CloudTrail and Config history/snapshot prefixes (a retention floor on the audit objects). The deny excludes the AWS Config ConfigWritabilityCheckFile probe key so Config's write-then-delete still works, which is why bucket-wide Object Lock is intentionally NOT re-enabled.

Workarounds

Replicating audit logs out-of-account to an Object-Locked archive bucket partially mitigates while you upgrade.

References

  • PR #178 (Cluster C); see CHANGELOG ### Migration for the forceDestroy behaviour change.

GHSA-5: Drift classifier fails open on adapter errors and over-promotes Mixed verdicts

Affected: @hulumi/drift < 1.4.0Fixed in: 1.4.0Severity: Medium — CWE-755 (Improper Handling of Exceptional Conditions)

Summary

@hulumi/drift runs four adapters that each ask a different question about whether a resource has drifted (Pulumi-state diff, provider-version change, CloudTrail event, etc.). A classifier combines the adapters' answers into a verdict like None / none, ConsoleBreakGlass / high, or Mixed / high, and caches the verdict for 6 hours by default.

Two related bugs from one root cause — the classifier only read each adapter's detected: true/false field and ignored whether the adapter itself succeeded:

  1. Cached "all clear" on adapter failure. When an adapter failed (e.g. transient network error from the Automation API), the classifier read detected: false, concluded "no drift", and cached the verdict as None / none for 6 hours. A single transient failure could mask real console-break-glass mutations for the rest of the window.
  2. Mixed verdicts without real evidence. The Mixed / high and ConsoleBreakGlass / high verdicts (incident severity) could fire on the "the CloudTrail probe round-tripped successfully" signal rather than actual evidence that anything had been changed via the console. Normal provider-API churn could end up falsely escalated to incident severity.

Impact

Consumers running drift detection in CI / cron could see transient adapter failures silently cached as "all clear" — masking real attacks for up to six hours — or see ordinary provider-version churn falsely promoted to incident severity. Either way, the verdict source was unreliable for downstream incident workflows that gate on it.

Patches

Upgrade to @hulumi/drift@1.4.0. Classifier-only fix (the TLA+-verified 6-row verdict matrix is byte-identical):

  • adapter failures now fail closed to Unknown / low, and degraded verdicts are not written to the cache;
  • the Mixed / ConsoleBreakGlass promotion now requires real CloudTrail event evidence rather than probe liveness.

Workarounds

Setting options.minConfidence: "medium" on the classifier call prevents the degraded None / none from being cached (it doesn't meet the threshold), partially mitigating case (1). No workaround for case (2).

References

  • PR #178 (Cluster D); regression tests in packages/drift/tests/classifier-fail-closed.test.ts.

GHSA-6: AccountFoundation reuse paths silently downgrade GuardDuty / Security Hub posture

Affected: @hulumi/baseline < 1.4.0Fixed in: 1.4.0Severity: Medium — CWE-693 (Protection Mechanism Failure)

Summary

AccountFoundation can either create AWS detective services (GuardDuty for threat detection, Security Hub for compliance dashboards) or reuse pre-existing ones via opt-in flags. The reuse paths just imported the existing resources and reported success — they never checked whether the existing services were actually doing their job.

  1. GuardDuty reuse. If the existing detector was suspended, or set to the slower 6-hour publishing cadence instead of the baseline 15-minute one, or otherwise misconfigured — Hulumi never noticed. The deployment succeeded with a misleadingly-positive guardDutyDetectorId output as if the baseline were active.
  2. Security Hub reuse. Although the account import was read-only, Hulumi unconditionally created the CIS / NIST StandardsSubscription resources with default delete behaviour. Pulumi then treated those subscriptions as its own — a later pulumi destroy of the stack would call BatchDisableStandards, unsubscribing the account from CIS / NIST compliance monitoring even on accounts that had those subscriptions before Hulumi ever ran.

Impact

Consumers using AccountFoundation's reuse mode could:

  • ship deployments that appeared to enable a detective baseline but actually weren't (case 1), or
  • accidentally turn off CIS / NIST compliance monitoring on an existing account just by destroying a Hulumi stack (case 2 — no malicious intent needed; a normal stack teardown was enough).

Patches

Upgrade to @hulumi/baseline@1.4.0.

  • GuardDuty reuse now asserts the imported detector is ENABLED with findingPublishingFrequency: FIFTEEN_MINUTES. Wrong posture fails the deploy at preview time.
  • Security Hub reuse creates the CIS / NIST StandardsSubscription resources with retainOnDelete: true, so destroying a reused stack no longer unsubscribes the account.

Net-new (non-reuse) deployments are unchanged.

Workarounds

Don't reuse pre-existing detective services with AccountFoundation until you've upgraded. If reuse is unavoidable, manually verify detector posture out-of-band.

References

  • PR #178 (Cluster G); regression tests in packages/baseline/tests/guardduty-reuse-posture.test.ts and packages/baseline/tests/securityhub-reuse-retain.test.ts.

Publication Steps

  1. Confirm .github/workflows/release.yml succeeded for tag v1.4.0 and the atomic six-package publish completed.
  2. Confirm all six npm tarballs are visible: @hulumi/baseline, @hulumi/policies, @hulumi/drift, @hulumi/k8s-baseline, @hulumi/cloudflare-baseline, @hulumi/platform-patterns.
  3. Run pnpm run release:verify-attestations against the published tarballs.
  4. Create draft advisories in GitHub → Security → Advisories → New draft security advisory. For each row in the table above:
    • Ecosystem: npm.
    • Package name: as listed.
    • Affected versions: < 1.4.0.
    • Patched versions: 1.4.0.
    • CWE: as listed (use the lookup).
    • Severity: as listed.
    • Description: paste the per-advisory body above (this file is the source of truth).
  5. Do NOT request CVEs during initial draft. Decide on CVE request when publishing each advisory — the GHSA flow handles MITRE coordination if you opt in.
  6. Publish advisories only after the npm artifacts are live and verifiable. The draft → publish step is the public-disclosure moment; consumers cannot patch until they can pnpm update @hulumi/* to 1.4.0.
  7. After publication, GitHub will emit Dependabot alerts to consumers within minutes to hours. Confirm at least one alert appears (use a test consumer repo if convenient).

Repo-internal findings NOT receiving a GHSA

The following findings were closed by PR #178 / PR #179 but do not warrant consumer-facing advisories because they affect repo-internal infrastructure (workflows, drift internals, integration tests) rather than the published library tarballs. They are documented in CHANGELOG.md's ### Security section.

Finding Why no GHSA
CloudWatch executor cross-account/region binding Reconciler executor internals; not consumed by anyone who only installs the package without registering the executor against their own account.
Security-singleton inference for GuardDuty / Security Hub Same as above — drift reconciler internals.
E2E sweep over-broad name-affix deletion Lives in packages/baseline/tests/integration/, not in the published tarball.
e2e-cleanup.yml missing maintainer-review gate Repo-internal workflow; no library surface.
Weekly-integration / e2e-cleanup S3 state-bucket ownership not verified Repo-internal workflow + docs.
Documented "SHA-pinned chart digest" claim in @hulumi/k8s-baseline/README.md Doc fix; no runtime bypass in the consumed code. README correction lands in 1.4.0.

If a downstream consumer later argues any of these should have been GHSAs, file an amendment to this checklist and treat the absence as a documentation gap, not a disclosure failure.