[security] AWS direct stop can terminate non-Crabbox EC2 instances by raw instance ID
Summary
The direct AWS provider accepts a raw EC2 instance ID in crabbox stop --provider aws --id i-..., resolves it with DescribeInstances, and passes the described instance into the release path without verifying that it is a Crabbox-owned lease. A local operator or automation path that can invoke Crabbox with configured AWS credentials can therefore make Crabbox call TerminateInstances for any describable instance in the configured AWS account/region set, including instances that lack the canonical crabbox=true, created_by=crabbox, provider=aws, lease, and slug tags.
This is in scope under the maintainer-authored SECURITY.md boundary because the policy explicitly calls out destructive provider actions against resources Crabbox cannot strongly identify as its own. It does not depend on hostile multi-tenant isolation; the crossed boundary is Crabbox's provider-resource ownership check before a destructive cloud API call.
Affected Components
- Checked commit:
6dba4afd1c5a4be0a780cf035d1b397e8982d478
- Component: CLI direct AWS provider lifecycle
- Affected files and lines:
internal/cli/run.go:2914-3105
internal/providers/aws/backend.go:129-149
internal/providers/aws/backend.go:200-208
internal/providers/aws/backend.go:347-359
internal/cli/aws.go:595-615
internal/cli/provider_backend.go:680-682
Attack Path
Attacker role:
local operator or local automation with access to configured AWS provider credentials
Prerequisites:
- The local Crabbox configuration selects
provider=aws or the caller supplies --provider aws.
- The configured AWS credentials can describe and terminate a target EC2 instance.
- The caller knows or can guess a target EC2 instance ID in the configured AWS region set.
- No internal controller expected-identity flags are supplied; ordinary CLI stop leaves the expected identity empty, so the generic identity validator does not enforce an immutable lease/resource match.
Steps:
- Choose an EC2 instance ID that is not a Crabbox lease and does not carry Crabbox ownership tags.
- Run
crabbox stop --provider aws --id i-0123456789abcdef0 from a local checkout or automation context using the configured AWS credentials.
App.stop resolves the ID with ReleaseOnly: true; because no internal expected identity flags were supplied, ValidateLeaseTargetProviderIdentity returns without checking the resolved target.
- The AWS backend raw-ID branch calls
GetServer for the i-* ID and returns the described server without checking Crabbox ownership tags, lease ID shape, slug, or name.
ReleaseLease calls deleteServer, which calls AWSClient.DeleteServer, issuing TerminateInstances for the raw instance ID.
Expected result:
Crabbox terminates an EC2 instance that it cannot strongly identify as a Crabbox-owned lease.
Control/dataflow:
local --id i-* argument
-> App.stop ResolveRequest{ReleaseOnly: true}
-> awsLeaseBackend.Resolve raw i-* branch
-> AWSClient.GetServer without Crabbox tag filter
-> awsLeaseBackend.ReleaseLease/deleteServer without ownership validation
-> AWSClient.DeleteServer / EC2 TerminateInstances
-> non-Crabbox EC2 instance deletion
Impact
This crosses the documented provider-resource safety boundary: Crabbox can perform a destructive provider action against a cloud resource it cannot strongly identify as its own. In an AWS account that mixes Crabbox leases with unrelated EC2 workloads, an operator mistake, malicious local automation, or a compromised local workflow using Crabbox's configured provider route can terminate non-Crabbox instances. The direct AWS inventory cleanup path is narrower because it lists only instances with tag:crabbox=true, while the raw-ID stop path bypasses that inventory filter and has no equivalent release-time ownership gate.
Severity Assessment
CVSS Assessment
| Metric |
v3.1 |
v4.0 |
| Score |
5.5 / 10.0 |
6.8 / 10.0 |
| Severity |
Medium |
Medium |
| Vector |
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H |
CVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N |
| Calculator |
CVSS v3.1 Calculator |
CVSS v4.0 Calculator |
The attack is local and requires a caller that can use the configured AWS route, so this is not a remote authentication bypass. The impact is high availability loss for the victim EC2 instance because Crabbox reaches a provider termination API without first proving the resource belongs to a Crabbox lease. Confidentiality and integrity are not directly affected by the observed path.
Recommended Remediation
Add a shared AWS ownership validation gate before any destructive AWS direct-provider operation. The gate should reject a server unless it has a canonical Crabbox lease identity: crabbox=true, created_by=crabbox, provider=aws, a valid lease id, a non-empty slug, and preferably the expected Name or provider-key relationship used by created leases. Apply it in both the raw i-* resolve branch when ReleaseOnly is true and in deleteServer as defense in depth. If raw EC2 IDs are intentionally supported for adoption or manual cleanup, require an explicit adoption flow that verifies and writes Crabbox ownership metadata before allowing release.
Validation
Validation method:
source review with local CVSS scoring
Evidence and counterevidence:
internal/cli/run.go:3067-3093 resolves the user-supplied ID with ReleaseOnly: true, then calls ReleaseLease; ValidateLeaseTargetProviderIdentity is a no-op when no internal expected identity flags were provided.
internal/providers/aws/backend.go:129-149 has a raw i-* branch that calls GetServer and returns a LeaseTarget without checking crabbox, created_by, provider, lease ID shape, slug, or name.
internal/cli/aws.go:595-605 implements GetServer with DescribeInstances by instance ID, unlike ListCrabboxServers in internal/cli/aws.go:147-152, which filters on tag:crabbox=true.
internal/providers/aws/backend.go:200-208 calls deleteServer from ReleaseLease, and internal/providers/aws/backend.go:347-359 calls client.DeleteServer without an AWS ownership check.
internal/cli/aws.go:610-615 maps DeleteServer to EC2 TerminateInstances.
internal/cli/provider_labels.go:10-31 shows the canonical tags Crabbox-created direct leases receive, and internal/cli/aws.go:1112-1129 shows that arbitrary EC2 tags are copied into the Server.Labels map returned by GetServer.
- Counterevidence considered: direct AWS cleanup uses
ListCrabboxServers, and adjacent providers such as Hetzner and GCP have explicit ownership or canonical-label gates before destructive paths. Those controls do not protect the raw AWS stop path.
Suggested regression coverage or verification:
internal/providers/aws/backend_test.go: add a test where Resolve(ReleaseOnly: true, ID: "i-foreign") returns a fake instance without Crabbox labels and assert that release is rejected before DeleteServer is called.
internal/providers/aws/backend_test.go: add a positive test proving canonical Crabbox-tagged AWS leases still release and clean up provider keys.
go test ./internal/providers/aws ./internal/cli
go test ./...
Remaining uncertainty:
No live AWS call was made during discovery; the finding is based on current source flow and local scoring. Verifier can confirm with a fake AWS client regression test without touching real cloud resources.
[security] AWS direct stop can terminate non-Crabbox EC2 instances by raw instance ID
Summary
The direct AWS provider accepts a raw EC2 instance ID in
crabbox stop --provider aws --id i-..., resolves it withDescribeInstances, and passes the described instance into the release path without verifying that it is a Crabbox-owned lease. A local operator or automation path that can invoke Crabbox with configured AWS credentials can therefore make Crabbox callTerminateInstancesfor any describable instance in the configured AWS account/region set, including instances that lack the canonicalcrabbox=true,created_by=crabbox,provider=aws, lease, and slug tags.This is in scope under the maintainer-authored
SECURITY.mdboundary because the policy explicitly calls out destructive provider actions against resources Crabbox cannot strongly identify as its own. It does not depend on hostile multi-tenant isolation; the crossed boundary is Crabbox's provider-resource ownership check before a destructive cloud API call.Affected Components
6dba4afd1c5a4be0a780cf035d1b397e8982d478internal/cli/run.go:2914-3105internal/providers/aws/backend.go:129-149internal/providers/aws/backend.go:200-208internal/providers/aws/backend.go:347-359internal/cli/aws.go:595-615internal/cli/provider_backend.go:680-682Attack Path
Attacker role:
local operator or local automation with access to configured AWS provider credentials
Prerequisites:
provider=awsor the caller supplies--provider aws.Steps:
crabbox stop --provider aws --id i-0123456789abcdef0from a local checkout or automation context using the configured AWS credentials.App.stopresolves the ID withReleaseOnly: true; because no internal expected identity flags were supplied,ValidateLeaseTargetProviderIdentityreturns without checking the resolved target.GetServerfor thei-*ID and returns the described server without checking Crabbox ownership tags, lease ID shape, slug, or name.ReleaseLeasecallsdeleteServer, which callsAWSClient.DeleteServer, issuingTerminateInstancesfor the raw instance ID.Expected result:
Crabbox terminates an EC2 instance that it cannot strongly identify as a Crabbox-owned lease.
Control/dataflow:
Impact
This crosses the documented provider-resource safety boundary: Crabbox can perform a destructive provider action against a cloud resource it cannot strongly identify as its own. In an AWS account that mixes Crabbox leases with unrelated EC2 workloads, an operator mistake, malicious local automation, or a compromised local workflow using Crabbox's configured provider route can terminate non-Crabbox instances. The direct AWS inventory cleanup path is narrower because it lists only instances with
tag:crabbox=true, while the raw-ID stop path bypasses that inventory filter and has no equivalent release-time ownership gate.Severity Assessment
CVSS Assessment
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:HCVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:NThe attack is local and requires a caller that can use the configured AWS route, so this is not a remote authentication bypass. The impact is high availability loss for the victim EC2 instance because Crabbox reaches a provider termination API without first proving the resource belongs to a Crabbox lease. Confidentiality and integrity are not directly affected by the observed path.
Recommended Remediation
Add a shared AWS ownership validation gate before any destructive AWS direct-provider operation. The gate should reject a server unless it has a canonical Crabbox lease identity:
crabbox=true,created_by=crabbox,provider=aws, a valid lease id, a non-empty slug, and preferably the expectedNameor provider-key relationship used by created leases. Apply it in both the rawi-*resolve branch whenReleaseOnlyis true and indeleteServeras defense in depth. If raw EC2 IDs are intentionally supported for adoption or manual cleanup, require an explicit adoption flow that verifies and writes Crabbox ownership metadata before allowing release.Validation
Validation method:
source review with local CVSS scoring
Evidence and counterevidence:
internal/cli/run.go:3067-3093resolves the user-supplied ID withReleaseOnly: true, then callsReleaseLease;ValidateLeaseTargetProviderIdentityis a no-op when no internal expected identity flags were provided.internal/providers/aws/backend.go:129-149has a rawi-*branch that callsGetServerand returns aLeaseTargetwithout checkingcrabbox,created_by,provider, lease ID shape, slug, or name.internal/cli/aws.go:595-605implementsGetServerwithDescribeInstancesby instance ID, unlikeListCrabboxServersininternal/cli/aws.go:147-152, which filters ontag:crabbox=true.internal/providers/aws/backend.go:200-208callsdeleteServerfromReleaseLease, andinternal/providers/aws/backend.go:347-359callsclient.DeleteServerwithout an AWS ownership check.internal/cli/aws.go:610-615mapsDeleteServerto EC2TerminateInstances.internal/cli/provider_labels.go:10-31shows the canonical tags Crabbox-created direct leases receive, andinternal/cli/aws.go:1112-1129shows that arbitrary EC2 tags are copied into theServer.Labelsmap returned byGetServer.ListCrabboxServers, and adjacent providers such as Hetzner and GCP have explicit ownership or canonical-label gates before destructive paths. Those controls do not protect the raw AWS stop path.Suggested regression coverage or verification:
internal/providers/aws/backend_test.go: add a test whereResolve(ReleaseOnly: true, ID: "i-foreign")returns a fake instance without Crabbox labels and assert that release is rejected beforeDeleteServeris called.internal/providers/aws/backend_test.go: add a positive test proving canonical Crabbox-tagged AWS leases still release and clean up provider keys.Remaining uncertainty:
No live AWS call was made during discovery; the finding is based on current source flow and local scoring. Verifier can confirm with a fake AWS client regression test without touching real cloud resources.