Skip to content

refactor(api): prevent integer overflow in query parameters - #1180

Open
sinchubhat wants to merge 2 commits into
mainfrom
jira-CM-357
Open

refactor(api): prevent integer overflow in query parameters#1180
sinchubhat wants to merge 2 commits into
mainfrom
jira-CM-357

Conversation

@sinchubhat

@sinchubhat sinchubhat commented Aug 7, 2026

Copy link
Copy Markdown
Contributor
  • Add validation to prevent integer overflow in query parameters (top, skip, count).
  • Returns 400 instead of 500 on invalid input.

Test

Get token

TOKEN=$(curl -sk https://localhost:8181/api/v1/authorize -H "Content-Type: application/json" -d '{"username":"<username>","password":"<password>"}' | jq -r '.token')

Test 1: Integer overflow on $top (should return 400, NOT 500)

curl -sk -i "https://localhost:8181/api/v1/devices?\$top=99999999999999999999999999999" \
  -H "Authorization: Bearer $TOKEN"

Output:

HTTP/2 400 
content-type: application/json; charset=utf-8
content-length: 163
date: Fri, 07 Aug 2026 04:55:25 GMT

{"error":"invalid $top parameter: parameter value exceeds maximum allowed range","message":"invalid $top parameter: parameter value exceeds maximum allowed range"}

Test 2: Integer overflow on $skip (should return 400, NOT 500)

curl -sk -i "https://localhost:8181/api/v1/devices?\$skip=28866823837974031616722743261592341250719431" \
  -H "Authorization: Bearer $TOKEN"

Output:

HTTP/2 400 
content-type: application/json; charset=utf-8
content-length: 165
date: Fri, 07 Aug 2026 04:56:10 GMT

{"error":"invalid $skip parameter: parameter value exceeds maximum allowed range","message":"invalid $skip parameter: parameter value exceeds maximum allowed range"}

Test 3: Negative value (should return 400)

curl -sk -i "https://localhost:8181/api/v1/devices?\$top=-10" \
  -H "Authorization: Bearer $TOKEN"

Output:

HTTP/2 400 
content-type: application/json; charset=utf-8
content-length: 133
date: Fri, 07 Aug 2026 04:56:42 GMT

{"error":"invalid $top parameter: parameter must be non-negative","message":"invalid $top parameter: parameter must be non-negative"}

Test 4: Exceeds max allowed (should return 400)

curl -sk -i "https://localhost:8181/api/v1/devices?\$top=50000" \
  -H "Authorization: Bearer $TOKEN"

Output:

HTTP/2 400 
content-type: application/json; charset=utf-8
content-length: 169
date: Fri, 07 Aug 2026 04:57:10 GMT

{"error":"invalid $top parameter: parameter exceeds maximum allowed value of 10000","message":"invalid $top parameter: parameter exceeds maximum allowed value of 10000"}

Test 5: Valid request (should return 200 with data)

curl -sk -i "https://localhost:8181/api/v1/devices?\$top=10&\$skip=0&\$count=true" \
  -H "Authorization: Bearer $TOKEN"

Output:

HTTP/2 200 
content-type: application/json; charset=utf-8
content-length: 1733
date: Fri, 07 Aug 2026 04:57:37 GMT

{"totalCount":2,"data":[{"connectionStatus":false,"mpsInstance":"","hostname":"<host-ip-addr>","guid":"<guid>","mpsusername":"","tags":null,"tenantId":"","friendlyName":"MININT-67OALSL","dnsSuffix":"","deviceInfo":{"fwVersion":"20.0.5","fwBuild":"1628","fwSku":"16392","discovered":false,"currentMode":"not activated","features":"AMT Pro Corporate","ipAddress":"<ip-addr>","lastSynced":"2026-08-03T07:43:34.3493246Z","lmsInstalled":true,"lmsVersion":"2542.0.5.0","amtEnabledInBIOS":true,"meInterfaceVersion":"2542.0.52.0","dhcpEnabled":true,"osName":"windows","osVersion":"10.0.26100.8875 Build 26100.8875","osDistro":"Microsoft Windows 11 Enterprise 24H2","cpuModel":"Intel(R) Core(TM) Ultra 5 238V","osIpAddress":"<ip-addr>","ethernetAdapterCount":1,"monitorConnected":true},"username":"<username>","password":"","mpspassword":"","mebxpassword":"","useTLS":true,"allowSelfSigned":true,"certHash":""},{"connectionStatus":false,"mpsInstance":"","hostname":"<host-ip-addr>","guid":"<guid>","mpsusername":"","tags":null,"tenantId":"","friendlyName":"","dnsSuffix":"","deviceInfo":{"fwVersion":"","fwBuild":"","fwSku":"","discovered":true,"firstDiscovered":"2026-07-27T05:37:35.973182717Z","currentMode":"","features":"","ipAddress":"0.0.0.0","lastSynced":"2026-07-27T05:37:35.973182717Z","lmsInstalled":false,"meInterfaceVersion":"6.8.0-111-generic","osName":"linux","osVersion":"6.8.0-111-generic","osDistro":"Ubuntu 22.04.5 LTS","cpuModel":"Intel(R) Core(TM) i7-8559U CPU @ 2.70GHz","osIpAddress":"<ip-addr>","ethernetAdapterCount":1,"monitorConnected":true},"username":"","password":"","mpspassword":"","mebxpassword":"","useTLS":false,"allowSelfSigned":false,"certHash":""}]}

@sinchubhat
sinchubhat force-pushed the jira-CM-357 branch 2 times, most recently from 67f9a1a to 21ed5c2 Compare August 7, 2026 04:13
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.46281% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.24%. Comparing base (2296f65) to head (9be8c38).

Files with missing lines Patch % Lines
internal/controller/httpapi/v1/error.go 60.86% 26 Missing and 1 partial ⚠️
internal/controller/httpapi/v1/devices.go 0.00% 2 Missing and 2 partials ⚠️
internal/controller/httpapi/v1/auditlog.go 0.00% 1 Missing and 1 partial ⚠️
internal/controller/httpapi/v1/ciraconfigs.go 33.33% 1 Missing and 1 partial ⚠️
internal/controller/httpapi/v1/domains.go 0.00% 1 Missing and 1 partial ⚠️
internal/controller/httpapi/v1/ieee8021xconfigs.go 0.00% 1 Missing and 1 partial ⚠️
internal/controller/httpapi/v1/profiles.go 0.00% 1 Missing and 1 partial ⚠️
internal/controller/httpapi/v1/wificonfigs.go 0.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1180      +/-   ##
==========================================
+ Coverage   50.09%   50.24%   +0.14%     
==========================================
  Files         146      147       +1     
  Lines       13552    13614      +62     
==========================================
+ Hits         6789     6840      +51     
- Misses       6171     6181      +10     
- Partials      592      593       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sinchubhat
sinchubhat force-pushed the jira-CM-357 branch 2 times, most recently from 0f52511 to 1a9abe7 Compare August 7, 2026 05:00
@sinchubhat
sinchubhat requested a lite review from Copilot August 7, 2026 05:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the v1 HTTP API’s OData-style query parameters ($top, $skip, $count) by adding explicit parsing/validation to prevent integer overflow and invalid values from bubbling into 500 responses, aligning invalid input handling with consistent 400 Bad Request behavior.

Changes:

  • Introduces OData.BindAndValidate with safe parsing and bounds checks for $top, $skip, and $count.
  • Switches multiple v1 list endpoints from ShouldBindQuery to the new validation path.
  • Adds focused unit tests covering overflow, negatives, type errors, and bounds.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
internal/controller/httpapi/v1/odata.go Adds safe OData query binding/validation and supporting error types/constants.
internal/controller/httpapi/v1/odata_test.go Adds unit tests for OData parsing/validation edge cases.
internal/controller/httpapi/v1/devices.go Uses the new validation path for device listing and related handlers.
internal/controller/httpapi/v1/ciraconfigs.go Uses the new validation path for CIRA config listing.
internal/controller/httpapi/v1/domains.go Uses the new validation path for domain listing.
internal/controller/httpapi/v1/profiles.go Uses the new validation path for profile listing.
internal/controller/httpapi/v1/wificonfigs.go Uses the new validation path for wireless config listing.
internal/controller/httpapi/v1/ieee8021xconfigs.go Uses the new validation path for IEEE 802.1x config listing.
internal/controller/httpapi/v1/auditlog.go Uses the new validation path for event log listing.

Comment thread internal/controller/httpapi/v1/wificonfigs.go
Comment thread internal/controller/httpapi/v1/profiles.go
Comment thread internal/controller/httpapi/v1/domains.go
Comment thread internal/controller/httpapi/v1/ieee8021xconfigs.go
Comment thread internal/controller/httpapi/v1/auditlog.go
Comment thread internal/controller/httpapi/v1/devices.go
Comment thread internal/controller/httpapi/v1/odata.go Outdated
Comment thread internal/controller/httpapi/v1/ciraconfigs.go Outdated
@sinchubhat
sinchubhat force-pushed the jira-CM-357 branch 4 times, most recently from 8ffcf8c to faaf51c Compare August 7, 2026 05:57
@sinchubhat
sinchubhat requested a lite review from Copilot August 7, 2026 06:04

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

internal/controller/httpapi/v1/odata_test.go:19

  • gin.SetMode modifies global Gin state. Because this test calls t.Parallel() before gin.SetMode, the SetMode call runs concurrently with other parallel tests and can introduce data races / cross-test interference under -race.
func TestOData_BindAndValidate(t *testing.T) {
	t.Parallel()

	gin.SetMode(gin.TestMode)

internal/controller/httpapi/v1/error.go:46

  • When OData validation errors are wrapped in dto.NotValidError (via ErrValidation*.Wrap), this first validation case matches via errors.Is/As and returns err.Error() directly, bypassing notValidErrorHandle. That can surface less user-friendly/internal context and makes responses inconsistent across handlers.
	switch {
	case errors.As(err, &odataValidationErr) || errors.Is(err, ErrInvalidInteger) ||
		errors.Is(err, ErrExceedsMaxRange) || errors.Is(err, ErrNegativeValue) || errors.Is(err, ErrInvalidBoolean):
		msg := err.Error()
		c.AbortWithStatusJSON(http.StatusBadRequest, response{Error: msg, Message: msg})

@sinchubhat
sinchubhat marked this pull request as ready for review August 7, 2026 06:11
@sinchubhat
sinchubhat requested a review from a team as a code owner August 7, 2026 06:11
@sudhir-intc
sudhir-intc requested a lite review from Copilot August 10, 2026 05:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

internal/controller/httpapi/v1/auditlog.go:95

  • This route wraps OData query validation errors with ErrValidationProfile, which labels the error source as "ProfileAPI". That’s misleading for audit log/event log requests and makes debugging/log triage harder. Consider either using an audit-log-specific validation wrapper or just passing the BindAndValidate error directly to ErrorResponse (as the devices handlers do).
	if err := odata.BindAndValidate(c); err != nil {
		validationErr := ErrValidationProfile.Wrap("get", "BindAndValidate", err)
		ErrorResponse(c, validationErr)

internal/controller/httpapi/v1/error.go:46

  • Because dto.NotValidError unwraps to the underlying OData validation error, the first switch case will match (errors.Is/As) and respond with err.Error(). For NotValidError this uses consoleerrors.InternalError.Error(), which includes internal file/function/call details in the HTTP 400 response instead of the intended friendly message. Handle dto.NotValidError (and other wrapper validation types) before checking the underlying OData sentinels so wrapped errors don’t leak internal context.
	switch {
	case errors.As(err, &odataValidationErr) || errors.Is(err, ErrInvalidInteger) ||
		errors.Is(err, ErrExceedsMaxRange) || errors.Is(err, ErrNegativeValue) || errors.Is(err, ErrInvalidBoolean):
		msg := err.Error()
		c.AbortWithStatusJSON(http.StatusBadRequest, response{Error: msg, Message: msg})

* Add validation to prevent integer overflow in query parameters (top, skip, count).
* Returns 400 instead of 500 on invalid input.
@sinchubhat sinchubhat changed the title fix(api): prevent integer overflow in query parameters refactor(api): prevent integer overflow in query parameters Aug 13, 2026

const (
// MaxPageSize is the maximum allowed value for $top parameter.
MaxPageSize = 10000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what is the source of this number, does it not look very high ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggest to keep it at 500

// MaxPageSize is the maximum allowed value for $top parameter.
MaxPageSize = 10000
// MaxSkipValue is the maximum allowed value for $skip parameter.
MaxSkipValue = 1000000

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

what is the source of this number, does it not look very high ?


func ErrorResponse(c *gin.Context, err error) {
// handleDomainErrors handles domain-specific errors.
func handleDomainErrors(c *gin.Context, err error) bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How is this change specific to this PR ?

}

// handleTypedErrors handles remaining typed errors.
func handleTypedErrors(c *gin.Context, err error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

How is this change specific to this PR ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants