Audit sandbox proxy mediation sources#935
Conversation
|
🚅 Deployed to the aileron-pr-935 environment in aileron
1 service not affected by this PR
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #935 +/- ##
==========================================
+ Coverage 81.14% 81.86% +0.72%
==========================================
Files 263 263
Lines 28410 28451 +41
==========================================
+ Hits 23052 23291 +239
+ Misses 3885 3670 -215
- Partials 1473 1490 +17
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
📝 WalkthroughWalkthroughThis PR extends sandbox HTTPS data-plane audit logging by introducing ChangesProxy source audit tracking for HTTPS data-plane sandbox operations
🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 golangci-lint (2.12.2)level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies" Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
internal/app/handlers_connector_operations_test.go (1)
772-781: ⚡ Quick winAssert event type in this audit check as well.
This block validates payload source, but a wrong event type could still pass. Add an explicit
EventTypeConnectorProxyProxiedassertion for this case.✅ Suggested test tightening
if len(events) != 1 { t.Fatalf("events = %d, want 1", len(events)) } + if events[0].EventType != model.EventTypeConnectorProxyProxied { + t.Fatalf("event type = %q, want %q", events[0].EventType, model.EventTypeConnectorProxyProxied) + } if got := events[0].Payload["aileron.proxy.source"]; got != sandboxProxySourceDaemonRequestBoundary { t.Errorf("proxy source = %v, want %q", got, sandboxProxySourceDaemonRequestBoundary) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/app/handlers_connector_operations_test.go` around lines 772 - 781, The test currently only checks the event payload source but not the event type, so add an explicit assertion that the single returned event has Type == audit.EventTypeConnectorProxyProxied; locate the ListEvents call and the resulting events slice (events, err := auditStore.ListEvents(...)) and after verifying len(events) == 1 add a check comparing events[0].Type (or events[0].EventType depending on struct) to audit.EventTypeConnectorProxyProxied and fail the test with a clear message if it does not match.internal/app/handlers_sandbox_forward_proxy.go (1)
198-214: ⚡ Quick winPrefer sentinel errors with
errors.Isfor reason mapping.
connector_specs_*andrequest_body_*audit reasons are currently derived by substring-matchingerr.Error()("invalid","read failed"), and the tests assert those exact message strings—small message wording changes can silently flip reason codes. Use sentinel errors +%wwrapping and switch the helpers toerrors.Is.♻️ Proposed hardening diff
var errSandboxForwardProxyAmbiguousOperation = errors.New("sandbox proxy decrypted request matched multiple connector operations") +var ( + errSandboxForwardProxyConnectorSpecsUnavailable = errors.New("connector specs unavailable") + errSandboxForwardProxyConnectorSpecsInvalid = errors.New("connector specs invalid") + errSandboxForwardProxyBodyReadFailed = errors.New("sandbox proxy decrypted request body read failed") + errSandboxForwardProxyBodyTooLarge = errors.New("sandbox proxy decrypted request body exceeded size limit") +) func (s *apiServer) matchSandboxForwardProxyOperation(method string, upstream *url.URL) (sandboxForwardProxyOperationMatch, bool, error) { specs, err := s.loadConnectorOperationSpecs() if err != nil { - return sandboxForwardProxyOperationMatch{}, false, fmt.Errorf("connector specs unavailable") + return sandboxForwardProxyOperationMatch{}, false, fmt.Errorf("%w", errSandboxForwardProxyConnectorSpecsUnavailable) } tools, err := discovery.SpecConnectorTools(specs) if err != nil { - return sandboxForwardProxyOperationMatch{}, false, fmt.Errorf("connector specs invalid") + return sandboxForwardProxyOperationMatch{}, false, fmt.Errorf("%w", errSandboxForwardProxyConnectorSpecsInvalid) } ... } func sandboxForwardProxyMatchErrorReason(err error) string { switch { case err == nil: return "" - case strings.Contains(err.Error(), "invalid"): + case errors.Is(err, errSandboxForwardProxyConnectorSpecsInvalid): return "connector_specs_invalid" default: return "connector_specs_unavailable" } } func sandboxForwardProxyBodyRejectReason(err error) string { - if err != nil && strings.Contains(err.Error(), "read failed") { + if errors.Is(err, errSandboxForwardProxyBodyReadFailed) { return "request_body_read_failed" } return "request_body_too_large" } func readSandboxForwardProxyRequestBody(decrypted *http.Request) ([]byte, string, error) { ... if err != nil { - return nil, "", fmt.Errorf("sandbox proxy decrypted request body read failed") + return nil, "", fmt.Errorf("%w", errSandboxForwardProxyBodyReadFailed) } if len(body) > sandboxForwardProxyMaxRequestBytes { - return nil, "", fmt.Errorf("sandbox proxy decrypted request body exceeded size limit") + return nil, "", fmt.Errorf("%w", errSandboxForwardProxyBodyTooLarge) } ... }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/app/handlers_sandbox_forward_proxy.go` around lines 198 - 214, Replace fragile substring-based error checks in sandboxForwardProxyMatchErrorReason and sandboxForwardProxyBodyRejectReason with sentinel errors and errors.Is checks: define package-level sentinel errors (e.g. ErrConnectorSpecsInvalid, ErrConnectorSpecsUnavailable, ErrRequestBodyReadFailed, ErrRequestBodyTooLarge), ensure any places that create these errors wrap underlying errors with fmt.Errorf("%w: ...", sentinel) or errors.Join so original context is preserved, then update the two helpers to use errors.Is(err, Err...) instead of strings.Contains(err.Error(), ...). Also update callers/tests to return or assert against the sentinel errors or wrapped variants so the reason mapping remains stable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@internal/app/handlers_connector_operations_test.go`:
- Around line 772-781: The test currently only checks the event payload source
but not the event type, so add an explicit assertion that the single returned
event has Type == audit.EventTypeConnectorProxyProxied; locate the ListEvents
call and the resulting events slice (events, err := auditStore.ListEvents(...))
and after verifying len(events) == 1 add a check comparing events[0].Type (or
events[0].EventType depending on struct) to audit.EventTypeConnectorProxyProxied
and fail the test with a clear message if it does not match.
In `@internal/app/handlers_sandbox_forward_proxy.go`:
- Around line 198-214: Replace fragile substring-based error checks in
sandboxForwardProxyMatchErrorReason and sandboxForwardProxyBodyRejectReason with
sentinel errors and errors.Is checks: define package-level sentinel errors (e.g.
ErrConnectorSpecsInvalid, ErrConnectorSpecsUnavailable,
ErrRequestBodyReadFailed, ErrRequestBodyTooLarge), ensure any places that create
these errors wrap underlying errors with fmt.Errorf("%w: ...", sentinel) or
errors.Join so original context is preserved, then update the two helpers to use
errors.Is(err, Err...) instead of strings.Contains(err.Error(), ...). Also
update callers/tests to return or assert against the sentinel errors or wrapped
variants so the reason mapping remains stable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: ca16aadf-4c32-4e2e-a1d9-d5edd7c4a625
📒 Files selected for processing (7)
docs/src/content/docs/adr/0019-v4-https-data-plane.mddocs/src/content/docs/guides/observability.mdinternal/app/handlers_connector_operations.gointernal/app/handlers_connector_operations_test.gointernal/app/handlers_sandbox_forward_proxy.gointernal/app/handlers_sandbox_forward_proxy_test.gointernal/model/model.go
Summary
aileron.proxy.sourceto connector-resolved HTTPS proxy audit events so generated shims, the daemon request boundary, and transparent CONNECT/TLS can be distinguished.sandbox.proxy.rejectedfor transparent proxy attempts rejected before a connector operation is uniquely resolved.Verification
git diff --checkgo test ./internal/app -run 'TestRunConnectorOperation_BodylessGETProxies|TestRecordSandboxProxyRequest_APIKey|TestRecordSandboxProxyRequest_CredentialFailures|TestRecordSandboxProxyRequest_AuditsRootPath|TestSandboxForwardProxy' -count=1go test ./internal/app ./internal/audit ./internal/observability ./internal/server -coverprofile=/private/tmp/aileron-898.covergo test ./internal/...go test ./cmd/aileron/... ./cmd/aileron-enclave/... ./cmd/aileron-mcp/... ./sdk/go/...pnpm run checknode design/build/generate-tokens.mjspnpm run buildLocal CodeRabbit CLI note:
coderabbit --versionreports0.5.3;coderabbit auth status --agentreports not authenticated, so local CodeRabbit review could not run. I performed a manual review pass and fixed the docs event-family count before opening this PR.Closes a narrow #898 slice. #898 should remain open for sandbox launch/container lifecycle, approval, and eventual shell-intercept audit shapes.
Summary by CodeRabbit
Documentation
New Features