Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
syntax = "proto3";

package sentry_protos.billing.v1.services.engagement.v1;

message AssignBalanceContractRequest {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this actually do?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah you're right this is pretty bad. i have a PR to refactor in getsentry.

after that's merged, i'll come back and fix these protobuf messages

optional uint64 previous_contract_id = 1;
uint64 contract_id = 2;
}

message AssignBalanceContractResponse {
bool assigned = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syntax = "proto3";

package sentry_protos.billing.v1.services.engagement.v1;

message GetContractBalanceRequest {
uint64 contract_id = 1;
}

message GetContractBalanceResponse {
uint64 balance_cents = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
syntax = "proto3";

package sentry_protos.billing.v1.services.engagement.v1;

message GetUnassignedBalancesRequest {}

message GetUnassignedBalancesResponse {
repeated uint64 previous_contract_ids = 1;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto3";

package sentry_protos.billing.v1.services.engagement.v1;

message RolloverMonetaryGrantsRequest {
uint64 previous_contract_id = 1;
uint64 next_contract_id = 2;
}

message RolloverMonetaryGrantsResponse {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
syntax = "proto3";

package sentry_protos.billing.v1.services.engagement.v1;

message StageRolloverBalanceRequest {
uint64 contract_id = 1;
// Amount used in the previous contract that will be subtracted when carrying over the balance.
uint64 amount_used_cents = 2;
}

message StageRolloverBalanceResponse {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
syntax = "proto3";

package sentry_protos.billing.v1.services.engagement.v1;

message StageRolloverMonetaryGrantsRequest {
uint64 previous_contract_id = 1;
optional uint64 balance_cents_used = 2;
}

message StageRolloverMonetaryGrantsResponse {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
syntax = "proto3";

package sentry_protos.billing.v1.services.engagement.v1;

message UpsertContractBalanceRequest {
uint64 contract_id = 1;
int64 amount_cents = 2;
// There may not be a previous contract if this is the first contract for the organization.
optional uint64 previous_contract_id = 3;
}

message UpsertContractBalanceResponse {
uint64 new_balance_cents = 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The response field new_balance_cents is a uint64, but the request's amount_cents is a signed int64. This can cause silent data corruption if a negative balance is returned.
Severity: HIGH

Suggested Fix

To prevent silent data corruption from negative balances, change the response field new_balance_cents from uint64 to int64. This ensures that both the request and response can correctly represent positive and negative monetary values.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location:
proto/sentry_protos/billing/v1/services/engagement/v1/endpoint_upsert_contract_balance.proto#L13

Potential issue: There is a type mismatch between the request field `amount_cents` which
is an `int64` and the response field `new_balance_cents` which is a `uint64`. If the
server-side logic calculates a negative balance, Protobuf's encoding will convert this
negative `int64` into a very large positive `uint64` via two's complement
representation. This results in silent data corruption, where the client receives an
incorrect and astronomically large balance without any error being raised. This can
happen if `amount_cents` is used for deductions that result in a negative total balance.

Also affects:

  • rust/src/sentry_protos.billing.v1.services.engagement.v1.rs:567~567

Did we get this right? 👍 / 👎 to inform future reviews.

}
72 changes: 72 additions & 0 deletions rust/src/sentry_protos.billing.v1.services.engagement.v1.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
// This file is @generated by prost-build.
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct AssignBalanceContractRequest {
#[prost(uint64, optional, tag = "1")]
pub previous_contract_id: ::core::option::Option<u64>,
#[prost(uint64, tag = "2")]
pub contract_id: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct AssignBalanceContractResponse {
#[prost(bool, tag = "1")]
pub assigned: bool,
}
/// How a Grant was created.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
Expand Down Expand Up @@ -308,6 +320,16 @@ pub struct GetActiveTrialsResponse {
#[prost(message, repeated, tag = "1")]
pub trials: ::prost::alloc::vec::Vec<Trial>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct GetContractBalanceRequest {
#[prost(uint64, tag = "1")]
pub contract_id: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct GetContractBalanceResponse {
#[prost(uint64, tag = "1")]
pub balance_cents: u64,
}
/// A grant with counter-grant chains collapsed into a single effective amount.
/// Returned in drain priority order (end_date ASC, effective_amount ASC, grant_id ASC).
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
Expand Down Expand Up @@ -432,6 +454,13 @@ impl EffectiveMonetarySource {
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct GetUnassignedBalancesRequest {}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct GetUnassignedBalancesResponse {
#[prost(uint64, repeated, tag = "1")]
pub previous_contract_ids: ::prost::alloc::vec::Vec<u64>,
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct EffectiveUnitGrant {
#[prost(string, tag = "1")]
Expand Down Expand Up @@ -467,6 +496,34 @@ pub struct GetUnitGrantsResponse {
#[prost(message, repeated, tag = "1")]
pub grants: ::prost::alloc::vec::Vec<EffectiveUnitGrant>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct RolloverMonetaryGrantsRequest {
#[prost(uint64, tag = "1")]
pub previous_contract_id: u64,
#[prost(uint64, tag = "2")]
pub next_contract_id: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct RolloverMonetaryGrantsResponse {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct StageRolloverBalanceRequest {
#[prost(uint64, tag = "1")]
pub contract_id: u64,
/// Amount used in the previous contract that will be subtracted when carrying over the balance.
#[prost(uint64, tag = "2")]
pub amount_used_cents: u64,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct StageRolloverBalanceResponse {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct StageRolloverMonetaryGrantsRequest {
#[prost(uint64, tag = "1")]
pub previous_contract_id: u64,
#[prost(uint64, optional, tag = "2")]
pub balance_cents_used: ::core::option::Option<u64>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct StageRolloverMonetaryGrantsResponse {}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct StartTrialRequest {
#[prost(uint64, tag = "1")]
Expand Down Expand Up @@ -494,3 +551,18 @@ pub mod start_trial_request {
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct StartTrialResponse {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct UpsertContractBalanceRequest {
#[prost(uint64, tag = "1")]
pub contract_id: u64,
#[prost(int64, tag = "2")]
pub amount_cents: i64,
/// There may not be a previous contract if this is the first contract for the organization.
#[prost(uint64, optional, tag = "3")]
pub previous_contract_id: ::core::option::Option<u64>,
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, ::prost::Message)]
pub struct UpsertContractBalanceResponse {
#[prost(uint64, tag = "1")]
pub new_balance_cents: u64,
}
Loading