Skip to content

HYPERFLEET-1259 - fix: SQL injection protection for order queries#244

Open
ma-hill wants to merge 1 commit into
openshift-hyperfleet:mainfrom
ma-hill:HYPERFLEET-1259
Open

HYPERFLEET-1259 - fix: SQL injection protection for order queries#244
ma-hill wants to merge 1 commit into
openshift-hyperfleet:mainfrom
ma-hill:HYPERFLEET-1259

Conversation

@ma-hill

@ma-hill ma-hill commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds SQL injection protection and field whitelisting to the orderBy query parameter handling. Previously, the ArgsToOrderBy function used a per-resource blacklist approach that was vulnerable to SQL injection attacks through malicious field names or sort directions. This refactors the validation to use a hardcoded allowlist of permitted fields and a regex pattern that rejects all SQL injection attempts (semicolons, comments, parentheses, etc.), providing defense-in-depth protection for all list endpoints.

HYPERFLEET-1259

Changes

  • Replaced per-resource disallowedFields blacklist parameter with a hardcoded orderByAllowedFields whitelist map containing only safe, permitted field names (id, name, created_time, updated_time, deleted_time, kind, created_by, updated_by, deleted_by, generation, href)
  • Added regex pattern validation (orderByPattern) that enforces strict syntax: lowercase field names (letters, underscore only) followed by optional asc/desc direction
  • Updated ArgsToOrderBy -> ArgsToOrder and signature to remove the disallowedFields parameter, simplifying the API to just accept the raw orderBy arguments array
  • Added comprehensive unit tests (TestArgsToOrderBy, TestArgsToOrderBy_SecurityValidation) covering valid inputs, edge cases (empty strings, whitespace), and SQL injection attempts
  • Added integration tests (TestOrderByFieldMapping, TestOrderByFieldValidation, TestOrderByAllowedFields, TestOrderByEmptyStrings) that verify ordering behavior end-to-end via HTTP and validate that injection attempts return 400 Bad Request
  • Updated all callers in pkg/services/generic.go to use the new single-parameter signature

Test Plan

  • Unit tests added/updated (TestArgsToOrderBy, TestArgsToOrderBy_SecurityValidation)
  • Integration tests added (TestOrderByFieldMapping, TestOrderByFieldValidation)
  • make test-all passes
  • make lint passes

@openshift-ci

openshift-ci Bot commented Jun 22, 2026

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci

openshift-ci Bot commented Jun 22, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign ldornele for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Order-by handling now uses a fixed field allowlist and a format regex, defaults missing direction to asc, skips blank entries, and returns BadRequest for malformed or unauthorized inputs. The generic list service now calls the updated parser directly. Unit and integration tests cover valid ordering, empty strings, and injection-style payload rejection. CWE-20 and CWE-89 remain relevant.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant sqlGenericService.buildOrderBy
  participant db.ArgsToOrder
  participant ListQuery
  Client->>sqlGenericService.buildOrderBy: submit OrderBy args
  sqlGenericService.buildOrderBy->>db.ArgsToOrder: validate and normalize args
  db.ArgsToOrder-->>sqlGenericService.buildOrderBy: cleaned list or BadRequest
  sqlGenericService.buildOrderBy->>ListQuery: apply cleaned order clauses
  ListQuery-->>Client: sorted response or 400
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 10 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Description check ✅ Passed The description is directly related to the orderBy validation and test changes in the diff.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Sec-02: Secrets In Log Output ✅ Passed No non-test/example log statements with token/password/credential/secret fields or interpolations were found; CWE-532 not triggered.
No Hardcoded Secrets ✅ Passed No hardcoded credentials, secret literals, embedded-credential URLs, or long base64 blobs found in the modified files; only allowlist strings and SQLi test payloads. CWE-798/CWE-321 not present.
No Weak Cryptography ✅ Passed No banned primitives, ECB mode, custom crypto, or secret comparisons in the changed files; orderBy changes are whitelist/regex only (no CWE-327/328/759).
No Injection Vectors ✅ Passed No new CWE-89/78/79/502 sink in changed non-test code; order-by input is regex-validated and allowlisted before gorm.Order().
No Privileged Containers ✅ Passed No privileged manifest flags found; only Dockerfile USER root is a documented build-step, and the runtime image drops to USER 65532, so no CWE-266/CWE-250 issue.
No Pii Or Sensitive Data In Logs ✅ Passed PASS: Touched orderBy code adds no logging; scan found only generic size warnings in generic.go and no fmt/log/slog/zap calls with request/order data (CWE-532).
Title check ✅ Passed The title matches the main change: hardened order-by query handling with whitelist validation against SQL injection (CWE-89).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Comment @coderabbitai help to get the list of available commands.

@ma-hill

ma-hill commented Jun 22, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ma-hill ma-hill force-pushed the HYPERFLEET-1259 branch 3 times, most recently from a88f369 to 52c393a Compare June 25, 2026 16:30
@ma-hill ma-hill marked this pull request as ready for review June 25, 2026 16:30
@openshift-ci openshift-ci Bot requested review from ldornele and vkareh June 25, 2026 16:30
@hyperfleet-ci-bot

hyperfleet-ci-bot Bot commented Jun 25, 2026

Copy link
Copy Markdown

Risk Score: 3 — risk/medium

Signal Detail Points
PR size 553 lines (>500) +2
Sensitive paths none +0
Test coverage Missing tests for: pkg/services +1

Computed by hyperfleet-risk-scorer

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
pkg/db/sql_helpers.go (1)

817-862: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Regex contract mismatch + unreachable error branches.

orderByPattern is ^[a-z_][a-z_]*(\s+(asc|desc))?$ — it rejects digits and uppercase, yet the comment on Line 817 claims "letters, digits, underscore." That's a latent foot-gun: adding any digit-bearing field (e.g. image_v2) to orderByAllowedFields would silently 400 despite being whitelisted.

Separately, because the regex already gates format, two branches below are dead:

  • the direction != "asc" && direction != "desc" check (Line 849-851) — regex only permits asc|desc.
  • the default case (Line 855-857) — strings.Fields can only yield 1 or 2 tokens after the regex passes.

The granular invalid sort direction message is therefore never emitted; name ascending and name asc extra both fall through to the format error (consistent with the tests). Fix the comment to match the pattern, and either drop the unreachable branches or include 0-9 in the field class to honor the documented contract.

Proposed alignment
-// orderByPattern matches valid orderBy syntax: field name (letters, digits, underscore) followed by optional asc/desc.
-// This regex rejects SQL injection attempts (semicolons, parentheses, dashes, comments, etc).
-var orderByPattern = regexp.MustCompile(`^[a-z_][a-z_]*(\s+(asc|desc))?$`)
+// orderByPattern matches valid orderBy syntax: a lowercase field name
+// (letters, digits, underscore) followed by an optional asc/desc direction.
+// It rejects SQL injection attempts (semicolons, parentheses, dashes, comments, etc).
+var orderByPattern = regexp.MustCompile(`^[a-z_][a-z0-9_]*(\s+(asc|desc))?$`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/db/sql_helpers.go` around lines 817 - 862, The ArgsToOrderBy validation
in orderByPattern and the surrounding parsing logic are inconsistent: the regex
currently only allows lowercase letters/underscores, while the comment claims
digits are supported, and the later direction/default checks in ArgsToOrderBy
are unreachable after the regex gate. Update the comment and either keep the
stricter regex and remove the dead validation branches, or расширить
orderByPattern to match the intended field contract; make sure
orderByAllowedFields remains the final whitelist check.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/db/sql_helpers_test.go`:
- Around line 957-958: The parallel subtests in TestArgsToOrderBy are sharing
Gomega’s global registration via RegisterTestingT, which can race when
t.Parallel is used. Update each subtest to create its own local Gomega instance
with NewWithT(t) and replace any shared Expect calls with g.Expect in that test
scope so each subtest uses its own fail handler.

In `@test/integration/order_field_mapping_test.go`:
- Around line 102-136: The MultipleOrderByFields test is asserting a composite
sort, but the current data makes the kind comparison trivial and the
comments/assertions don’t match the actual kind asc,name desc input. Update the
test in order_field_mapping_test.go around MultipleOrderByFields to verify a
field that actually varies or assert the full (kind, name) ordering sequence
using the GetClustersWithResponse result, and make the inline comments and name
comparison consistent with descending name order.

---

Nitpick comments:
In `@pkg/db/sql_helpers.go`:
- Around line 817-862: The ArgsToOrderBy validation in orderByPattern and the
surrounding parsing logic are inconsistent: the regex currently only allows
lowercase letters/underscores, while the comment claims digits are supported,
and the later direction/default checks in ArgsToOrderBy are unreachable after
the regex gate. Update the comment and either keep the stricter regex and remove
the dead validation branches, or расширить orderByPattern to match the intended
field contract; make sure orderByAllowedFields remains the final whitelist
check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Central YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 80dd1400-1a49-4cb3-bcee-ad077adc7a1a

📥 Commits

Reviewing files that changed from the base of the PR and between 922ed5d and 52c393a.

📒 Files selected for processing (4)
  • pkg/db/sql_helpers.go
  • pkg/db/sql_helpers_test.go
  • pkg/services/generic.go
  • test/integration/order_field_mapping_test.go
🔗 Linked repositories identified

CodeRabbit considers these linked repositories for cross-repo context during reviews:

  • openshift-hyperfleet/architecture (manual)
  • openshift-hyperfleet/hyperfleet-api (manual)
  • openshift-hyperfleet/hyperfleet-sentinel (manual)
  • openshift-hyperfleet/hyperfleet-adapter (manual)
  • openshift-hyperfleet/hyperfleet-broker (manual)

Comment thread pkg/db/sql_helpers_test.go Outdated
Comment thread test/integration/order_field_mapping_test.go Outdated
@ma-hill ma-hill force-pushed the HYPERFLEET-1259 branch 3 times, most recently from 86a1652 to 648cccb Compare June 25, 2026 17:12
Comment thread pkg/db/sql_helpers.go
Comment on lines +556 to +570
// orderAllowedFields defines the whitelist of fields that are allowed to be ordered.
// This prevents SQL injection and restricts invalid order queries.
var orderAllowedFields = map[string]bool{
"id": true,
"name": true,
"created_time": true,
"updated_time": true,
"deleted_time": true,
"kind": true,
"created_by": true,
"updated_by": true,
"deleted_by": true,
"generation": true,
"href": true,
}

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.

This list is missing deleted_time and deleted_by from the orderByAllowedFields map, so 2 of 11 allowed fields don't have integration coverage. Consider adding them so the test lives up to its "all whitelisted fields" intent.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

deleted_time ( line 563 ) and deleted-by ( line 567 )

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.

The allowlist in sql_helpers.go is correct. My comment was about the integration test TestOrderByAllowedFields in order_field_mapping_test.go, which only tests 9 of 11 fields. deleted_time and deleted_by aren't in the test's allowedFields slice, so they don't have integration coverage.

Comment on lines +128 to +135
Expect(currKind == prevKind).To(BeTrue(), fmt.Sprintf("Kinds should be equal: %s == %s", currKind, prevKind))

// Secondary key: within same kind, names should be ascending
Expect(currName <= prevName).To(BeTrue(),
fmt.Sprintf("Within same kind, names should be descending: %s <= %s",
currName, prevName))
}
}

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.

All test clusters share the same kind value, so the kind sort assertion always passes trivially. Consider ordering by name asc, created_time desc instead, since both fields vary across the test data.

@ma-hill ma-hill Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@pnguyen44 Yes, this is true. But this test is verifying that if the primary key fields are the same the order by functionality will order based on the secondary key. For this test, it would be the cluster names since kind is always Cluster, using name asc, created_time desc won't really show us anything because you cannot a cluster with the same name. So will just order by name. I guess I could extend this tests to include other resources besides clusters.

Comment on lines +147 to +159
t.Run("InvalidFieldName", func(t *testing.T) {
RegisterTestingT(t)

orderBy := openapi.QueryParamsOrderBy("nonexistent_field asc")
params := &openapi.GetClustersParams{
OrderBy: &orderBy,
}
resp, err := client.GetClustersWithResponse(ctx, params, test.WithAuthToken(ctx))

Expect(err).NotTo(HaveOccurred())
Expect(resp.StatusCode()).To(Equal(http.StatusBadRequest))
Expect(string(resp.Body)).To(ContainSubstring("not allowed for ordering"))
})

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.

These 7 subtests (InvalidFieldName through TooManyParts) all share the same structure with only input and expected error varying. Consider collapsing them into a table-driven test, matching the pattern already used by TestArgsToOrderBy in the unit tests.

@pnguyen44

pnguyen44 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Missing CHANGELOG entry. This restricts which fields are orderable, which is a user-visible behavioral change. Consider adding an entry.

Comment on lines +977 to +988
injectionAttempts := []struct {
name string
input string
}{
{"union injection", "name UNION SELECT password FROM users"},
{"comment injection", "name--"},
{"semicolon terminator", "name; DROP TABLE resources;"},
{"quote escape", "name' OR '1'='1"},
{"parentheses", "name) OR (1=1"},
{"wildcard", "name*"},
{"backtick", "name`"},
}

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.

Consider adding a test case for uppercase rejection to verify the lowercase-only enforcement.

@ma-hill ma-hill force-pushed the HYPERFLEET-1259 branch 2 times, most recently from cc225eb to f6f114e Compare June 25, 2026 18:05
@ma-hill ma-hill force-pushed the HYPERFLEET-1259 branch 2 times, most recently from 32ceb82 to 95e8e84 Compare June 25, 2026 19:49
@ma-hill ma-hill changed the title HYPERFLEET-1259 - fix: SQL injection protection for orderBy/search HYPERFLEET-1259 - fix: SQL injection protection for order queries Jun 25, 2026
@pnguyen44

Copy link
Copy Markdown
Contributor

Looks like the rebase reverted the ArgsToOrderBy → ArgsToOrder rename in a few places. The function and one test still use OrderBy while everything else uses order. Can you do a quick search for any remaining OrderBy references and align them?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants