fix(openapi): skip auth injection for public operations#191
Conversation
- add operation-level auth requirement evaluation for OpenAPI/Swagger security metadata - skip URL/header auth injection when operation is explicitly public - keep legacy auth injection when security metadata is unknown - annotate Binance Spot signed endpoints with security + api_key scheme - add adapter tests for public/requires-auth/unknown paths
There was a problem hiding this comment.
Pull request overview
This PR updates the OpenAPI adapter to evaluate OpenAPI/Swagger security metadata per operation so that auth (URL/query signing + header injection) is skipped for operations that are explicitly public, addressing Binance public endpoints being rejected when signed.
Changes:
- Added
OperationAuthRequirementevaluation from operation-level and root-levelsecurity, with a fallback heuristic for mixed public/secured schemas. - Gated
apply_auth_profile_to_urlandapply_auth_profileinexecute()so public operations do not receive auth injection. - Annotated Binance Spot signed endpoints with
securityand addedcomponents.securitySchemes.api_keyto the reference OpenAPI document.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/adapters/openapi.rs |
Implements per-operation auth requirement evaluation and gates auth injection; adds unit/integration tests for Public/RequiresAuth/Unknown behaviors. |
skills/binance-spot-openapi-skill/references/binance-spot.openapi.json |
Adds api_key security scheme and marks signed endpoints with operation-level security to enable correct auth gating. |
Comments suppressed due to low confidence (1)
src/adapters/openapi.rs:1086
execute()always usessend_with_oauth_retry(), which refreshes OAuth profiles (and can trigger network refreshes) even whenauth_requirementisPublicand auth injection is skipped. To fully skip auth work for public operations, consider bypassing OAuth refresh/retry in this branch (e.g., build the request withNoneprofile and send directly).
let auth_requirement = Self::operation_auth_requirement(operation_spec, &schema);
let should_apply_auth = !matches!(auth_requirement, OperationAuthRequirement::Public);
let resp = self
.send_with_oauth_retry(|profile| {
let full_url = {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| fn schema_has_any_operation_security(root: &Value) -> bool { | ||
| root.get("paths") | ||
| .and_then(|paths| paths.as_object()) | ||
| .is_some_and(|paths| { | ||
| paths.values().any(|path_item| { | ||
| path_item.as_object().is_some_and(|methods| { | ||
| methods.iter().any(|(method, spec)| { | ||
| Self::is_http_method(&method.to_lowercase()) | ||
| && spec.get("security").is_some() | ||
| }) | ||
| }) | ||
| }) | ||
| }) | ||
| } |
There was a problem hiding this comment.
schema_has_any_operation_security() walks the entire paths map to detect any security usage. This is called for every execution when both operation/root security are absent, so mixed schemas can end up doing an O(schema_size) scan per request. Consider computing/caching this once per fetched schema (e.g., memoize a boolean alongside the cached schema) and reusing it in operation_auth_requirement().
There was a problem hiding this comment.
Review for PR #191: fix(openapi): skip auth injection for public operations
Summary
This PR addresses issue #189 by implementing operation-level auth requirement evaluation using OpenAPI/Swagger security metadata. The implementation correctly skips auth injection (both URL query params and headers) for explicitly public operations while maintaining backward compatibility for schemas without security metadata.
Overall Assessment: LGTM with minor suggestions
The implementation is sound, well-tested, and follows OpenAPI semantics correctly. All tests pass, code is properly formatted, and no clippy warnings were raised.
Key Findings
Strengths
-
Correct OpenAPI Semantics (
src/adapters/openapi.rs:810-870)- Properly implements the three-tier security evaluation: operation-level
security→ root-levelsecurity→ implicit public when other operations have security - The
security_requirementfunction correctly handles empty arrays{}as public and non-empty objects as requiring auth schema_has_any_operation_securityproperly implements the OpenAPI 3.x behavior where operations withoutsecurityare public when ANY operation in the schema defines security
- Properly implements the three-tier security evaluation: operation-level
-
Backward Compatibility (
src/adapters/openapi.rs:1081-1082)- The
Unknowncase ensures existing behavior is preserved for schemas without security metadata should_apply_authdefaults totruefor unknown cases, preventing breaking changes
- The
-
Comprehensive Test Coverage (
src/adapters/openapi.rs:1293-1463)- Unit tests cover all three cases (Public, RequiresAuth, Unknown)
- Integration tests verify both URL and header auth injection are correctly skipped/applied
- Tests use mock servers with proper query parameter matching
-
Binance Schema Annotations (
skills/binance-spot-openapi-skill/references/binance-spot.openapi.json)- Correctly adds
security: [{"api_key": []}]to 9 signed endpoints - Properly defines
components.securitySchemes.api_keywith correctX-MBX-APIKEYheader name - Public endpoints like
/api/v3/pingremain without security annotations
- Correctly adds
Minor Suggestions
-
Documentation Clarity (
src/adapters/openapi.rs:834-847)
Theschema_has_any_operation_securityfunction could benefit from a doc comment explaining the OpenAPI 3.x specification behavior it implements. This would help future maintainers understand why operations without explicitsecurityare treated as public in this context. -
Enum Visibility (
src/adapters/openapi.rs:882-887)
Consider whetherOperationAuthRequirementshould bepub(crate)orpubif it might be useful for testing or debugging in other modules. Currently it's private, which is fine, but making it more visible could aid in diagnostics.
Security Analysis
No security concerns identified.
The implementation correctly handles security requirements:
- Public operations explicitly skip both URL query param injection (
apply_auth_profile_to_url) and header injection (apply_auth_profile) - The three-state design (Public/RequiresAuth/Unknown) prevents accidental auth exposure
- Backward compatibility via
Unknownensures existing behavior isn't silently changed
Performance Analysis
No performance concerns.
operation_auth_requirementis called once per request and performs straightforward JSON traversalschema_has_any_operation_securitycould be optimized by caching the result per schema, but this is unnecessary given schemas are already cached- The additional overhead is negligible compared to network I/O
Testing Verification
- All 20 OpenAPI adapter tests pass
- Code passes
cargo fmt -- --check - No clippy warnings in modified files
- Tests verify the exact issue from #189 is fixed (public endpoints don't receive auth params)
Recommendation
APPROVE - This PR correctly implements the fix for issue #189 with proper test coverage, backward compatibility, and adherence to OpenAPI specification semantics. The minor suggestions above are optional improvements that don't block merge.
What
securitymetadatasecurityand addcomponents.securitySchemes.api_keyPublic / RequiresAuth / Unknownand execute-path auth gatingWhy
Issue #189 reports signer/query injection being applied to public endpoints such as
GET /api/v3/ping, causing Binance to reject requests with-1101 Too many parameters.How
OperationAuthRequirement(Public,RequiresAuth,Unknown) insrc/adapters/openapi.rssecurity, then root-levelsecurity, with OpenAPI semantics handling for mixed secured/public operationsapply_auth_profile_to_urlandapply_auth_profileinexecute()based on requirementskills/binance-spot-openapi-skill/references/binance-spot.openapi.jsonto explicitly mark signed operations and declareapi_keysecurity schemeTesting
cargo fmt -- --checkcargo test --package uxc openapi::tests:: -- --nocaptureCloses #189