cloudflare / cloudflare/cloudflare-os

Code-only blueprint updates unexpectedly rewrite binding metadata

Open
#147 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
9.9k
Forks
1.2k
Avg merge
1d 20h
Merged PRs (30d)
107

Description

## Suggested severity

High (S2): the dedicated code-only update path can silently change a published blueprint's connection contract, or fail for an unrelated binding-annotation error.

## Summary

The `Overseer.updateBlueprint()` API documents `updateCode` and `updateBindings` as independent options. However, calling `updateBlueprint(id, { updateCode: true })` currently calls `collectBindingMetadata()` and assigns its result to `record.metadata.bindings`.

The Workshop's **Update code** action invokes exactly this code-only option, so the normal UI workflow is affected.

## Deterministic reproduction on unpatched `main`

Using commit `8b08672050e6957987a87b5f9cab2801ba89b42c`:

1. Create a blueprint record whose stored binding metadata is `OLD_API`.
2. Make the source gadget's current binding metadata return `NEW_API`.
3. Call `updateBlueprint("blueprint-1", { updateCode: true })`.
4. Inspect `record.metadata.bindings` and the `collectBindingMetadata()` call count.

### Actual result

`collectBindingMetadata()` is called once and the stored metadata changes from `OLD_API` to `NEW_API`, even though `updateBindings` was not requested.

If collection throws because a current binding annotation is incomplete, the code-only update rejects before snapshotting the code. The controlled regression test failed on unpatched `main` with `promise rejected "Error: invalid current binding annotation" instead of resolving`.

### Expected result

A code-only update should update the code snapshot/version and preserve the blueprint's existing binding metadata. Binding metadata should be read and replaced only when `updateBindings: true` is present.

## Impact

- A user choosing **Update code** can unintentionally change the inputs/connections required by an already published blueprint.
- An invalid current binding annotation can block an otherwise valid code-only update.
- The behavior contradicts the public RPC contract, which assigns binding refresh to the independent `updateBindings` option.

## Root cause

`updateBlueprint()` enters one shared `if (options.updateCode || options.updateBindings)` block and unconditionally collects and assigns bindings before checking `options.updateCode`.

## Proposed fix

Keep the shared gadget-ID resolution, but guard collection and assignment with `if (options.updateBindings)`. Cover code-only, bindings-only, and combined updates.

Contributor guide

Open the contributing guide

Research direction

Start at Overseer.updateBlueprint() and trace the shared updateCode/updateBindings path through collectBindingMetadata(). Use the deterministic reproduction and controlled regression test described in the issue to verify code-only, bindings-only, and combined updates. Done means code-only updates preserve stored binding metadata and still resolve when current binding annotations are invalid, while binding refresh occurs only when requested.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend-api-design
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.