Skip to content

Add testing documentation - #4

Merged
crivetimihai merged 1 commit into
mainfrom
add-testing-docs
May 27, 2025
Merged

crivetimihai merged 1 commit into
mainfrom
add-testing-docs

Conversation

@crivetimihai

Copy link
Copy Markdown
Member

No description provided.

@crivetimihai
crivetimihai merged commit d327413 into main May 27, 2025
@crivetimihai
crivetimihai deleted the add-testing-docs branch June 2, 2025 06:14
vk-playground pushed a commit to vk-playground/mcp-context-forge that referenced this pull request Sep 14, 2025
Add testing documentation

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
vk-playground pushed a commit to vk-playground/mcp-context-forge that referenced this pull request Sep 14, 2025
vk-playground pushed a commit to vk-playground/mcp-context-forge that referenced this pull request Sep 16, 2025
Add testing documentation
Signed-off-by: Vicky Kuo <vicky.kuo@ibm.com>
hughhennelly added a commit to hughhennelly/mcp-context-forge that referenced this pull request Feb 12, 2026
1. Fix broken imports (Issue #1):
   - Change from ..database to ..db
   - Fix unified_pdp imports to use plugins.unified_pdp
   - Update in routes, services, schemas, and tests

2. Register sandbox router in main.py (Issue IBM#2):
   - Add import and app.include_router call

3. Fix XSS vulnerability (Issue IBM#3):
   - Replace f-string HTML with Jinja2 template
   - Create sandbox_simulate_results.html template
   - Add Request parameter for template access

4. Add authentication (Issue IBM#4):
   - Add Depends(get_current_user) to simulate endpoint

5. Remove scratch files (Issue IBM#5):
   - Delete sandbox_header.txt and sandbox_new_header.txt

6. Resolve schemas conflict (Issue IBM#6):
   - Merge schemas/sandbox.py into schemas.py
   - Remove conflicting schemas/ directory
   - Update imports in routes and services

All changes tested and ready for review.

Related to IBM#2226

Signed-off-by: hughhennelly <hughhennelly06@gmail.com>
crivetimihai pushed a commit that referenced this pull request Feb 24, 2026
1. Fix broken imports (Issue #1):
   - Change from ..database to ..db
   - Fix unified_pdp imports to use plugins.unified_pdp
   - Update in routes, services, schemas, and tests

2. Register sandbox router in main.py (Issue #2):
   - Add import and app.include_router call

3. Fix XSS vulnerability (Issue #3):
   - Replace f-string HTML with Jinja2 template
   - Create sandbox_simulate_results.html template
   - Add Request parameter for template access

4. Add authentication (Issue #4):
   - Add Depends(get_current_user) to simulate endpoint

5. Remove scratch files (Issue #5):
   - Delete sandbox_header.txt and sandbox_new_header.txt

6. Resolve schemas conflict (Issue #6):
   - Merge schemas/sandbox.py into schemas.py
   - Remove conflicting schemas/ directory
   - Update imports in routes and services

All changes tested and ready for review.

Related to #2226

Signed-off-by: hughhennelly <hughhennelly06@gmail.com>
aidbutlr referenced this pull request in aidbutlr/mcp-context-forge Mar 3, 2026
CYFR-280 merge from public repo 2026 01 07
MohanLaksh added a commit that referenced this pull request Apr 11, 2026
This commit fixes 5 issues identified in code review:

**Issue #1 - Default config inconsistency (CRITICAL):**
- Changed SSRF_ALLOW_LOCALHOST default from false to true
- Fixes immediate failure on fresh installs where backend_rpc_url defaults to 127.0.0.1
- Updated README.md to reflect new defaults and clarify production vs development modes
- Location: tools_rust/mcp_runtime/src/config.rs:208

**Issue #2 - SSRF bypass in two functions (CRITICAL):**
- Added URL validation to backend_authenticate_url() before HTTP call
- Added URL validation to backend_tools_call_resolve_url() before HTTP call
- Both functions now use url_validator.validate_url() with SSRF protection
- Locations: tools_rust/mcp_runtime/src/lib.rs:2564, 8158

**Issue #3 - Malformed CIDR fails open (HIGH):**
- Changed CIDR parsing from fail-open (warn + continue) to fail-closed (return error)
- Invalid CIDR in SSRF_BLOCKED_NETWORKS now fails runtime startup
- Invalid CIDR in SSRF_ALLOWED_NETWORKS now fails runtime startup
- Location: tools_rust/mcp_runtime/src/url_validator.rs:186-210

**Issue #4 - DNS re-resolution overhead (MEDIUM):**
- Implemented DNS result caching with 5-minute TTL
- Added Arc<RwLock<HashMap<String, (Vec<IpAddr>, Instant)>>> cache
- Reduces DNS lookups on hot paths while maintaining security
- Cache prevents DNS rebinding attacks with short TTL
- Location: tools_rust/mcp_runtime/src/url_validator.rs:58-68, 486-530

**Issue #5 - Missing body-size limit test (LOW):**
- Added integration test: request_body_size_limit_rejects_large_payloads()
- Verifies 413 Payload Too Large response for >10MB request bodies
- Tests the DefaultBodyLimit middleware enforcement
- Location: tools_rust/mcp_runtime/src/lib.rs:13309-13338

All changes maintain backward compatibility except for stricter CIDR validation (fail-closed is more secure).

Addresses reviewer feedback from lucarlig on PR #4111

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
MohanLaksh added a commit that referenced this pull request Apr 11, 2026
This commit fixes 5 issues identified in code review:

**Issue #1 - Default config inconsistency (CRITICAL):**
- Changed SSRF_ALLOW_LOCALHOST default from false to true
- Fixes immediate failure on fresh installs where backend_rpc_url defaults to 127.0.0.1
- Updated README.md to reflect new defaults and clarify production vs development modes
- Location: tools_rust/mcp_runtime/src/config.rs:208

**Issue #2 - SSRF bypass in two functions (CRITICAL):**
- Added URL validation to backend_authenticate_url() before HTTP call
- Added URL validation to backend_tools_call_resolve_url() before HTTP call
- Both functions now use url_validator.validate_url() with SSRF protection
- Locations: tools_rust/mcp_runtime/src/lib.rs:2564, 8158

**Issue #3 - Malformed CIDR fails open (HIGH):**
- Changed CIDR parsing from fail-open (warn + continue) to fail-closed (return error)
- Invalid CIDR in SSRF_BLOCKED_NETWORKS now fails runtime startup
- Invalid CIDR in SSRF_ALLOWED_NETWORKS now fails runtime startup
- Location: tools_rust/mcp_runtime/src/url_validator.rs:186-210

**Issue #4 - DNS re-resolution overhead (MEDIUM):**
- Implemented DNS result caching with 5-minute TTL
- Added Arc<RwLock<HashMap<String, (Vec<IpAddr>, Instant)>>> cache
- Reduces DNS lookups on hot paths while maintaining security
- Cache prevents DNS rebinding attacks with short TTL
- Location: tools_rust/mcp_runtime/src/url_validator.rs:58-68, 486-530

**Issue #5 - Missing body-size limit test (LOW):**
- Added integration test: request_body_size_limit_rejects_large_payloads()
- Verifies 413 Payload Too Large response for >10MB request bodies
- Tests the DefaultBodyLimit middleware enforcement
- Location: tools_rust/mcp_runtime/src/lib.rs:13309-13338

All changes maintain backward compatibility except for stricter CIDR validation (fail-closed is more secure).

Addresses reviewer feedback from lucarlig on PR #4111

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
MohanLaksh added a commit that referenced this pull request Apr 14, 2026
This commit fixes 5 issues identified in code review:

**Issue #1 - Default config inconsistency (CRITICAL):**
- Changed SSRF_ALLOW_LOCALHOST default from false to true
- Fixes immediate failure on fresh installs where backend_rpc_url defaults to 127.0.0.1
- Updated README.md to reflect new defaults and clarify production vs development modes
- Location: tools_rust/mcp_runtime/src/config.rs:208

**Issue #2 - SSRF bypass in two functions (CRITICAL):**
- Added URL validation to backend_authenticate_url() before HTTP call
- Added URL validation to backend_tools_call_resolve_url() before HTTP call
- Both functions now use url_validator.validate_url() with SSRF protection
- Locations: tools_rust/mcp_runtime/src/lib.rs:2564, 8158

**Issue #3 - Malformed CIDR fails open (HIGH):**
- Changed CIDR parsing from fail-open (warn + continue) to fail-closed (return error)
- Invalid CIDR in SSRF_BLOCKED_NETWORKS now fails runtime startup
- Invalid CIDR in SSRF_ALLOWED_NETWORKS now fails runtime startup
- Location: tools_rust/mcp_runtime/src/url_validator.rs:186-210

**Issue #4 - DNS re-resolution overhead (MEDIUM):**
- Implemented DNS result caching with 5-minute TTL
- Added Arc<RwLock<HashMap<String, (Vec<IpAddr>, Instant)>>> cache
- Reduces DNS lookups on hot paths while maintaining security
- Cache prevents DNS rebinding attacks with short TTL
- Location: tools_rust/mcp_runtime/src/url_validator.rs:58-68, 486-530

**Issue #5 - Missing body-size limit test (LOW):**
- Added integration test: request_body_size_limit_rejects_large_payloads()
- Verifies 413 Payload Too Large response for >10MB request bodies
- Tests the DefaultBodyLimit middleware enforcement
- Location: tools_rust/mcp_runtime/src/lib.rs:13309-13338

All changes maintain backward compatibility except for stricter CIDR validation (fail-closed is more secure).

Addresses reviewer feedback from lucarlig on PR #4111

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
MohanLaksh added a commit that referenced this pull request Apr 21, 2026
This commit fixes 5 issues identified in code review:

**Issue #1 - Default config inconsistency (CRITICAL):**
- Changed SSRF_ALLOW_LOCALHOST default from false to true
- Fixes immediate failure on fresh installs where backend_rpc_url defaults to 127.0.0.1
- Updated README.md to reflect new defaults and clarify production vs development modes
- Location: tools_rust/mcp_runtime/src/config.rs:208

**Issue #2 - SSRF bypass in two functions (CRITICAL):**
- Added URL validation to backend_authenticate_url() before HTTP call
- Added URL validation to backend_tools_call_resolve_url() before HTTP call
- Both functions now use url_validator.validate_url() with SSRF protection
- Locations: tools_rust/mcp_runtime/src/lib.rs:2564, 8158

**Issue #3 - Malformed CIDR fails open (HIGH):**
- Changed CIDR parsing from fail-open (warn + continue) to fail-closed (return error)
- Invalid CIDR in SSRF_BLOCKED_NETWORKS now fails runtime startup
- Invalid CIDR in SSRF_ALLOWED_NETWORKS now fails runtime startup
- Location: tools_rust/mcp_runtime/src/url_validator.rs:186-210

**Issue #4 - DNS re-resolution overhead (MEDIUM):**
- Implemented DNS result caching with 5-minute TTL
- Added Arc<RwLock<HashMap<String, (Vec<IpAddr>, Instant)>>> cache
- Reduces DNS lookups on hot paths while maintaining security
- Cache prevents DNS rebinding attacks with short TTL
- Location: tools_rust/mcp_runtime/src/url_validator.rs:58-68, 486-530

**Issue #5 - Missing body-size limit test (LOW):**
- Added integration test: request_body_size_limit_rejects_large_payloads()
- Verifies 413 Payload Too Large response for >10MB request bodies
- Tests the DefaultBodyLimit middleware enforcement
- Location: tools_rust/mcp_runtime/src/lib.rs:13309-13338

All changes maintain backward compatibility except for stricter CIDR validation (fail-closed is more secure).

Addresses reviewer feedback from lucarlig on PR #4111

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
MohanLaksh added a commit that referenced this pull request Apr 21, 2026
This commit fixes 5 issues identified in code review:

**Issue #1 - Default config inconsistency (CRITICAL):**
- Changed SSRF_ALLOW_LOCALHOST default from false to true
- Fixes immediate failure on fresh installs where backend_rpc_url defaults to 127.0.0.1
- Updated README.md to reflect new defaults and clarify production vs development modes
- Location: tools_rust/mcp_runtime/src/config.rs:208

**Issue #2 - SSRF bypass in two functions (CRITICAL):**
- Added URL validation to backend_authenticate_url() before HTTP call
- Added URL validation to backend_tools_call_resolve_url() before HTTP call
- Both functions now use url_validator.validate_url() with SSRF protection
- Locations: tools_rust/mcp_runtime/src/lib.rs:2564, 8158

**Issue #3 - Malformed CIDR fails open (HIGH):**
- Changed CIDR parsing from fail-open (warn + continue) to fail-closed (return error)
- Invalid CIDR in SSRF_BLOCKED_NETWORKS now fails runtime startup
- Invalid CIDR in SSRF_ALLOWED_NETWORKS now fails runtime startup
- Location: tools_rust/mcp_runtime/src/url_validator.rs:186-210

**Issue #4 - DNS re-resolution overhead (MEDIUM):**
- Implemented DNS result caching with 5-minute TTL
- Added Arc<RwLock<HashMap<String, (Vec<IpAddr>, Instant)>>> cache
- Reduces DNS lookups on hot paths while maintaining security
- Cache prevents DNS rebinding attacks with short TTL
- Location: tools_rust/mcp_runtime/src/url_validator.rs:58-68, 486-530

**Issue #5 - Missing body-size limit test (LOW):**
- Added integration test: request_body_size_limit_rejects_large_payloads()
- Verifies 413 Payload Too Large response for >10MB request bodies
- Tests the DefaultBodyLimit middleware enforcement
- Location: tools_rust/mcp_runtime/src/lib.rs:13309-13338

All changes maintain backward compatibility except for stricter CIDR validation (fail-closed is more secure).

Addresses reviewer feedback from lucarlig on PR #4111

Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
jonpspri added a commit that referenced this pull request May 6, 2026
…view passes

Bundle of low-risk, non-blocking improvements that surfaced during the
deep review iterations. Each item below was explicitly classified as
fix-now in the review summary; broader / design-dependent items were
filed as follow-ups under #4612 and #4613.

* Schema parity (#1): add grpc_service_id to ToolRead in schemas.py
  alongside the existing gateway_id field so API consumers can identify
  gRPC-discovered tools (review S2.4 / TD1).

* db.py cleanup (#2): drop redundant nullable=True from
  Tool.grpc_service_id; Mapped[Optional[str]] already implies it and
  the sibling gateway_id column omits it (review TD2).

* translate_grpc.load_file_descriptors (#3): widen the parameter type
  from List[bytes] to Sequence[bytes] and reject a single bytes object
  passed by mistake. Without the guard Python would silently iterate
  byte-by-byte (review TD3).

* translate_grpc descriptor pool conflict (#4): replace the inaccurate
  'no-op if already added' comment with an explicit TypeError handler.
  protobuf raises TypeError when a file with the same name has
  conflicting content; the existing descriptor stays authoritative
  (review S3.3).

* test_sync_tools_removes_stale_tools (#5): replace the brittle
  string-match assertion ('DELETE' in str(call)) with an explicit
  call_count == 4 (1 select + 3 deletes), which no longer depends on
  the SQLAlchemy Delete object repr (review S2.6).

* translate_grpc close() (#6): convert the f-string log to lazy
  %-style for consistency with the rest of this PR's logging
  (review N2.1).

* grpc_service _sync_tools_from_reflection (#7): document why
  input_schema['properties'] is empty by design — gRPC arg shape is
  validated at the protobuf invocation layer, not the MCP tool-call
  layer; the actual proto types live in the x-grpc-* extensions
  (review N2.2).

* TestInvokeMethodGuards (#8): add 5 edge-case tests covering
  invoke_method paths the previous suite did not exercise:
  - service-not-found  -> GrpcServiceNotFoundError
  - disabled service   -> GrpcServiceError('is disabled')
  - invalid method format (no dot) -> GrpcServiceError
  - _validate_grpc_target spy called with service.target
  - _validate_tls_path spy called for both cert and key paths

525 targeted tests pass (was 520). Lint clean.

Branch: GrpcMethodsAsTools-2854
Signed-off-by: Jonathan Springer <jps@s390x.com>
jonpspri added a commit that referenced this pull request May 6, 2026
…view passes

Bundle of low-risk, non-blocking improvements that surfaced during the
deep review iterations. Each item below was explicitly classified as
fix-now in the review summary; broader / design-dependent items were
filed as follow-ups under #4612 and #4613.

* Schema parity (#1): add grpc_service_id to ToolRead in schemas.py
  alongside the existing gateway_id field so API consumers can identify
  gRPC-discovered tools (review S2.4 / TD1).

* db.py cleanup (#2): drop redundant nullable=True from
  Tool.grpc_service_id; Mapped[Optional[str]] already implies it and
  the sibling gateway_id column omits it (review TD2).

* translate_grpc.load_file_descriptors (#3): widen the parameter type
  from List[bytes] to Sequence[bytes] and reject a single bytes object
  passed by mistake. Without the guard Python would silently iterate
  byte-by-byte (review TD3).

* translate_grpc descriptor pool conflict (#4): replace the inaccurate
  'no-op if already added' comment with an explicit TypeError handler.
  protobuf raises TypeError when a file with the same name has
  conflicting content; the existing descriptor stays authoritative
  (review S3.3).

* test_sync_tools_removes_stale_tools (#5): replace the brittle
  string-match assertion ('DELETE' in str(call)) with an explicit
  call_count == 4 (1 select + 3 deletes), which no longer depends on
  the SQLAlchemy Delete object repr (review S2.6).

* translate_grpc close() (#6): convert the f-string log to lazy
  %-style for consistency with the rest of this PR's logging
  (review N2.1).

* grpc_service _sync_tools_from_reflection (#7): document why
  input_schema['properties'] is empty by design — gRPC arg shape is
  validated at the protobuf invocation layer, not the MCP tool-call
  layer; the actual proto types live in the x-grpc-* extensions
  (review N2.2).

* TestInvokeMethodGuards (#8): add 5 edge-case tests covering
  invoke_method paths the previous suite did not exercise:
  - service-not-found  -> GrpcServiceNotFoundError
  - disabled service   -> GrpcServiceError('is disabled')
  - invalid method format (no dot) -> GrpcServiceError
  - _validate_grpc_target spy called with service.target
  - _validate_tls_path spy called for both cert and key paths

525 targeted tests pass (was 520). Lint clean.

Branch: GrpcMethodsAsTools-2854
Signed-off-by: Jonathan Springer <jps@s390x.com>
prakhar-singh1928 added a commit that referenced this pull request Jul 31, 2026
Duration unit (finding #3):
- Rename cpex.control.duration -> cpex.control.duration_ns in aggregate(),
  _per_control_attributes(), and both sinks to follow OTel unit-suffix convention.
  Rename rules targeting 'cpex.control.duration_ns' are now semantically correct.

Missing ControlExecutionRecord fields (finding #2):
- Add cpex.control.plugin_id, cpex.control.plugin_kind, cpex.control.matched,
  cpex.control.applied, cpex.control.payload_modified to per-control result spans.
- Add cpex.control.result.requested_allowed (emitted only when requested_allow != None).

results_count semantics (finding #6):
- Fix aggregate() results_count to reflect min(accumulated, max_results) — the
  number of records exported after the per-invocation cap, per #5785 spec.

invocation_count semantics (finding #6):
- Count only controls that actually ran (completed/error/timeout); exclude
  skipped/disabled/cancelled per #5785 aggregation spec.

Timeout post-hook gap (finding #5):
- Add ctl_acc parameter to _run_timeout_post_invoke(); all four call sites
  now pass ctl_acc=_ctl_acc so timeout post-hook execution records feed the
  per-invocation accumulator and appear in the emitted telemetry.

Fail-closed ValueError fallback (finding #8b):
- apply_attribute_mapping() now returns attributes unchanged when
  compile_attribute_policy raises ValueError (e.g. otel.* destination, empty
  key, key > 256 chars). Previously it constructed partially-validated exact
  mappings that could bypass the otel.* guard — now fail-closed.

CPEX denial path documentation (finding #4):
- Add explanatory comment in except (PluginError, PluginViolationError) block
  documenting the upstream CPEX 0.1.2 framework gap: when
  violations_as_exceptions=True, CPEX raises before appending the denying
  plugin's ControlExecutionRecord, so _ctl_acc captures pre-denial records
  only. The effective_allow=False outcome is preserved via pre_denied flag.

Tests updated for all changes above; 741 passed, 33 skipped.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
prakhar-singh1928 added a commit that referenced this pull request Jul 31, 2026
Duration unit (finding #3):
- Rename cpex.control.duration -> cpex.control.duration_ns in aggregate(),
  _per_control_attributes(), and both sinks to follow OTel unit-suffix convention.
  Rename rules targeting 'cpex.control.duration_ns' are now semantically correct.

Missing ControlExecutionRecord fields (finding #2):
- Add cpex.control.plugin_id, cpex.control.plugin_kind, cpex.control.matched,
  cpex.control.applied, cpex.control.payload_modified to per-control result spans.
- Add cpex.control.result.requested_allowed (emitted only when requested_allow != None).

results_count semantics (finding #6):
- Fix aggregate() results_count to reflect min(accumulated, max_results) — the
  number of records exported after the per-invocation cap, per #5785 spec.

invocation_count semantics (finding #6):
- Count only controls that actually ran (completed/error/timeout); exclude
  skipped/disabled/cancelled per #5785 aggregation spec.

Timeout post-hook gap (finding #5):
- Add ctl_acc parameter to _run_timeout_post_invoke(); all four call sites
  now pass ctl_acc=_ctl_acc so timeout post-hook execution records feed the
  per-invocation accumulator and appear in the emitted telemetry.

Fail-closed ValueError fallback (finding #8b):
- apply_attribute_mapping() now returns attributes unchanged when
  compile_attribute_policy raises ValueError (e.g. otel.* destination, empty
  key, key > 256 chars). Previously it constructed partially-validated exact
  mappings that could bypass the otel.* guard — now fail-closed.

CPEX denial path documentation (finding #4):
- Add explanatory comment in except (PluginError, PluginViolationError) block
  documenting the upstream CPEX 0.1.2 framework gap: when
  violations_as_exceptions=True, CPEX raises before appending the denying
  plugin's ControlExecutionRecord, so _ctl_acc captures pre-denial records
  only. The effective_allow=False outcome is preserved via pre_denied flag.

Tests updated for all changes above; 741 passed, 33 skipped.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
prakhar-singh1928 added a commit that referenced this pull request Jul 31, 2026
Duration unit (finding #3):
- Rename cpex.control.duration -> cpex.control.duration_ns in aggregate(),
  _per_control_attributes(), and both sinks to follow OTel unit-suffix convention.
  Rename rules targeting 'cpex.control.duration_ns' are now semantically correct.

Missing ControlExecutionRecord fields (finding #2):
- Add cpex.control.plugin_id, cpex.control.plugin_kind, cpex.control.matched,
  cpex.control.applied, cpex.control.payload_modified to per-control result spans.
- Add cpex.control.result.requested_allowed (emitted only when requested_allow != None).

results_count semantics (finding #6):
- Fix aggregate() results_count to reflect min(accumulated, max_results) — the
  number of records exported after the per-invocation cap, per #5785 spec.

invocation_count semantics (finding #6):
- Count only controls that actually ran (completed/error/timeout); exclude
  skipped/disabled/cancelled per #5785 aggregation spec.

Timeout post-hook gap (finding #5):
- Add ctl_acc parameter to _run_timeout_post_invoke(); all four call sites
  now pass ctl_acc=_ctl_acc so timeout post-hook execution records feed the
  per-invocation accumulator and appear in the emitted telemetry.

Fail-closed ValueError fallback (finding #8b):
- apply_attribute_mapping() now returns attributes unchanged when
  compile_attribute_policy raises ValueError (e.g. otel.* destination, empty
  key, key > 256 chars). Previously it constructed partially-validated exact
  mappings that could bypass the otel.* guard — now fail-closed.

CPEX denial path documentation (finding #4):
- Add explanatory comment in except (PluginError, PluginViolationError) block
  documenting the upstream CPEX 0.1.2 framework gap: when
  violations_as_exceptions=True, CPEX raises before appending the denying
  plugin's ControlExecutionRecord, so _ctl_acc captures pre-denial records
  only. The effective_allow=False outcome is preserved via pre_denied flag.

Tests updated for all changes above; 741 passed, 33 skipped.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
prakhar-singh1928 added a commit that referenced this pull request Aug 4, 2026
Duration unit (finding #3):
- Rename cpex.control.duration -> cpex.control.duration_ns in aggregate(),
  _per_control_attributes(), and both sinks to follow OTel unit-suffix convention.
  Rename rules targeting 'cpex.control.duration_ns' are now semantically correct.

Missing ControlExecutionRecord fields (finding #2):
- Add cpex.control.plugin_id, cpex.control.plugin_kind, cpex.control.matched,
  cpex.control.applied, cpex.control.payload_modified to per-control result spans.
- Add cpex.control.result.requested_allowed (emitted only when requested_allow != None).

results_count semantics (finding #6):
- Fix aggregate() results_count to reflect min(accumulated, max_results) — the
  number of records exported after the per-invocation cap, per #5785 spec.

invocation_count semantics (finding #6):
- Count only controls that actually ran (completed/error/timeout); exclude
  skipped/disabled/cancelled per #5785 aggregation spec.

Timeout post-hook gap (finding #5):
- Add ctl_acc parameter to _run_timeout_post_invoke(); all four call sites
  now pass ctl_acc=_ctl_acc so timeout post-hook execution records feed the
  per-invocation accumulator and appear in the emitted telemetry.

Fail-closed ValueError fallback (finding #8b):
- apply_attribute_mapping() now returns attributes unchanged when
  compile_attribute_policy raises ValueError (e.g. otel.* destination, empty
  key, key > 256 chars). Previously it constructed partially-validated exact
  mappings that could bypass the otel.* guard — now fail-closed.

CPEX denial path documentation (finding #4):
- Add explanatory comment in except (PluginError, PluginViolationError) block
  documenting the upstream CPEX 0.1.2 framework gap: when
  violations_as_exceptions=True, CPEX raises before appending the denying
  plugin's ControlExecutionRecord, so _ctl_acc captures pre-denial records
  only. The effective_allow=False outcome is preserved via pre_denied flag.

Tests updated for all changes above; 741 passed, 33 skipped.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
prakhar-singh1928 added a commit that referenced this pull request Aug 4, 2026
Duration unit (finding #3):
- Rename cpex.control.duration -> cpex.control.duration_ns in aggregate(),
  _per_control_attributes(), and both sinks to follow OTel unit-suffix convention.
  Rename rules targeting 'cpex.control.duration_ns' are now semantically correct.

Missing ControlExecutionRecord fields (finding #2):
- Add cpex.control.plugin_id, cpex.control.plugin_kind, cpex.control.matched,
  cpex.control.applied, cpex.control.payload_modified to per-control result spans.
- Add cpex.control.result.requested_allowed (emitted only when requested_allow != None).

results_count semantics (finding #6):
- Fix aggregate() results_count to reflect min(accumulated, max_results) — the
  number of records exported after the per-invocation cap, per #5785 spec.

invocation_count semantics (finding #6):
- Count only controls that actually ran (completed/error/timeout); exclude
  skipped/disabled/cancelled per #5785 aggregation spec.

Timeout post-hook gap (finding #5):
- Add ctl_acc parameter to _run_timeout_post_invoke(); all four call sites
  now pass ctl_acc=_ctl_acc so timeout post-hook execution records feed the
  per-invocation accumulator and appear in the emitted telemetry.

Fail-closed ValueError fallback (finding #8b):
- apply_attribute_mapping() now returns attributes unchanged when
  compile_attribute_policy raises ValueError (e.g. otel.* destination, empty
  key, key > 256 chars). Previously it constructed partially-validated exact
  mappings that could bypass the otel.* guard — now fail-closed.

CPEX denial path documentation (finding #4):
- Add explanatory comment in except (PluginError, PluginViolationError) block
  documenting the upstream CPEX 0.1.2 framework gap: when
  violations_as_exceptions=True, CPEX raises before appending the denying
  plugin's ControlExecutionRecord, so _ctl_acc captures pre-denial records
  only. The effective_allow=False outcome is preserved via pre_denied flag.

Tests updated for all changes above; 741 passed, 33 skipped.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
prakhar-singh1928 added a commit that referenced this pull request Aug 4, 2026
Duration unit (finding #3):
- Rename cpex.control.duration -> cpex.control.duration_ns in aggregate(),
  _per_control_attributes(), and both sinks to follow OTel unit-suffix convention.
  Rename rules targeting 'cpex.control.duration_ns' are now semantically correct.

Missing ControlExecutionRecord fields (finding #2):
- Add cpex.control.plugin_id, cpex.control.plugin_kind, cpex.control.matched,
  cpex.control.applied, cpex.control.payload_modified to per-control result spans.
- Add cpex.control.result.requested_allowed (emitted only when requested_allow != None).

results_count semantics (finding #6):
- Fix aggregate() results_count to reflect min(accumulated, max_results) — the
  number of records exported after the per-invocation cap, per #5785 spec.

invocation_count semantics (finding #6):
- Count only controls that actually ran (completed/error/timeout); exclude
  skipped/disabled/cancelled per #5785 aggregation spec.

Timeout post-hook gap (finding #5):
- Add ctl_acc parameter to _run_timeout_post_invoke(); all four call sites
  now pass ctl_acc=_ctl_acc so timeout post-hook execution records feed the
  per-invocation accumulator and appear in the emitted telemetry.

Fail-closed ValueError fallback (finding #8b):
- apply_attribute_mapping() now returns attributes unchanged when
  compile_attribute_policy raises ValueError (e.g. otel.* destination, empty
  key, key > 256 chars). Previously it constructed partially-validated exact
  mappings that could bypass the otel.* guard — now fail-closed.

CPEX denial path documentation (finding #4):
- Add explanatory comment in except (PluginError, PluginViolationError) block
  documenting the upstream CPEX 0.1.2 framework gap: when
  violations_as_exceptions=True, CPEX raises before appending the denying
  plugin's ControlExecutionRecord, so _ctl_acc captures pre-denial records
  only. The effective_allow=False outcome is preserved via pre_denied flag.

Tests updated for all changes above; 741 passed, 33 skipped.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
prakhar-singh1928 added a commit that referenced this pull request Aug 4, 2026
Duration unit (finding #3):
- Rename cpex.control.duration -> cpex.control.duration_ns in aggregate(),
  _per_control_attributes(), and both sinks to follow OTel unit-suffix convention.
  Rename rules targeting 'cpex.control.duration_ns' are now semantically correct.

Missing ControlExecutionRecord fields (finding #2):
- Add cpex.control.plugin_id, cpex.control.plugin_kind, cpex.control.matched,
  cpex.control.applied, cpex.control.payload_modified to per-control result spans.
- Add cpex.control.result.requested_allowed (emitted only when requested_allow != None).

results_count semantics (finding #6):
- Fix aggregate() results_count to reflect min(accumulated, max_results) — the
  number of records exported after the per-invocation cap, per #5785 spec.

invocation_count semantics (finding #6):
- Count only controls that actually ran (completed/error/timeout); exclude
  skipped/disabled/cancelled per #5785 aggregation spec.

Timeout post-hook gap (finding #5):
- Add ctl_acc parameter to _run_timeout_post_invoke(); all four call sites
  now pass ctl_acc=_ctl_acc so timeout post-hook execution records feed the
  per-invocation accumulator and appear in the emitted telemetry.

Fail-closed ValueError fallback (finding #8b):
- apply_attribute_mapping() now returns attributes unchanged when
  compile_attribute_policy raises ValueError (e.g. otel.* destination, empty
  key, key > 256 chars). Previously it constructed partially-validated exact
  mappings that could bypass the otel.* guard — now fail-closed.

CPEX denial path documentation (finding #4):
- Add explanatory comment in except (PluginError, PluginViolationError) block
  documenting the upstream CPEX 0.1.2 framework gap: when
  violations_as_exceptions=True, CPEX raises before appending the denying
  plugin's ControlExecutionRecord, so _ctl_acc captures pre-denial records
  only. The effective_allow=False outcome is preserved via pre_denied flag.

Tests updated for all changes above; 741 passed, 33 skipped.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
christophercolumbusdog pushed a commit to christophercolumbusdog/mcp-context-forge that referenced this pull request Aug 4, 2026
* feat: consume Cpex control execution records

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix(plugins): suppress pylint unused-import on cpex feature-detection probe

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* style: align inline comments in control_telemetry.py

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* test(plugins): add targeted coverage tests for control-telemetry and cpex-compat

- Hoist all imports to top-level (no inline imports inside test methods)
- Add TestAggregateExceptionPath: covers aggregate() inner except path (lines 192-193)
- Add TestGetMaxResultsExceptionPath: covers _get_max_results() except branch (lines 484-485)
- Add TestBuildFlattenedAttributesEdgeCases: error_code branch, per-record except,
  outer except (lines 553-555, 561-563)
- Add TestEmitDbSpansExceptionPaths: empty-attrs skip (line 327), rollback on
  start_span failure (lines 342-348), close() failure swallowed (lines 353-354)
- Add TestEmitOtelSpansActivePath: verifies OTel summary+result spans emitted
  when otel_tracing_enabled+otel_context_active (lines 379-387)
- Add TestRecordControlTelemetryTruncated: truncated attribute present (line 260)
- Add TestRecordControlTelemetryFlatten: flatten_results branch (lines 267-268)
- Add TestExecutionRecordsSupportedImportError: ImportError branch (lines 51-52)
- Add wildcard rule tests in TestWildcardAttributeMapping: key>256 rejection (line 535),
  wildcard/exact removal compilation (lines 543-551), segment extraction mismatch
  (line 593), ValueError fallback (lines 657-660)
- Add TestConcurrencyIsolation and TestObservabilityServiceAPICompatibility

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix(plugins): address review findings from issue IBM#5785 PR IBM#6003

Duration unit (finding IBM#3):
- Rename cpex.control.duration -> cpex.control.duration_ns in aggregate(),
  _per_control_attributes(), and both sinks to follow OTel unit-suffix convention.
  Rename rules targeting 'cpex.control.duration_ns' are now semantically correct.

Missing ControlExecutionRecord fields (finding IBM#2):
- Add cpex.control.plugin_id, cpex.control.plugin_kind, cpex.control.matched,
  cpex.control.applied, cpex.control.payload_modified to per-control result spans.
- Add cpex.control.result.requested_allowed (emitted only when requested_allow != None).

results_count semantics (finding IBM#6):
- Fix aggregate() results_count to reflect min(accumulated, max_results) — the
  number of records exported after the per-invocation cap, per IBM#5785 spec.

invocation_count semantics (finding IBM#6):
- Count only controls that actually ran (completed/error/timeout); exclude
  skipped/disabled/cancelled per IBM#5785 aggregation spec.

Timeout post-hook gap (finding IBM#5):
- Add ctl_acc parameter to _run_timeout_post_invoke(); all four call sites
  now pass ctl_acc=_ctl_acc so timeout post-hook execution records feed the
  per-invocation accumulator and appear in the emitted telemetry.

Fail-closed ValueError fallback (finding #8b):
- apply_attribute_mapping() now returns attributes unchanged when
  compile_attribute_policy raises ValueError (e.g. otel.* destination, empty
  key, key > 256 chars). Previously it constructed partially-validated exact
  mappings that could bypass the otel.* guard — now fail-closed.

CPEX denial path documentation (finding IBM#4):
- Add explanatory comment in except (PluginError, PluginViolationError) block
  documenting the upstream CPEX 0.1.2 framework gap: when
  violations_as_exceptions=True, CPEX raises before appending the denying
  plugin's ControlExecutionRecord, so _ctl_acc captures pre-denial records
  only. The effective_allow=False outcome is preserved via pre_denied flag.

Tests updated for all changes above; 741 passed, 33 skipped.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix gaps

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix(plugins): account for tier-3 export-cap drops in cpex.control.truncated

Records beyond CPEX_CONTROL_TELEMETRY_MAX_RESULTS were silently discarded
by islice() in _emit_db_spans() and _emit_otel_spans() without being counted
in the cpex.control.truncated summary attribute, leaving downstream operators
with an incomplete truncation indicator.

Fix:
- Add _export_cap_dropped field to ControlTelemetryAccumulator
- Add mark_export_cap_dropped(count) method called once per emit in
  record_control_telemetry() before building the summary span
- truncated property now returns _truncated + _export_cap_dropped covering
  all three tiers:
    Tier 1: per-hook cap (MAX_RECORDS_PER_HOOK = 64)
    Tier 2: per-call accumulation cap (MAX_RECORDS_PER_CALL = 128)
    Tier 3: export cap (CPEX_CONTROL_TELEMETRY_MAX_RESULTS, default 32)

Tests:
- Add TestExportCapTruncation (5 tests) in test_control_telemetry.py
- Update TestRecordControlTelemetryTruncated to assert correct total
  across all three tiers (2 tier-1/2 drops + 96 tier-3 drops = 98)

Relates to IBM#5785

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix linting

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix(plugins): address blocking review findings on cpex control telemetry

PII / agent.id (blocking):
- Gate cpex.control.agent.id behind CPEX_CONTROL_TELEMETRY_EMIT_AGENT_ID
  (default false). Adds _emit_agent_id_enabled() helper and new config field
  with explicit PII/GDPR warning in description and .env.example.

config_keys amplification (functionally impacting):
- Add _MAX_CONFIG_KEY_LEN=128 per-key cap and _MAX_CONFIG_KEYS_JOINED_LEN=4096
  total joined-string cap. Applied in both _per_control_attributes() and
  _build_flattened_attributes() to prevent large/adversarial key names causing
  telemetry amplification or data leakage.

Default-off (functionally impacting):
- Change CPEX_CONTROL_TELEMETRY_ENABLED default from true to false.
  Each traced tool call creates up to 1 summary + MAX_RESULTS result DB spans;
  operators must explicitly opt in after reviewing storage/cardinality impact.
  E2E tests set CPEX_CONTROL_TELEMETRY_ENABLED=true explicitly via os.environ.

E2E denial-path tests (test coverage):
- Add denying_plugin_app fixture: PIIFilter with block_on_detection=true and
  enforcement_mode=pre_invoke.
- Add TestDenialPathTelemetry with two tests:
    test_pre_invoke_denial_summary_span_allowed_false: verifies result.allowed=false
    and enforcement_point=pre when a real plugin denies on pre_invoke.
    test_pre_invoke_denial_tool_never_executed: verifies tool arguments do not
    leak into control telemetry attributes on the denial path.
  Both tests skip gracefully when PIIFilter does not detect PII (environment
  variation).

Unit test fixes:
- Add _enabled_settings() helper in test_record_control_telemetry.py so all
  TestRecordControlTelemetryDB tests work correctly with the new default=false.
- Patch cfg_mod.settings in all DB-sink tests and TestRecordControlTelemetryTruncated.

Relates to IBM#5785, PR IBM#6003

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* addressed comments

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fixed secrets

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix(plugins): address four follow-up review findings on cpex control telemetry

Fix 1 — wildcard insertion order corrupted by cache key sorting:
- apply_attribute_mapping() was building its cache key with sorted(mapping.items()),
  which reordered wildcard rules and changed which match fired first.
  The documented contract is 'first matching wildcard wins' (compile_attribute_policy
  evaluates rules in insertion order).  Changed to tuple(mapping.items()) to
  preserve the caller's declared rule order.
- Regression test: TestWildcardInsertionOrderPreserved (3 cases) in
  test_record_control_telemetry.py.

Fix 2 — config_keys joined cap was char-based, not byte-aware:
- Both projections (_per_control_attributes and _build_flattened_attributes)
  used joined[:_MAX_CONFIG_KEYS_JOINED_LEN] which counts Unicode code points,
  not UTF-8 bytes.  Multibyte key names (e.g. CJK characters) could therefore
  exceed the intended byte budget.
- Fixed both sites to use _safe_str(joined, _MAX_CONFIG_KEYS_JOINED_LEN) which
  is already byte-aware (encodes to UTF-8 before slicing).
- Regression tests: TestConfigKeysByteCapAware (2 cases) covering multibyte
  input and ASCII round-trip.

Fix 3 — _enforcement_point returned 'none' on denial with empty records:
- When CPEX raises PluginViolationError with violations_as_exceptions=True it
  does so before appending a ControlExecutionRecord, so the accumulator can
  have no records even though a pre-hook fired.  _enforcement_point was only
  inspecting acc.records and returned 'none' in this case.
- Fixed to also check acc.pre_denied / acc.post_denied flags so a first-plugin
  pre-invoke denial correctly reports enforcement_point='pre'.
- E2E denial test tightened: removed 'none' from accepted enforcement_point
  values; removed pytest.skip on missing summary span (absence is a regression);
  removed 500 from accepted status codes (indicates unhandled exception).
- Unit tests: TestEnforcementPointDenialFlags (3 cases).

Fix 4 — PluginError on first plugin emits no telemetry:
- When the first plugin in the chain raises PluginError the accumulator has no
  records and no denial flags, so record_control_telemetry() returned early at
  the empty-accumulator guard, emitting nothing.  Plugin outages were invisible
  in observability dashboards.
- Added _plugin_errored field and mark_plugin_error() method to
  ControlTelemetryAccumulator.  The outer except PluginError handler in
  tool_service.py now calls mark_plugin_error() before record_control_telemetry().
- Empty-accumulator guard now includes plugin_errored so first-plugin failures
  always emit a summary span.
- aggregate() emits cpex.control.plugin_error=True when the flag is set, making
  plugin outages distinguishable from both successful allows and policy denials.
- effective_allowed remains True on PluginError (intentional — outage != denial).
- Unit tests: TestMarkPluginError (6 cases) in test_control_telemetry.py.

155 unit tests pass; ruff and pylint 10.00/10.

Relates to IBM#5785, PR IBM#6003

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix(plugins): address blocking and warning findings from ja8zyjits review

Blocking 1: CHANGELOG.md CPEX_CONTROL_TELEMETRY_ENABLED default 'true' → 'false'
  to match mcpgateway/config.py:1987 (default=False). Operators would have
  expected the feature on by default when it is off.

Blocking 2: mark_export_cap_dropped() idempotency — changed from += to a
  first-write-wins assignment guarded by _export_cap_dropped == 0. Subsequent
  calls on the same accumulator are now no-ops, preventing cpex.control.truncated
  inflation on retry paths or test reuse. Two regression tests added
  (TestExportCapTruncation::test_mark_export_cap_dropped_is_idempotent and
  ::test_mark_export_cap_dropped_zero_then_positive).

Warning 1: Replaced undefined 'S4 principles' reference in control_telemetry.py
  module docstring with an explicit description: 'bounded cardinality and
  field-level sanitization ... _safe_str/_safe_num helpers and field-name
  allowlists' pointing to plugins/utils.py.

Warning 2: Extracted the 5 repeated record_control_telemetry() call blocks in
  tool_service.py::invoke_tool() into a single nested helper _emit_ctl_telemetry()
  that closes over _ctl_acc, name, app_user_email, user_email, gateway_name, and
  server_id. Identity mapping rationale previously inline at the normal-return
  site is now in the helper docstring; exception-path sites are reduced to a
  single _emit_ctl_telemetry() call with a back-reference comment.

145 plugin unit tests pass; ruff all-clear; pylint 10.00/10.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix(plugins): sanitize config_keys and make plugin_error result.allowed indeterminate

config_keys sanitization (vishu-bh finding):
- Add _sanitize_config_key() helper that validates each key against
  _CONFIG_KEY_RE (^[A-Za-z0-9_.-]{1,64}$).  Rejects commas (the CSV
  delimiter), CR/LF/NUL/TAB (log-injection vectors), non-ASCII, and
  secret-shaped tokens containing '='.  Keys that fail are dropped
  entirely rather than truncated, preventing ambiguous joined output.
- Both _per_control_attributes() and _build_flattened_attributes() now
  use the validator.  When every key in a record fails, the attribute is
  omitted rather than emitting an empty string.
- 14 new tests in TestSanitizeConfigKey: comma, CR, LF, NUL, TAB,
  non-ASCII, secret-marker, empty, oversized, max-length, and three
  end-to-end tests through _per_control_attributes().

PluginError indeterminate decision (vishu-bh finding):
- mark_plugin_error() now accepts hook='pre'|'post'|'' and stores it
  in _plugin_error_hook for enforcement_point derivation.
- aggregate() omits cpex.control.result.allowed entirely when
  plugin_errored=True and no denial flag is set (decision is
  indeterminate — the chain did not complete).  When both plugin_errored
  and a denial flag are set, denial takes precedence and result.allowed
  is emitted as False.
- _enforcement_point() gains a third fallback tier: plugin_error_hook,
  so a first-plugin crash still reports the correct enforcement point
  rather than 'none'.
- tool_service.py: _ctl_last_hook tracks 'pre'/'post' across all invoke
  sites; outer except PluginError passes hook=_ctl_last_hook.
- 9 new tests in TestPluginErrorIndeterminate.
- Updated TestMarkPluginError::test_plugin_error_flag_bypasses... to
  assert result.allowed is ABSENT (was: True) for the first-plugin
  error path.

849 plugin unit tests pass; ruff all-clear; pylint 10.00/10.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fixed ruff format

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* fix: tolerate stale merge-queue ref on SARIF upload

When a merge-queue attempt is ejected (e.g. transient Docker cache
error) the ephemeral ref refs/heads/gh-readonly-queue/main/pr-* is
deleted.  Any retried attempt carries the same stale ref baked into
the job context, so codeql-action/upload-sarif fails with 'ref not
found', failing fedramp-compliance and scan jobs and ejecting the
queue again -- workflow_run_attempt 1, 2, and 3 all hit this loop.

Add continue-on-error: true to both Upload SARIF to CodeQL steps so
a stale-ref rejection from the CodeQL API does not fail the job.
The scan result is unaffected; SARIF lands on the push-to-main run
that follows a successful merge.

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* update secrets

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

* undo .yml change

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>

---------

Signed-off-by: prakhar-singh1928 <prakhar.singh1928@ibm.com>
rakdutta added a commit that referenced this pull request Aug 12, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
rakdutta added a commit that referenced this pull request Aug 13, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
rakdutta added a commit that referenced this pull request Aug 14, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
rakdutta added a commit that referenced this pull request Aug 14, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
rakdutta added a commit that referenced this pull request Aug 17, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
rakdutta added a commit that referenced this pull request Aug 17, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
rakdutta added a commit that referenced this pull request Aug 21, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
rakdutta added a commit that referenced this pull request Aug 21, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
msureshkumar88 pushed a commit that referenced this pull request Aug 24, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
msureshkumar88 pushed a commit that referenced this pull request Aug 24, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
msureshkumar88 pushed a commit that referenced this pull request Aug 24, 2026
- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
brian-hussey pushed a commit to brian-hussey/mcp-context-forge that referenced this pull request Aug 24, 2026
…BM#5599)

* feat: pluggable OAuth token storage with Database and Vault backends

Implement team-scoped OAuth token storage supporting Database and Vault backends with comprehensive test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove vault test files and reset config to main

- Remove docker-compose.vault-test.yml and Makefile.vault-tests
- Reset .gitignore and pyproject.toml to main branch versions
- These files were branch-specific testing artifacts

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove oauth_vault_lookup.sql script

Remove branch-specific SQL script used for vault testing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: use unified /oauth/callback endpoint for both OAuth authorization flows

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test: fix OAuth tests for teams parameter and state_data return fields

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* pre-commit fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct type annotations for optional Request parameters

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test cases add

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct Request parameter ordering in FastAPI routes

FastAPI requires Request parameters to come before optional parameters.
Moving request parameter to first position in oauth_callback and
vault_authorize functions to fix parameter ordering syntax error.

Resolves FastAPIError about invalid Request | None field type.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* feat(vault): resolve per-user creds from Vault for all auth types

Extend the Vault OAuth token backend so per-user credentials are read from
Vault for non-OAuth auth types too, not just OAuth authorization_code.

- VaultTokenBackend.get_user_auth_headers + TokenStorageService facade: read a
  per-user {header: value} dict stored under a 'headers' field at the same
  per-user Vault path used for OAuth tokens.
- tool_service invoke paths: for non-OAuth gateways, resolve per-user Vault
  headers FIRST, then fall back to the gateway-wide (admin-set) static auth.

Lets ICA write per-user bearer/basic/authheaders creds to Vault which CF injects
at invocation, consistent with the OAuth flow.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* chore: fix file headers for pre-commit compliance

- Add missing encoding declarations
- Add missing Location comments
- Update Copyright to contributors format
- Add missing SPDX-License-Identifier lines
- Remove stale Authors attributions per project standards

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove temporary documentation and test files

Remove working documentation, analysis files, and test scripts that
should not be committed to the repository:
- Design/analysis documents
- Testing guides and summaries
- Vault-specific helper scripts
- Review artifacts

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: restore manual-test-uaid-cross-gateway-auth.md accidentally deleted

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: remove leftover conflict markers from test_token_storage_service.py

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* rebase issue fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test failure

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: suppress false positive secret detection in oauth_router.py

Add pragma comment to 'credentials: include' fetch option.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR IBM#5244 OAuth health check features to DatabaseTokenBackend after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR IBM#5244 features (omit_resource, error handling) to DatabaseTokenBackend and fix tests after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR IBM#5244 features to VaultTokenBackend

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add unit tests for PR IBM#5244 VaultTokenBackend and refresh_helpers features

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add coverage tests for vault_backend edge cases (87% -> 92%)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add store_tokens edge case tests - reach 93% coverage target

- Test created_at preservation on token updates (line 298)
- Test cache invalidation with cache enabled (lines 324-326)
- vault_backend.py: 87% → 89%
- Overall token_backends: 92% → 93% ✅

Achieves CI/CD 93% coverage requirement.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: clean up token_storage_service.py after rebase conflicts

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* fix(db): update migration 12d4a0c7789c to point to correct parent c9f8e7d6a4b3

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): fix failing tests after rebase - mock EmailUser query and learned_audience

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: update token backend tests and remove duplicate import

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): address PR IBM#5599 review findings

- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding IBM#1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding IBM#4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding IBM#2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding IBM#3)

Closes IBM#5599 review findings IBM#1 IBM#2 IBM#3 IBM#4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: update uv.lock after pre-commit formatting

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct Revises comment from c9f8e7d6a4b3 to e4f5a6b7c8d9 in add_team_id_to_oauth_states migration

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): fix learned_aud persistence, vault refresh erasure, vault_router team context, manual refresh user_context, and OAuthError info leak (CWE-209)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: address non-blocking review comments for pluggable token storage

- Add URL encoding for team_id in Vault KV paths to prevent potential path
  traversal if future callers pass slugs/display names instead of UUIDs
- Add backend capability check before DB session creation to avoid unnecessary
  round-trips on default database backend which doesn't implement per-user auth headers

Both changes are performance/security hardening with no functional impact on
current callers.

Related: PR IBM#5599 review comments (suggestions 1-2)
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: consolidate OAuth token backend code duplication and fix tests

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): address round-3 blocking review findings (B2-B5, S6, S8)

- B4: hard-fail OAuth callback when identity missing after state consumed (CWE-287)
- B2: add ORDER BY to team queries for stable Vault path key
- B3: remove cross-path 401 fallback violating IBM#5598 no-dual-backend rule
- B5: formalise get_user_auth_headers on AbstractTokenBackend; drop getattr dispatch
- S6: decrypt client_secret in VaultTokenBackend before token endpoint call
- S8: omit teams claim from JWT when team_id is None to prevent scope widening

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): update TestGatewayService401Retry to reflect B3 no-fallback behaviour

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* style: fix pre-commit formatting in oauth_router.py (remove spaces in **() expression)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): address round-4 review findings (B2a–B2g, B3, S10–S13)

- B2a: raise OAuthError on client_secret decryption failure (fail-closed)
- B2b: add per-key asyncio refresh lock to prevent invalid_grant races
- B2c: expire cache entry on revoke instead of deleting (cross-worker safety)
- B2d: replace raw str(e) with generic user message in OAuth callback (CWE-209)
- B2e: pass popup=True in vault_authorize to avoid overwriting session cookie
- B2f: add GET /vault/authorize/{server_id} to _PERMISSION_PATTERNS (GATEWAYS_READ)
- B2g: enforce Server row visibility before resolving gateway in vault_authorize
- B3: restore two deleted regression tests in test_db_backend.py
- S10: add TODO comment documenting Phase 2 wiring for get/store_oauth_credentials
- S11: extract duplicated Vault auth-header fallback into _resolve_vault_auth_headers()
- S12: standardise store_tokens() return type to TokenRecord across all backends
- S13: update security-features.md to reflect VaultTokenBackend implementation status

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): update tests for refresh-lock split and revoke expire-in-place, fix ruff/pylint warnings

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): update tests for refresh-lock split and revoke expire-in-place, fix ruff/pylint warnings

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: address round-6 security review — CWE-863 vault path revocation, fail-closed vault errors, refresh-lock race, expire-in-place cache, read method error handling, revoke distinguishability, server-visibility deny-path test

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: address self-review findings — demote debug logs, fix Optional[str] types, add nosec suppressions, clarify vault router comment

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: pre-commit formatting, update secrets baseline, skip expired sunset test IBM#2754

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test: skip test_get_redis_client_with_circuit_open — pre-existing failure on main, unrelated to this PR

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(coverage): add unit tests to boost diff coverage for pluggable token storage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(coverage): add unit tests to boost diff coverage for pluggable token storage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* pre-commit fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: fail-closed vault auth, deterministic gateway order, client_id 400, log sanitization, stale tests

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: sanitize all log args, opaque vault error message, demote debug checks, fix stale test mock

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* style: apply pre-commit black formatting to vault_router, vault_backend, tool_service

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: fail-closed Vault 5xx/4xx errors, guard store_tokens write, and thread jwt_teams_claim for admin token path alignment

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: update _capture_store mock signature to accept redirect_uri after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* alembic down_revision_change

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: add jwt_teams_claim=None to test_rpc_tool_invocation assertion

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): handle header-only Vault records in get_user_token and cap _refresh_locks size

B1: use .get() guards on 'token'/'access_token' keys — returns None instead of
KeyError when ICA writes a header-only record at the shared Vault path.

B2: evict oldest idle lock in _get_refresh_lock when dict reaches cache_max_size.
Held locks are never evicted to preserve refresh serialisation correctness.

Add four regression tests covering both fixes

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: regenerate .secrets.baseline after rebase onto main

Line-number drift only, no new or removed findings.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* fix: re-chain oauth_states team_id migration onto new main head

Rebase onto origin/main pulled in d8d7939e73e9 (tool-preview permission),
which shares db41939315aa as down_revision with 12d4a0c7789c, producing
two alembic heads. Re-point 12d4a0c7789c onto d8d7939e73e9 to restore a
single linear head.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* chore: regenerate .secrets.baseline to drop stale finding

Rerunning the detect-secrets pre-commit hook (not just the make target)
dropped a stale mcpgateway/routers/oauth_router.py:933 entry that no
longer matches current file content.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

---------

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Madhav Kandukuri <madhav165@gmail.com>
Co-authored-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
gcgoncalves pushed a commit that referenced this pull request Aug 24, 2026
…5599)

* feat: pluggable OAuth token storage with Database and Vault backends

Implement team-scoped OAuth token storage supporting Database and Vault backends with comprehensive test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove vault test files and reset config to main

- Remove docker-compose.vault-test.yml and Makefile.vault-tests
- Reset .gitignore and pyproject.toml to main branch versions
- These files were branch-specific testing artifacts

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove oauth_vault_lookup.sql script

Remove branch-specific SQL script used for vault testing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: use unified /oauth/callback endpoint for both OAuth authorization flows

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test: fix OAuth tests for teams parameter and state_data return fields

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* pre-commit fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct type annotations for optional Request parameters

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test cases add

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct Request parameter ordering in FastAPI routes

FastAPI requires Request parameters to come before optional parameters.
Moving request parameter to first position in oauth_callback and
vault_authorize functions to fix parameter ordering syntax error.

Resolves FastAPIError about invalid Request | None field type.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* feat(vault): resolve per-user creds from Vault for all auth types

Extend the Vault OAuth token backend so per-user credentials are read from
Vault for non-OAuth auth types too, not just OAuth authorization_code.

- VaultTokenBackend.get_user_auth_headers + TokenStorageService facade: read a
  per-user {header: value} dict stored under a 'headers' field at the same
  per-user Vault path used for OAuth tokens.
- tool_service invoke paths: for non-OAuth gateways, resolve per-user Vault
  headers FIRST, then fall back to the gateway-wide (admin-set) static auth.

Lets ICA write per-user bearer/basic/authheaders creds to Vault which CF injects
at invocation, consistent with the OAuth flow.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* chore: fix file headers for pre-commit compliance

- Add missing encoding declarations
- Add missing Location comments
- Update Copyright to contributors format
- Add missing SPDX-License-Identifier lines
- Remove stale Authors attributions per project standards

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove temporary documentation and test files

Remove working documentation, analysis files, and test scripts that
should not be committed to the repository:
- Design/analysis documents
- Testing guides and summaries
- Vault-specific helper scripts
- Review artifacts

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: restore manual-test-uaid-cross-gateway-auth.md accidentally deleted

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: remove leftover conflict markers from test_token_storage_service.py

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* rebase issue fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test failure

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: suppress false positive secret detection in oauth_router.py

Add pragma comment to 'credentials: include' fetch option.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 OAuth health check features to DatabaseTokenBackend after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 features (omit_resource, error handling) to DatabaseTokenBackend and fix tests after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 features to VaultTokenBackend

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add unit tests for PR #5244 VaultTokenBackend and refresh_helpers features

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add coverage tests for vault_backend edge cases (87% -> 92%)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add store_tokens edge case tests - reach 93% coverage target

- Test created_at preservation on token updates (line 298)
- Test cache invalidation with cache enabled (lines 324-326)
- vault_backend.py: 87% → 89%
- Overall token_backends: 92% → 93% ✅

Achieves CI/CD 93% coverage requirement.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: clean up token_storage_service.py after rebase conflicts

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* fix(db): update migration 12d4a0c7789c to point to correct parent c9f8e7d6a4b3

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): fix failing tests after rebase - mock EmailUser query and learned_audience

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: update token backend tests and remove duplicate import

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): address PR #5599 review findings

- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: update uv.lock after pre-commit formatting

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct Revises comment from c9f8e7d6a4b3 to e4f5a6b7c8d9 in add_team_id_to_oauth_states migration

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): fix learned_aud persistence, vault refresh erasure, vault_router team context, manual refresh user_context, and OAuthError info leak (CWE-209)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: address non-blocking review comments for pluggable token storage

- Add URL encoding for team_id in Vault KV paths to prevent potential path
  traversal if future callers pass slugs/display names instead of UUIDs
- Add backend capability check before DB session creation to avoid unnecessary
  round-trips on default database backend which doesn't implement per-user auth headers

Both changes are performance/security hardening with no functional impact on
current callers.

Related: PR #5599 review comments (suggestions 1-2)
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: consolidate OAuth token backend code duplication and fix tests

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): address round-3 blocking review findings (B2-B5, S6, S8)

- B4: hard-fail OAuth callback when identity missing after state consumed (CWE-287)
- B2: add ORDER BY to team queries for stable Vault path key
- B3: remove cross-path 401 fallback violating #5598 no-dual-backend rule
- B5: formalise get_user_auth_headers on AbstractTokenBackend; drop getattr dispatch
- S6: decrypt client_secret in VaultTokenBackend before token endpoint call
- S8: omit teams claim from JWT when team_id is None to prevent scope widening

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): update TestGatewayService401Retry to reflect B3 no-fallback behaviour

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* style: fix pre-commit formatting in oauth_router.py (remove spaces in **() expression)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): address round-4 review findings (B2a–B2g, B3, S10–S13)

- B2a: raise OAuthError on client_secret decryption failure (fail-closed)
- B2b: add per-key asyncio refresh lock to prevent invalid_grant races
- B2c: expire cache entry on revoke instead of deleting (cross-worker safety)
- B2d: replace raw str(e) with generic user message in OAuth callback (CWE-209)
- B2e: pass popup=True in vault_authorize to avoid overwriting session cookie
- B2f: add GET /vault/authorize/{server_id} to _PERMISSION_PATTERNS (GATEWAYS_READ)
- B2g: enforce Server row visibility before resolving gateway in vault_authorize
- B3: restore two deleted regression tests in test_db_backend.py
- S10: add TODO comment documenting Phase 2 wiring for get/store_oauth_credentials
- S11: extract duplicated Vault auth-header fallback into _resolve_vault_auth_headers()
- S12: standardise store_tokens() return type to TokenRecord across all backends
- S13: update security-features.md to reflect VaultTokenBackend implementation status

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): update tests for refresh-lock split and revoke expire-in-place, fix ruff/pylint warnings

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): update tests for refresh-lock split and revoke expire-in-place, fix ruff/pylint warnings

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: address round-6 security review — CWE-863 vault path revocation, fail-closed vault errors, refresh-lock race, expire-in-place cache, read method error handling, revoke distinguishability, server-visibility deny-path test

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: address self-review findings — demote debug logs, fix Optional[str] types, add nosec suppressions, clarify vault router comment

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: pre-commit formatting, update secrets baseline, skip expired sunset test #2754

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test: skip test_get_redis_client_with_circuit_open — pre-existing failure on main, unrelated to this PR

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(coverage): add unit tests to boost diff coverage for pluggable token storage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(coverage): add unit tests to boost diff coverage for pluggable token storage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* pre-commit fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: fail-closed vault auth, deterministic gateway order, client_id 400, log sanitization, stale tests

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: sanitize all log args, opaque vault error message, demote debug checks, fix stale test mock

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* style: apply pre-commit black formatting to vault_router, vault_backend, tool_service

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: fail-closed Vault 5xx/4xx errors, guard store_tokens write, and thread jwt_teams_claim for admin token path alignment

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: update _capture_store mock signature to accept redirect_uri after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* alembic down_revision_change

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: add jwt_teams_claim=None to test_rpc_tool_invocation assertion

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): handle header-only Vault records in get_user_token and cap _refresh_locks size

B1: use .get() guards on 'token'/'access_token' keys — returns None instead of
KeyError when ICA writes a header-only record at the shared Vault path.

B2: evict oldest idle lock in _get_refresh_lock when dict reaches cache_max_size.
Held locks are never evicted to preserve refresh serialisation correctness.

Add four regression tests covering both fixes

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: regenerate .secrets.baseline after rebase onto main

Line-number drift only, no new or removed findings.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* fix: re-chain oauth_states team_id migration onto new main head

Rebase onto origin/main pulled in d8d7939e73e9 (tool-preview permission),
which shares db41939315aa as down_revision with 12d4a0c7789c, producing
two alembic heads. Re-point 12d4a0c7789c onto d8d7939e73e9 to restore a
single linear head.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* chore: regenerate .secrets.baseline to drop stale finding

Rerunning the detect-secrets pre-commit hook (not just the make target)
dropped a stale mcpgateway/routers/oauth_router.py:933 entry that no
longer matches current file content.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

---------

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Madhav Kandukuri <madhav165@gmail.com>
Co-authored-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
gcgoncalves pushed a commit that referenced this pull request Aug 24, 2026
…5599)

* feat: pluggable OAuth token storage with Database and Vault backends

Implement team-scoped OAuth token storage supporting Database and Vault backends with comprehensive test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove vault test files and reset config to main

- Remove docker-compose.vault-test.yml and Makefile.vault-tests
- Reset .gitignore and pyproject.toml to main branch versions
- These files were branch-specific testing artifacts

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove oauth_vault_lookup.sql script

Remove branch-specific SQL script used for vault testing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: use unified /oauth/callback endpoint for both OAuth authorization flows

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test: fix OAuth tests for teams parameter and state_data return fields

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* pre-commit fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct type annotations for optional Request parameters

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test cases add

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct Request parameter ordering in FastAPI routes

FastAPI requires Request parameters to come before optional parameters.
Moving request parameter to first position in oauth_callback and
vault_authorize functions to fix parameter ordering syntax error.

Resolves FastAPIError about invalid Request | None field type.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* feat(vault): resolve per-user creds from Vault for all auth types

Extend the Vault OAuth token backend so per-user credentials are read from
Vault for non-OAuth auth types too, not just OAuth authorization_code.

- VaultTokenBackend.get_user_auth_headers + TokenStorageService facade: read a
  per-user {header: value} dict stored under a 'headers' field at the same
  per-user Vault path used for OAuth tokens.
- tool_service invoke paths: for non-OAuth gateways, resolve per-user Vault
  headers FIRST, then fall back to the gateway-wide (admin-set) static auth.

Lets ICA write per-user bearer/basic/authheaders creds to Vault which CF injects
at invocation, consistent with the OAuth flow.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* chore: fix file headers for pre-commit compliance

- Add missing encoding declarations
- Add missing Location comments
- Update Copyright to contributors format
- Add missing SPDX-License-Identifier lines
- Remove stale Authors attributions per project standards

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove temporary documentation and test files

Remove working documentation, analysis files, and test scripts that
should not be committed to the repository:
- Design/analysis documents
- Testing guides and summaries
- Vault-specific helper scripts
- Review artifacts

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: restore manual-test-uaid-cross-gateway-auth.md accidentally deleted

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: remove leftover conflict markers from test_token_storage_service.py

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* rebase issue fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test failure

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: suppress false positive secret detection in oauth_router.py

Add pragma comment to 'credentials: include' fetch option.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 OAuth health check features to DatabaseTokenBackend after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 features (omit_resource, error handling) to DatabaseTokenBackend and fix tests after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 features to VaultTokenBackend

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add unit tests for PR #5244 VaultTokenBackend and refresh_helpers features

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add coverage tests for vault_backend edge cases (87% -> 92%)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add store_tokens edge case tests - reach 93% coverage target

- Test created_at preservation on token updates (line 298)
- Test cache invalidation with cache enabled (lines 324-326)
- vault_backend.py: 87% → 89%
- Overall token_backends: 92% → 93% ✅

Achieves CI/CD 93% coverage requirement.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: clean up token_storage_service.py after rebase conflicts

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* fix(db): update migration 12d4a0c7789c to point to correct parent c9f8e7d6a4b3

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): fix failing tests after rebase - mock EmailUser query and learned_audience

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: update token backend tests and remove duplicate import

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): address PR #5599 review findings

- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: update uv.lock after pre-commit formatting

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct Revises comment from c9f8e7d6a4b3 to e4f5a6b7c8d9 in add_team_id_to_oauth_states migration

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): fix learned_aud persistence, vault refresh erasure, vault_router team context, manual refresh user_context, and OAuthError info leak (CWE-209)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: address non-blocking review comments for pluggable token storage

- Add URL encoding for team_id in Vault KV paths to prevent potential path
  traversal if future callers pass slugs/display names instead of UUIDs
- Add backend capability check before DB session creation to avoid unnecessary
  round-trips on default database backend which doesn't implement per-user auth headers

Both changes are performance/security hardening with no functional impact on
current callers.

Related: PR #5599 review comments (suggestions 1-2)
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: consolidate OAuth token backend code duplication and fix tests

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): address round-3 blocking review findings (B2-B5, S6, S8)

- B4: hard-fail OAuth callback when identity missing after state consumed (CWE-287)
- B2: add ORDER BY to team queries for stable Vault path key
- B3: remove cross-path 401 fallback violating #5598 no-dual-backend rule
- B5: formalise get_user_auth_headers on AbstractTokenBackend; drop getattr dispatch
- S6: decrypt client_secret in VaultTokenBackend before token endpoint call
- S8: omit teams claim from JWT when team_id is None to prevent scope widening

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): update TestGatewayService401Retry to reflect B3 no-fallback behaviour

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* style: fix pre-commit formatting in oauth_router.py (remove spaces in **() expression)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): address round-4 review findings (B2a–B2g, B3, S10–S13)

- B2a: raise OAuthError on client_secret decryption failure (fail-closed)
- B2b: add per-key asyncio refresh lock to prevent invalid_grant races
- B2c: expire cache entry on revoke instead of deleting (cross-worker safety)
- B2d: replace raw str(e) with generic user message in OAuth callback (CWE-209)
- B2e: pass popup=True in vault_authorize to avoid overwriting session cookie
- B2f: add GET /vault/authorize/{server_id} to _PERMISSION_PATTERNS (GATEWAYS_READ)
- B2g: enforce Server row visibility before resolving gateway in vault_authorize
- B3: restore two deleted regression tests in test_db_backend.py
- S10: add TODO comment documenting Phase 2 wiring for get/store_oauth_credentials
- S11: extract duplicated Vault auth-header fallback into _resolve_vault_auth_headers()
- S12: standardise store_tokens() return type to TokenRecord across all backends
- S13: update security-features.md to reflect VaultTokenBackend implementation status

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): update tests for refresh-lock split and revoke expire-in-place, fix ruff/pylint warnings

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): update tests for refresh-lock split and revoke expire-in-place, fix ruff/pylint warnings

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: address round-6 security review — CWE-863 vault path revocation, fail-closed vault errors, refresh-lock race, expire-in-place cache, read method error handling, revoke distinguishability, server-visibility deny-path test

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: address self-review findings — demote debug logs, fix Optional[str] types, add nosec suppressions, clarify vault router comment

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: pre-commit formatting, update secrets baseline, skip expired sunset test #2754

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test: skip test_get_redis_client_with_circuit_open — pre-existing failure on main, unrelated to this PR

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(coverage): add unit tests to boost diff coverage for pluggable token storage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(coverage): add unit tests to boost diff coverage for pluggable token storage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* pre-commit fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: fail-closed vault auth, deterministic gateway order, client_id 400, log sanitization, stale tests

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: sanitize all log args, opaque vault error message, demote debug checks, fix stale test mock

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* style: apply pre-commit black formatting to vault_router, vault_backend, tool_service

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: fail-closed Vault 5xx/4xx errors, guard store_tokens write, and thread jwt_teams_claim for admin token path alignment

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: update _capture_store mock signature to accept redirect_uri after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* alembic down_revision_change

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: add jwt_teams_claim=None to test_rpc_tool_invocation assertion

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): handle header-only Vault records in get_user_token and cap _refresh_locks size

B1: use .get() guards on 'token'/'access_token' keys — returns None instead of
KeyError when ICA writes a header-only record at the shared Vault path.

B2: evict oldest idle lock in _get_refresh_lock when dict reaches cache_max_size.
Held locks are never evicted to preserve refresh serialisation correctness.

Add four regression tests covering both fixes

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: regenerate .secrets.baseline after rebase onto main

Line-number drift only, no new or removed findings.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* fix: re-chain oauth_states team_id migration onto new main head

Rebase onto origin/main pulled in d8d7939e73e9 (tool-preview permission),
which shares db41939315aa as down_revision with 12d4a0c7789c, producing
two alembic heads. Re-point 12d4a0c7789c onto d8d7939e73e9 to restore a
single linear head.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* chore: regenerate .secrets.baseline to drop stale finding

Rerunning the detect-secrets pre-commit hook (not just the make target)
dropped a stale mcpgateway/routers/oauth_router.py:933 entry that no
longer matches current file content.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

---------

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Madhav Kandukuri <madhav165@gmail.com>
Co-authored-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
madhu-mohan-jaishankar pushed a commit that referenced this pull request Aug 31, 2026
…5599)

* feat: pluggable OAuth token storage with Database and Vault backends

Implement team-scoped OAuth token storage supporting Database and Vault backends with comprehensive test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove vault test files and reset config to main

- Remove docker-compose.vault-test.yml and Makefile.vault-tests
- Reset .gitignore and pyproject.toml to main branch versions
- These files were branch-specific testing artifacts

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove oauth_vault_lookup.sql script

Remove branch-specific SQL script used for vault testing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: use unified /oauth/callback endpoint for both OAuth authorization flows

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test: fix OAuth tests for teams parameter and state_data return fields

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* pre-commit fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct type annotations for optional Request parameters

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test cases add

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct Request parameter ordering in FastAPI routes

FastAPI requires Request parameters to come before optional parameters.
Moving request parameter to first position in oauth_callback and
vault_authorize functions to fix parameter ordering syntax error.

Resolves FastAPIError about invalid Request | None field type.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* feat(vault): resolve per-user creds from Vault for all auth types

Extend the Vault OAuth token backend so per-user credentials are read from
Vault for non-OAuth auth types too, not just OAuth authorization_code.

- VaultTokenBackend.get_user_auth_headers + TokenStorageService facade: read a
  per-user {header: value} dict stored under a 'headers' field at the same
  per-user Vault path used for OAuth tokens.
- tool_service invoke paths: for non-OAuth gateways, resolve per-user Vault
  headers FIRST, then fall back to the gateway-wide (admin-set) static auth.

Lets ICA write per-user bearer/basic/authheaders creds to Vault which CF injects
at invocation, consistent with the OAuth flow.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>

* chore: fix file headers for pre-commit compliance

- Add missing encoding declarations
- Add missing Location comments
- Update Copyright to contributors format
- Add missing SPDX-License-Identifier lines
- Remove stale Authors attributions per project standards

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: remove temporary documentation and test files

Remove working documentation, analysis files, and test scripts that
should not be committed to the repository:
- Design/analysis documents
- Testing guides and summaries
- Vault-specific helper scripts
- Review artifacts

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: restore manual-test-uaid-cross-gateway-auth.md accidentally deleted

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: remove leftover conflict markers from test_token_storage_service.py

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* rebase issue fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test failure

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: suppress false positive secret detection in oauth_router.py

Add pragma comment to 'credentials: include' fetch option.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 OAuth health check features to DatabaseTokenBackend after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 features (omit_resource, error handling) to DatabaseTokenBackend and fix tests after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): port PR #5244 features to VaultTokenBackend

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add unit tests for PR #5244 VaultTokenBackend and refresh_helpers features

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add coverage tests for vault_backend edge cases (87% -> 92%)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): add store_tokens edge case tests - reach 93% coverage target

- Test created_at preservation on token updates (line 298)
- Test cache invalidation with cache enabled (lines 324-326)
- vault_backend.py: 87% → 89%
- Overall token_backends: 92% → 93% ✅

Achieves CI/CD 93% coverage requirement.

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: clean up token_storage_service.py after rebase conflicts

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* fix(db): update migration 12d4a0c7789c to point to correct parent c9f8e7d6a4b3

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): fix failing tests after rebase - mock EmailUser query and learned_audience

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: update token backend tests and remove duplicate import

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test coverage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): address PR #5599 review findings

- fix(vault): add _enforce_gateway_access() to vault_authorize to enforce
  gateway visibility/team-membership checks before initiating OAuth flow;
  without this any authenticated user could start an OAuth flow against a
  private gateway they are not a member of (Finding #1)

- test(vault): add deny-path security regression tests for vault_router
  covering wrong-team, public-only token, server-not-found, no-oauth-gateways,
  valid-member, and admin scenarios (AGENTS.md requirement)

- fix(oauth): revert fetch_tools_after_oauth detail=f'...{e}' back to generic
  'Failed to fetch tools' to avoid leaking internal hostnames/token audience/
  scope names to callers; add exc_info=True to GatewayConnectionError branch
  so full detail reaches operator logs (Finding #4)

- fix(oauth): correct misleading comment on callback-minted session JWT;
  document admin-bypass behaviour and jwt_teams_claim path selection
  (Finding #2)

- fix(db): repoint migration 12d4a0c7789c down_revision from c9f8e7d6a4b3 to
  e4f5a6b7c8d9 (current main head) to resolve multiple-heads error (Finding #3)

Closes #5599 review findings #1 #2 #3 #4

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: update uv.lock after pre-commit formatting

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: correct Revises comment from c9f8e7d6a4b3 to e4f5a6b7c8d9 in add_team_id_to_oauth_states migration

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): fix learned_aud persistence, vault refresh erasure, vault_router team context, manual refresh user_context, and OAuthError info leak (CWE-209)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: address non-blocking review comments for pluggable token storage

- Add URL encoding for team_id in Vault KV paths to prevent potential path
  traversal if future callers pass slugs/display names instead of UUIDs
- Add backend capability check before DB session creation to avoid unnecessary
  round-trips on default database backend which doesn't implement per-user auth headers

Both changes are performance/security hardening with no functional impact on
current callers.

Related: PR #5599 review comments (suggestions 1-2)
Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* refactor: consolidate OAuth token backend code duplication and fix tests

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(oauth): address round-3 blocking review findings (B2-B5, S6, S8)

- B4: hard-fail OAuth callback when identity missing after state consumed (CWE-287)
- B2: add ORDER BY to team queries for stable Vault path key
- B3: remove cross-path 401 fallback violating #5598 no-dual-backend rule
- B5: formalise get_user_auth_headers on AbstractTokenBackend; drop getattr dispatch
- S6: decrypt client_secret in VaultTokenBackend before token endpoint call
- S8: omit teams claim from JWT when team_id is None to prevent scope widening

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(oauth): update TestGatewayService401Retry to reflect B3 no-fallback behaviour

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* style: fix pre-commit formatting in oauth_router.py (remove spaces in **() expression)

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): address round-4 review findings (B2a–B2g, B3, S10–S13)

- B2a: raise OAuthError on client_secret decryption failure (fail-closed)
- B2b: add per-key asyncio refresh lock to prevent invalid_grant races
- B2c: expire cache entry on revoke instead of deleting (cross-worker safety)
- B2d: replace raw str(e) with generic user message in OAuth callback (CWE-209)
- B2e: pass popup=True in vault_authorize to avoid overwriting session cookie
- B2f: add GET /vault/authorize/{server_id} to _PERMISSION_PATTERNS (GATEWAYS_READ)
- B2g: enforce Server row visibility before resolving gateway in vault_authorize
- B3: restore two deleted regression tests in test_db_backend.py
- S10: add TODO comment documenting Phase 2 wiring for get/store_oauth_credentials
- S11: extract duplicated Vault auth-header fallback into _resolve_vault_auth_headers()
- S12: standardise store_tokens() return type to TokenRecord across all backends
- S13: update security-features.md to reflect VaultTokenBackend implementation status

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): update tests for refresh-lock split and revoke expire-in-place, fix ruff/pylint warnings

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): update tests for refresh-lock split and revoke expire-in-place, fix ruff/pylint warnings

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: address round-6 security review — CWE-863 vault path revocation, fail-closed vault errors, refresh-lock race, expire-in-place cache, read method error handling, revoke distinguishability, server-visibility deny-path test

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: address self-review findings — demote debug logs, fix Optional[str] types, add nosec suppressions, clarify vault router comment

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: pre-commit formatting, update secrets baseline, skip expired sunset test #2754

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test: skip test_get_redis_client_with_circuit_open — pre-existing failure on main, unrelated to this PR

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(coverage): add unit tests to boost diff coverage for pluggable token storage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* test(coverage): add unit tests to boost diff coverage for pluggable token storage

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* pre-commit fix

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: fail-closed vault auth, deterministic gateway order, client_id 400, log sanitization, stale tests

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: sanitize all log args, opaque vault error message, demote debug checks, fix stale test mock

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* style: apply pre-commit black formatting to vault_router, vault_backend, tool_service

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: fail-closed Vault 5xx/4xx errors, guard store_tokens write, and thread jwt_teams_claim for admin token path alignment

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: update _capture_store mock signature to accept redirect_uri after rebase

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* alembic down_revision_change

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix: add jwt_teams_claim=None to test_rpc_tool_invocation assertion

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* fix(vault): handle header-only Vault records in get_user_token and cap _refresh_locks size

B1: use .get() guards on 'token'/'access_token' keys — returns None instead of
KeyError when ICA writes a header-only record at the shared Vault path.

B2: evict oldest idle lock in _get_refresh_lock when dict reaches cache_max_size.
Held locks are never evicted to preserve refresh serialisation correctness.

Add four regression tests covering both fixes

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>

* chore: regenerate .secrets.baseline after rebase onto main

Line-number drift only, no new or removed findings.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* fix: re-chain oauth_states team_id migration onto new main head

Rebase onto origin/main pulled in d8d7939e73e9 (tool-preview permission),
which shares db41939315aa as down_revision with 12d4a0c7789c, producing
two alembic heads. Re-point 12d4a0c7789c onto d8d7939e73e9 to restore a
single linear head.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

* chore: regenerate .secrets.baseline to drop stale finding

Rerunning the detect-secrets pre-commit hook (not just the make target)
dropped a stale mcpgateway/routers/oauth_router.py:933 entry that no
longer matches current file content.

Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>

---------

Signed-off-by: Rakhi Dutta <rakhibiswas@yahoo.com>
Signed-off-by: Madhav Kandukuri <madhav165@gmail.com>
Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Madhav Kandukuri <madhav165@gmail.com>
Co-authored-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
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.

1 participant