Skip to content

[security] Non-admin broker leases can override privileged GCP project, network, image, tags, and service account fields #705

Description

@coygeek

[security] Non-admin broker leases can override privileged GCP project, network, image, tags, and service account fields

Summary

Authenticated non-admin broker users can create normal GCP leases, but the
coordinator currently lets those requests supply several GCP resource selectors
that are consumed by the broker's privileged GCP service account:
gcpProject, gcpImage, gcpNetwork, gcpSubnet, gcpTags, and
gcpServiceAccount.

This crosses Crabbox's supported coordinator boundary when a broker service
account is intentionally broad enough to manage more than one project, network,
or attachable service account. A non-admin user can ask the coordinator to
create a Compute Engine instance in a caller-selected project/network, use a
caller-selected boot image, attach caller-selected target tags, and attach a
caller-selected service account. That is materially different from a trusted
lease owner running commands on their own already-authorized lease: it lets the
lease-create request steer where and under which provider identity the
coordinator spends its GCP authority.

Crabbox already treats adjacent provider-resource pinning and native image
sources as admin-only. POST /v1/leases rejects hostId / awsMacHostID and
native snapshot or machine-image sources for non-admin callers, but it does not
apply an equivalent guard to the remaining GCP project, network, image, tag, or
service-account selectors.

Affected Components

  • Target commit: 6dba4afd1c5a4be0a780cf035d1b397e8982d478
  • Coordinator lease creation: worker/src/fleet.ts
  • Lease request normalization: worker/src/config.ts
  • GCP provider implementation: worker/src/gcp.ts
  • CLI broker request serialization: internal/cli/coordinator.go
  • Existing tests documenting the current permissive behavior:
    worker/test/fleet.test.ts

Relevant source evidence:

  • worker/src/fleet.ts:1654-1671 rejects non-admin native lease sources
    (gcpMachineImage / gcpSnapshot, plus AWS/Azure snapshot fields) and
    provider host pinning, but does not reject gcpProject, gcpImage,
    gcpNetwork, gcpSubnet, gcpTags, or gcpServiceAccount.
  • worker/src/config.ts:296-306 copies those GCP request fields into
    LeaseConfig without constraining them to broker-managed defaults.
  • worker/src/gcp.ts:171-176 constructs a GCPClient for
    config.gcpProject; worker/src/gcp.ts:266-317 uses
    config.gcpProject, config.gcpImage, config.gcpNetwork,
    config.gcpSubnet, config.gcpTags, and config.gcpServiceAccount when
    creating the Compute Engine instance.
  • worker/src/gcp.ts:481-499 creates the SSH firewall for
    config.gcpNetwork and caller-influenced target tags.
  • internal/cli/coordinator.go:869-907 serializes explicit GCP config fields
    into the coordinator lease-create body.
  • worker/test/fleet.test.ts:11270-11310 currently asserts that a non-admin
    request with gcpProject: "request-project" succeeds and records that
    project on the lease.

Attack Path

Attacker role: an authenticated non-admin user token, signed user token, or
shared operator token accepted by the broker.

Deployment prerequisites:

  • The coordinator has broker-side GCP credentials configured.
  • The broker's GCP service account has permissions broader than one strictly
    managed project/network/default service account, such as access to multiple
    projects or iam.serviceAccounts.actAs on service accounts the broker did
    not intend ordinary lease creators to select.

Steps:

  1. Send POST /v1/leases as a non-admin caller with provider: "gcp" and a
    body containing values such as:

    {
      "provider": "gcp",
      "gcpProject": "other-project",
      "gcpNetwork": "projects/other-project/global/networks/privileged-net",
      "gcpSubnet": "projects/other-project/regions/us-central1/subnetworks/privileged-subnet",
      "gcpImage": "projects/other-project/global/images/custom-image",
      "gcpTags": ["privileged-firewall-tag"],
      "gcpServiceAccount": "privileged-sa@other-project.iam.gserviceaccount.com",
      "sshPublicKey": "ssh-ed25519 AAAA..."
    }
  2. leaseConfig preserves those values, and createLease only checks whether
    the request uses native snapshot or machine-image sources or host pinning.
    The non-admin request proceeds.

  3. GCPProvider.prepareLeaseConfig preserves a caller-supplied gcpProject
    instead of replacing it with the broker's default project.

  4. GCPClient.createServer creates a Compute Engine instance with the
    caller-selected project, network/subnet, image, tags, and service account.

Result: where the coordinator service account has enough GCP IAM, a non-admin
Crabbox caller can spend broker authority in a project/network or with a
service account that should be controlled by broker administrators.

Impact

The impact is provider-authorization expansion from a non-admin Crabbox identity
to broker-controlled GCP authority:

  • A non-admin can launch an instance in a caller-selected GCP project and
    network/subnet visible to the coordinator service account.
  • A non-admin can attach a caller-selected GCP service account to the instance;
    if the broker service account is allowed to act as that identity, code running
    on the lease can use instance metadata credentials for that service account.
  • A non-admin can attach caller-selected network tags and cause Crabbox to
    create or use firewall policy for those tags.
  • A non-admin can select a caller-controlled image path, even though the
    stronger native GCP machine-image/snapshot lease sources are already
    admin-gated.

This is in scope under Crabbox's maintainer-authored security policy because it
can escalate a non-admin coordinator identity into provider capabilities that
are effectively administrative for the broker's GCP estate. It also affects the
policy's provider-resource boundary when Crabbox uses privileged cloud
credentials against resources not strongly constrained to broker-managed
defaults.

This is not a hostile multi-tenant isolation claim and does not require treating
repository configuration as untrusted code. The boundary is the documented
coordinator authentication and admin/non-admin split for brokered provider
actions.

Severity Assessment

CVSS Assessment

Metric v3.1 v4.0
Score 7.1 / 10.0 7.1 / 10.0
Severity High High
Vector CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:H/SI:L/SA:N
Calculator CVSS v3.1 Calculator CVSS v4.0 Calculator

The score assumes exploitation requires a real but deployment-dependent
precondition: the broker's GCP IAM must be broader than a single tightly scoped
managed project/default service account. Confidentiality impact is high because
attaching an unintended service account can expose that identity's cloud data
and APIs to commands running on the created instance. Integrity impact is low
because the attacker can steer resource creation and network tagging, while the
most severe provider-state mutation still depends on the selected service
account's downstream permissions.

Recommended Remediation

Add a coordinator-side allowlist or admin gate for brokered GCP resource
selectors before provider creation:

  • Reject non-admin gcpProject values that differ from the broker-managed
    project selected by CRABBOX_GCP_PROJECT / GCP_PROJECT_ID, unless the
    deployment explicitly allows per-user projects.
  • Reject non-admin gcpNetwork and gcpSubnet values that differ from
    broker-managed defaults or from an administrator-configured allowlist.
  • Reject non-admin gcpServiceAccount values that differ from the configured
    default service account or from an administrator-configured allowlist.
  • Reject non-admin gcpImage values unless they are known safe public defaults
    or administrator-promoted images.
  • Restrict non-admin gcpTags to broker-managed tags or an
    administrator-configured allowlist.

If per-user GCP project selection is an intended broker feature, make it
explicit and safe: bind allowed projects/service accounts to the authenticated
owner or org, record that policy in documentation, and test that a user cannot
select another owner's project or an arbitrary service account.

Add regression tests in worker/test/fleet.test.ts that cover non-admin
POST /v1/leases requests with each GCP selector set to a value outside the
broker-managed defaults or allowlist. The existing
records per-request GCP project without filling Worker-owned defaults test
should be updated to cover the intended allowlist or admin-only behavior.

Validation

Validation performed:

  • Read the maintainer-authored scope policy in crabbox/SECURITY.md and the
    operations guide in crabbox/docs/security.md.
  • Read the verification context for this run and confirmed no
    discovery-to-verification target drift: discovery and verification both use
    commit 6dba4afd1c5a4be0a780cf035d1b397e8982d478.
  • Reviewed the current lease-create source path and GCP provider sink in the
    isolated target copy.
  • Recomputed the CVSS scores with the local cvss Python package:
    CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:L/A:N scores 7.1 High and
    CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:H/SI:L/SA:N scores
    7.1 High.

Counterevidence considered:

  • docs/providers/gcp.md documents gcp.project, gcp.network,
    gcp.tags, and gcp.serviceAccount as configurable provider settings, and
    brokered GCP mode uses coordinator-side credentials.
  • worker/test/fleet.test.ts:11270-11310 intentionally records a non-admin
    per-request GCP project today.
  • These facts show the current behavior is deliberate enough to have test
    coverage, but they do not establish a safe authorization policy for arbitrary
    non-admin selection of projects, networks, tags, images, or service accounts
    backed by broker credentials. The maintainer security policy still treats
    non-admin-to-admin escalation and unsafe provider-resource mutation as
    reportable boundaries.

No live GCP exploitation was attempted. The source proof is sufficient for the
authorization flaw; concrete exploitability and blast radius depend on the
broker's GCP IAM scope and should be assessed per deployment.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Emergency: data loss, security bypass, crash loop, or unusable core runtime.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:securityThis issue is about security boundaries, credentials, authz, sandboxing, or sensitive data.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions