hcengineering/platform · error · PlatformError

Forbidden

Forbidden

Error message

Forbidden

What it means

Forbidden is thrown by getPendingWorkspace when the decoded token's extra service claim is not exactly 'workspace'. This internal operation is reserved for the workspace service; any other service (e.g. 'tool', 'account', user tokens) is rejected before the pending-workspace query runs. It is a service-to-service authorization check, not a user permission failure.

Source

Thrown at server/account/src/serviceOperations.ts:273

 * on every progress update.
 * If no progress is reported for the workspace during this time,
 * it will become available again to be processed by another executor.
 */
export async function getPendingWorkspace (
  ctx: MeasureContext,
  db: AccountDB,
  branding: Branding | null,
  token: string,
  params: {
    region: string
    version: Data<Version>
    operation: WorkspaceOperation
  }
): Promise<WorkspaceInfoWithStatus | undefined> {
  const { region, version, operation } = params
  const { extra } = decodeTokenVerbose(ctx, token)
  if (extra?.service !== 'workspace') {
    throw new PlatformError(new Status(Severity.ERROR, platform.status.Forbidden, {}))
  }

  const wsLivenessDays = getMetadata(accountPlugin.metadata.WsLivenessDays)
  const wsLivenessMs = wsLivenessDays !== undefined ? wsLivenessDays * 24 * 60 * 60 * 1000 : undefined

  const result = await db.getPendingWorkspace(region, version, operation, processingTimeoutMs, wsLivenessMs)

  if (result != null) {
    ctx.info('getPendingWorkspace', {
      workspaceId: result.uuid,
      workspaceName: result.name,
      dataId: result.dataId,
      mode: result.status.mode,
      operation,
      region,
      major: result.status.versionMajor,
      minor: result.status.versionMinor,
      patch: result.status.versionPatch,

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Obtain/sign the token with extra.service === 'workspace' (use the workspace service's own credentials/keys).
  2. Check token issuance config so the service claim is set correctly for internal calls (not defaulted to another service).
  3. If you are another service, call the operation through the workspace service instead of directly.
  4. Decode the token locally (decodeTokenVerbose) and assert extra.service before making the call to fail fast.

Example fix

// before
const token = generateToken(ctx, 'account', { service: 'tool' })
await accountClient.getPendingWorkspace(ctx, token, params)

// after
const token = generateServiceToken(ctx, 'account', { service: 'workspace' }) // must be the workspace service
await accountClient.getPendingWorkspace(ctx, token, params)
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check the service claim before calling
const { extra } = decodeTokenVerbose(ctx, token)
if (extra?.service !== 'workspace') {
  throw new Error('getPendingWorkspace requires a workspace-service token')
}

Type guard

function isWorkspaceServiceToken(extra: TokenExtra | undefined): extra is TokenExtra & { service: 'workspace' } {
  return extra?.service === 'workspace'
}

Try / catch

try {
  return await getPendingWorkspace(params)
} catch (err) {
  if (err instanceof PlatformError && err.status.code === platform.status.Forbidden) {
    throw new Error('Token service must be "workspace"; re-mint token with correct service claim')
  }
  throw err
}

Prevention

When it happens

Trigger: Invoking getPendingWorkspace with a token whose extra.service != 'workspace' — e.g. using a tool-service token, an unauthenticated/user token with no service claim, or a token issued for the wrong service.

Common situations: Misconfigured service-to-service auth where the workspace worker reuses a generic service token; a new microservice calling workspace maintenance APIs with its own service claim; upgrading/rotating tokens and losing the 'workspace' service identifier; calling the endpoint from client-side code with a user token.

Understand the failure class

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/d6dfd9bbec344713. Report an issue: GitHub.