From 1ea7fc1960b818e3c3ac3984f85922e3acba4061 Mon Sep 17 00:00:00 2001 From: Dave Hubbard <4305408+unsmith@users.noreply.github.com> Date: Wed, 26 Aug 2026 15:59:11 -0400 Subject: [PATCH] expand result code in pagerduty detector (#5230) This now handles all documented response codes: 200 Valid key, has access 401 Invalid token 403 Valid key, insufficient permissions 402 Valid key, account plan limitation 429 Transient, rate limit 400 Permanent, malformed request 404 Permanent, resource not found --- .../pagerdutyapikey/pagerdutyapikey.go | 27 ++++++++ .../pagerdutyapikey_integration_test.go | 66 ++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/pkg/detectors/pagerdutyapikey/pagerdutyapikey.go b/pkg/detectors/pagerdutyapikey/pagerdutyapikey.go index a61f2ee00..67bcbcd65 100644 --- a/pkg/detectors/pagerdutyapikey/pagerdutyapikey.go +++ b/pkg/detectors/pagerdutyapikey/pagerdutyapikey.go @@ -86,8 +86,35 @@ func verifyPagerdutyapikey(ctx context.Context, client *http.Client, token strin case http.StatusOK: return true, nil case http.StatusUnauthorized: + // Token is missing, malformed, or invalid. + // Note that revoked (disabled) keys are treated as invalid. + return false, nil + case http.StatusForbidden: + // PagerDuty returns 403 when the token authenticated successfully but + // lacks permission for the requested resource -- e.g. a user token for + // a restricted-role user, or a scoped OAuth token missing users.read. + // This confirms the credential is live. + return true, nil + case http.StatusTooManyRequests: + // Rate-limited. Can't determine validity; keep indeterminate for retry. + return false, fmt.Errorf("PagerDuty rate limit reached") + case http.StatusBadRequest: + // Malformed request parameters. Shouldn't happen for our simple GET. + // Not transient -- the same request will always produce the same result. + return false, nil + case http.StatusPaymentRequired: + // Account lacks the pricing-plan abilities for this endpoint. PagerDuty + // must have authenticated the token before checking plan features, so + // the credential is live. + return true, nil + case http.StatusNotFound: + // Resource not found. Unusual on the /users collection endpoint. + // Not transient -- retrying won't make the endpoint appear. + // Shouldn't happen in practice. return false, nil default: + // Unknown status codes (including 5xx server errors) are potentially + // transient, so return an error to keep the result indeterminate. return false, fmt.Errorf("unexpected HTTP response status %d", res.StatusCode) } } diff --git a/pkg/detectors/pagerdutyapikey/pagerdutyapikey_integration_test.go b/pkg/detectors/pagerdutyapikey/pagerdutyapikey_integration_test.go index bb57ddf6c..58ca0d6af 100644 --- a/pkg/detectors/pagerdutyapikey/pagerdutyapikey_integration_test.go +++ b/pkg/detectors/pagerdutyapikey/pagerdutyapikey_integration_test.go @@ -73,7 +73,7 @@ func TestPagerDutyApiKey_FromChunk(t *testing.T) { wantVerificationErr: true, }, { - name: "found, verified but unexpected api surface", + name: "found, unverified due to not found (404)", s: Scanner{client: common.ConstantResponseHttpClient(404, "")}, args: args{ ctx: context.Background(), @@ -86,9 +86,73 @@ func TestPagerDutyApiKey_FromChunk(t *testing.T) { Verified: false, }, }, + wantErr: false, + }, + { + name: "found, verified via 403 (valid key, insufficient permissions)", + s: Scanner{client: common.ConstantResponseHttpClient(403, `{"error":{"message":"Access Denied","code":2010}}`)}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a pagerdutyapikey secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detector_typepb.DetectorType_PagerDutyApiKey, + Verified: true, + }, + }, + wantErr: false, + }, + { + name: "found, indeterminate due to rate limiting (429)", + s: Scanner{client: common.ConstantResponseHttpClient(429, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a pagerdutyapikey secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detector_typepb.DetectorType_PagerDutyApiKey, + Verified: false, + }, + }, wantErr: false, wantVerificationErr: true, }, + { + name: "found, unverified due to bad request (400)", + s: Scanner{client: common.ConstantResponseHttpClient(400, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a pagerdutyapikey secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detector_typepb.DetectorType_PagerDutyApiKey, + Verified: false, + }, + }, + wantErr: false, + }, + { + name: "found, verified via 402 (valid key, account plan limitation)", + s: Scanner{client: common.ConstantResponseHttpClient(402, "")}, + args: args{ + ctx: context.Background(), + data: []byte(fmt.Sprintf("You can find a pagerdutyapikey secret %s within", secret)), + verify: true, + }, + want: []detectors.Result{ + { + DetectorType: detector_typepb.DetectorType_PagerDutyApiKey, + Verified: true, + }, + }, + wantErr: false, + }, { name: "found, unverified", s: Scanner{},