fix(hosting): never serve stale placeholder config.json after deploy#174
fix(hosting): never serve stale placeholder config.json after deploy#174fossamagna wants to merge 6 commits into
Conversation
…s-devtools-labs#173) The build-time placeholder config.json (`{"_placeholder":true}`) was uploaded with the 1-year mutable cache-control (`public, s-maxage=31536000, max-age=0, must-revalidate`), and the post-deploy CloudFront invalidation targeted `/.blocks-sandbox/*`. The skew-protection viewer-request function rewrites the URI to `/builds/<buildId>/.blocks-sandbox/config.json` before the cache lookup, so the real cache key lives under `/builds/…` and the invalidation never matches it. An edge that cached the placeholder during the deploy window could keep serving it for up to a year — `getApiUrl()` then throws and every client API call fails. - Primary: register `.blocks-sandbox/config.json` as a no-cache path so the placeholder is uploaded with `no-cache, no-store, must-revalidate` and edges never cache it long-term. The real config is still deployed separately with its own short `max-age=60`. - Defense-in-depth: also invalidate the post-rewrite cache key `/builds/<buildId>/.blocks-sandbox/*` so a post-deploy invalidation actually clears any edge entry at its real key.
Superseded by re-post with inline comments
Superseded — diagnosing line anchoring
ahmedhamouda78
left a comment
There was a problem hiding this comment.
Approving. Correctly fixes both root-cause defects behind #173: (1) the placeholder config.json fell into the mutable-asset tier (public, s-maxage=31536000) and could be edge-cached for up to a year — now registered in noCachePaths and uploaded with no-cache, no-store, must-revalidate; and (2) the post-deploy invalidation targeted /.blocks-sandbox/* but the skew-protection CloudFront function rewrites the URI to /builds//.blocks-sandbox/config.json before cache lookup, so the invalidation never matched — now adds /builds/${buildId}/.blocks-sandbox/* while correctly retaining /.blocks-sandbox/* for non-rewrite cases. Scoping invalidation to the current buildId only is the right call (cookie-pinned users keep their config). Clean TDD, no security concerns, zero drift from main.
Non-blocking suggestions below.
| // short `max-age=60`. | ||
| const configNoCachePath = `${BLOCKS_SANDBOX_DIR}/config.json`; | ||
| const existingNoCachePaths = manifest.staticAssets.noCachePaths ?? []; | ||
| manifest.staticAssets.noCachePaths = existingNoCachePaths.includes(configNoCachePath) |
There was a problem hiding this comment.
The noCachePaths registration runs unconditionally (no api guard), which is correct — but both new tests always pass MOCK_API. A static-only test (no api prop) would guard against a future regression where the registration is accidentally moved inside the if (props.api) block, since the placeholder is always written.
| const deployments = bucketDeployments(stack); | ||
| // The BlocksConfigDeployment is the one targeting the .blocks-sandbox | ||
| // key prefix with an invalidation configured. | ||
| const configDeploy = deployments.find( |
There was a problem hiding this comment.
The configDeploy lookup uses a structural heuristic (DestinationBucketKeyPrefix.endsWith('.blocks-sandbox') && Array.isArray(DistributionPaths)). If CDK renames internal properties the heuristic silently fails to find the deployment and the test throws on assert.ok(undefined, ...) rather than a clear failure. Prefer Template.fromStack + Match.objectLike for CDK-upgrade-resilient assertions. (The .blocks-sandbox regex escaping is correct.)
| JSON.stringify({ _placeholder: true }), | ||
| ); | ||
|
|
||
| // ── 5a. Mark config.json as a no-cache path ───────────────────── |
There was a problem hiding this comment.
nit: the 8-line comment narrates the full #173 bug history that's already in the commit message. Keep the non-obvious invariant inline (config.json is mutable and must not inherit the content-hashed-asset s-maxage) and move the forensics to the commit body.
| }); | ||
| }); | ||
|
|
||
| // ── #173: stale placeholder config.json never served long-term ── |
There was a problem hiding this comment.
nit: the describe(...) block name already conveys this; section-divider comments inside test bodies add noise.
- Add a static-only (no `api`) test asserting the placeholder is registered as a no-cache path — guards against a regression that makes the registration depend on `props.api`. - Rewrite the invalidation-path test to assert via `Template.hasResourceProperties` + `Match` so a future CDK property rename fails loudly instead of silently skipping the assertion. - Trim the step-5a comment to the non-obvious invariant (config.json is mutable and must not inherit the content-hashed-asset s-maxage); the bug forensics already live in the fix commit body. - Drop a redundant section-divider comment inside the test file.
|
@ahmedhamouda78
Full |
Problem
Issue #, if available: #173
After a normal deploy, a fraction of browsers permanently receive the build-time placeholder
/.blocks-sandbox/config.json({"_placeholder":true}) from CloudFront instead of the real{"apiUrl":…}. The@aws-blocks/core/clientgetApiUrl()treats a body withoutapiUrlas invalid and throws, so every client API call (includingauthApi.getAuthState()) rejects — the login page is rendered permanently unusable for affected users.Two independent defects combine:
config.jsonis written into the static asset dir but is not registered as a no-cache path, so it falls into the mutable asset tier and is uploaded withpublic, s-maxage=31536000, max-age=0, must-revalidate. Any edge that caches the placeholder during the deploy window keeps serving it for up to a year./builds/<buildId>/.blocks-sandbox/config.jsonbefore the cache lookup, so the real cache key lives under/builds/…. TheBucketDeploymentinvalidated only/.blocks-sandbox/*, which never matches the rewritten key — so the invalidation is a no-op forconfig.json.Changes
.blocks-sandbox/config.jsoninmanifest.staticAssets.noCachePathsso the placeholder is uploaded withno-cache, no-store, must-revalidateinstead of the 1-year mutable cache-control. The edge no longer caches the placeholder long-term. The real config is still deployed separately with its own shortmax-age=60, so runtime performance is unchanged. (This reuses the existingnoCachePathsmechanism, whose own doc names.blocks-sandbox/config.jsonas the intended use case.)BucketDeployment'sdistributionPathsto also include the post-rewrite key/builds/<buildId>/.blocks-sandbox/*, so a post-deploy invalidation actually clears any edge entry at its real cache key.Validation
packages/core/src/hosting.test.ts(config.json stale-placeholder guard (#173)):config.jsonis uploaded withno-cache, no-store, must-revalidateand that no deployment appliess-maxage=31536000to it;distributionPathsinvalidates the post-rewrite cache key/builds/<id>/.blocks-sandbox/*.@aws-blocks/coresuite green (582 tests); no new lint findings on the changed lines.Checklist
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.