HeyPuter/puter · error · HttpError

forbidden

forbidden

Error message

Cannot delete a protected subdomain

What it means

The site row exists and is owned by the caller, but its `protected` flag is set, so the controller refuses deletion with HTTP 403. Protected subdomains are reserved system sites that must not be removed through the public delete endpoint. This is a deliberate guard, not a bug.

Source

Thrown at src/backend/controllers/hosting/HostingController.js:85

                const { site_uuid } = req.body ?? {};
                if (!site_uuid || typeof site_uuid !== 'string') {
                    throw new HttpError(400, 'Missing or invalid `site_uuid`', {
                        legacyCode: 'bad_request',
                    });
                }

                const row = await this.subdomainStore.getByUuid(site_uuid, {
                    userId: req.actor.user.id,
                });
                if (!row) {
                    throw new HttpError(
                        404,
                        'Site not found or not owned by you',
                        { legacyCode: 'not_found' },
                    );
                }
                if (row.protected) {
                    throw new HttpError(
                        403,
                        'Cannot delete a protected subdomain',
                        { legacyCode: 'forbidden' },
                    );
                }

                await this.subdomainStore.deleteByUuid(site_uuid, {
                    userId: req.actor.user.id,
                });

                res.json({});
            },
        );
    }

    onServerStart() {}
    onServerPrepareShutdown() {}
    onServerShutdown() {}

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Do not attempt to delete protected subdomains via the public API — they are intentionally locked.
  2. If protection is no longer needed, unprotect the site through the admin/DB layer first, then retry.
  3. Filter protected sites out of any bulk-delete or cleanup script.
  4. Surface a clear message to the user that this site cannot be removed.

Example fix

// before — delete without checking
await api.call('delete-site', { site_uuid: site.uuid });

// after — skip protected sites in bulk operations
if (site.protected) {
  console.log(`Skipping protected site: ${site.subdomain}`);
  continue;
}
await api.call('delete-site', { site_uuid: site.uuid });
Defensive patterns

Strategy: validation

Validate before calling

// Check the protected flag from the site list before attempting delete
const sites = await api.call('hosting/sites');
const site = sites.find(s => s.uuid === site_uuid);
if (site?.protected) {
  alert('This site is protected and cannot be deleted.');
  return;
}
await api.call('delete-site', { site_uuid });

Type guard

/** @typedef {{ uuid: string, protected?: boolean }} SiteRow */
/** @param {SiteRow} s @returns {boolean} */
function isDeletable(s) {
  return !s.protected;
}

Try / catch

try {
  await api.call('delete-site', { site_uuid });
} catch (e) {
  if (e.code === 'forbidden') {
    console.error('This site is protected and cannot be deleted.');
  } else throw e;
}

Prevention

When it happens

Trigger: Calling `POST /delete-site` with the UUID of a protected subdomain (e.g., a default/system-hosted site). The store returns the row, but `row.protected` is truthy.

Common situations: Trying to clean up a default site created during provisioning; an admin marked a site protected and a user attempts deletion; automated cleanup scripts that iterate all sites without checking the flag.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/2739c0984503ef54. Report an issue: GitHub.