Skip to content

fix(coderd): disallow POSTing a workspace build on a deleted workspace#20584

Merged
johnstcn merged 1 commit into
mainfrom
cj/no-post-workspacebuild-deleted-workspace
Oct 30, 2025
Merged

fix(coderd): disallow POSTing a workspace build on a deleted workspace#20584
johnstcn merged 1 commit into
mainfrom
cj/no-post-workspacebuild-deleted-workspace

Conversation

@johnstcn
Copy link
Copy Markdown
Member

@johnstcn johnstcn commented Oct 30, 2025

Problem

It looks like, for a good long while, we have allows POSTing a workspace build on a deleted workspace. As far as I can tell, this goes way back (to at least 2.21, but maybe earlier).

Credit to @DanielleMaywood

Impact

  • You can "start" a deleted workspace if you would have previously had access to it (either due to your role or owning the workspace) and know its ID.
  • It won't show up in the UI.
  • Resources will be created (most likely re-created due to the previous DELETE transition).
  • The agent won't connect so you won't be able to access the resources via coder.

Proposed Solution

This PR adds a check on /api/v2/workspacebuilds to disallow creating a START or STOP build if the workspace is deleted. I elected to allow a DELETE build however.

Remediation

I haven't added any automatic remediation, but below is a (Claude-coded) SQL query that should show affected workspaces and associated resources:

WITH successful_deletes AS (
    SELECT wb.workspace_id, wb.created_at as delete_time, wb.id as delete_build_id
    FROM workspace_builds wb
    JOIN provisioner_jobs pj ON wb.job_id = pj.id
    WHERE wb.transition = 'delete'
      AND pj.completed_at IS NOT NULL
      AND pj.error IS NULL
  )
  SELECT
    w.id as workspace_id,
    u.username || '/' || w.name as workspace,
    wb.id as violating_build_id,
    wb.transition as violating_transition,
    wb.created_at as violating_build_time,
    sd.delete_time,
    sd.delete_build_id,
    wb.created_at - sd.delete_time as time_after_delete,
    COALESCE(
      string_agg(
        DISTINCT wr.name || ' (' || wr.type || ')',
        ', ' ORDER BY wr.name || ' (' || wr.type || ')'
      ),
      'No resources'
    ) as workspace_resources
  FROM workspace_builds wb
  JOIN successful_deletes sd ON wb.workspace_id = sd.workspace_id
  JOIN workspaces w ON wb.workspace_id = w.id
  JOIN users u ON w.owner_id = u.id
  LEFT JOIN workspace_resources wr ON wb.job_id = wr.job_id
    AND wr.type NOT LIKE 'coder_%'
  WHERE wb.transition IN ('start', 'stop')
    AND wb.created_at > sd.delete_time
  GROUP BY
    w.id, w.name, u.username, wb.id, wb.transition,
    wb.created_at, sd.delete_time, sd.delete_build_id
  ORDER BY wb.created_at DESC;

@johnstcn johnstcn self-assigned this Oct 30, 2025
@deansheather
Copy link
Copy Markdown
Member

I think allowing extra delete transitions on a deleted workspace is fine but I don't think we should elevate it/support it in the CLI/UI (something Cian mentioned he was thinking of potentially adding in DMs).

  1. A successful delete transition will do terraform destroy which deletes all resources anyway, so any additional destroys (even on a different template version) will do nothing.
  2. If a workspace was orphaned, it was built with an empty state, so it's effectively the same as point 1.

There are no other ways for a workspace to become deleted AFAIK.

Obviously, if someone uploaded a custom state on their build the story is a bit different, which is why I think it's fine to leave it in the API. But I don't think anyone will ever need to do it so I don't think we should add it to the CLI/UI.

@johnstcn johnstcn merged commit 3801701 into main Oct 30, 2025
37 checks passed
@johnstcn johnstcn deleted the cj/no-post-workspacebuild-deleted-workspace branch October 30, 2025 13:32
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 30, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants