Skip to content

Audit sandbox proxy mediation sources#935

Merged
ALRubinger merged 1 commit into
mainfrom
feat/sandbox-proxy-audit-schema
Jun 4, 2026
Merged

Audit sandbox proxy mediation sources#935
ALRubinger merged 1 commit into
mainfrom
feat/sandbox-proxy-audit-schema

Conversation

@ALRubinger

@ALRubinger ALRubinger commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add aileron.proxy.source to connector-resolved HTTPS proxy audit events so generated shims, the daemon request boundary, and transparent CONNECT/TLS can be distinguished.
  • Add sandbox.proxy.rejected for transparent proxy attempts rejected before a connector operation is uniquely resolved.
  • Document sandbox HTTPS data-plane audit attributes and redaction rules in the observability guide and ADR-0019.

Verification

  • git diff --check
  • go test ./internal/app -run 'TestRunConnectorOperation_BodylessGETProxies|TestRecordSandboxProxyRequest_APIKey|TestRecordSandboxProxyRequest_CredentialFailures|TestRecordSandboxProxyRequest_AuditsRootPath|TestSandboxForwardProxy' -count=1
  • go test ./internal/app ./internal/audit ./internal/observability ./internal/server -coverprofile=/private/tmp/aileron-898.cover
  • go test ./internal/...
  • go test ./cmd/aileron/... ./cmd/aileron-enclave/... ./cmd/aileron-mcp/... ./sdk/go/...
  • docs pnpm run check
  • node design/build/generate-tokens.mjs
  • docs pnpm run build

Local CodeRabbit CLI note: coderabbit --version reports 0.5.3; coderabbit auth status --agent reports 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

    • Updated observability guide with new sandbox HTTPS data-plane audit event families and attribute schema for proxy operations and rejections.
    • Added architectural decision record documenting HTTPS data-plane event sources in audit payloads.
  • New Features

    • Enhanced audit logging for sandbox proxy rejections, now tracking proxy source and detailed rejection reasons before connector resolution.
    • Improved rejection reason classification for operation matching and request validation failures.

@railway-app
railway-app Bot temporarily deployed to aileron / aileron-pr-935 June 4, 2026 15:55 Destroyed
@railway-app

railway-app Bot commented Jun 4, 2026

Copy link
Copy Markdown

🚅 Deployed to the aileron-pr-935 environment in aileron

Service Status Web Updated (UTC)
docs ✅ Success (View Logs) Web Jun 4, 2026 at 3:58 pm
1 service not affected by this PR
  • aileron-internal

@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.52542% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.86%. Comparing base (e9ccb25) to head (fd0dcba).
✅ All tests successful. No failed tests found.

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     
Flag Coverage Δ
integration 14.57% <0.00%> (+6.51%) ⬆️
unit 78.39% <91.52%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extends sandbox HTTPS data-plane audit logging by introducing aileron.proxy.source tracking across proxy rejection and proxied-request paths. It adds a new EventTypeSandboxProxyRejected event type, threads source constants from handler entry points through the execution pipeline, updates audit recording helpers, enhances error handling in the forward proxy boundary with stable rejection reasons, and validates the changes through comprehensive test coverage and documentation updates.

Changes

Proxy source audit tracking for HTTPS data-plane sandbox operations

Layer / File(s) Summary
Audit event type and proxy source constants
internal/model/model.go, internal/app/handlers_connector_operations.go
EventTypeSandboxProxyRejected constant introduced; three proxy source constants defined to identify origins: generated_connector_shim, daemon_request_boundary, and transparent_connect_tls.
Handler entry points with source threading
internal/app/handlers_connector_operations.go
RunConnectorOperation and RecordSandboxProxyRequest updated to pass appropriate source values to executeSandboxProxyRequest; executor signature expanded to accept source parameter.
Sandbox proxy execution with source threading
internal/app/handlers_connector_operations.go
Source parameter threaded through executeSandboxProxyRequest to all rejection audit paths (invalid upstream, transport failure, read failure, response too large) and proxied path; passed downstream to credential injection and recording helpers.
Credential injection with source audit
internal/app/handlers_connector_operations.go
injectSandboxProxyCredential signature updated to accept source; all rejection audit calls (binding unavailable, required, resolution failures, kind mismatches, unsupported kinds) include source parameter.
Audit recording helpers with source
internal/app/handlers_connector_operations.go
recordSandboxProxyRejected and recordSandboxProxyProxied updated to accept source and populate aileron.proxy.source in audit payloads; new recordSandboxProxyUnresolvedRejected helper introduced for rejections before connector resolution.
Forward proxy rejection handling and reason helpers
internal/app/handlers_sandbox_forward_proxy.go
handleSandboxForwardProxyDecrypted records specific rejection reasons for connector match errors, no matches, and body read/size failures; adds sandboxForwardProxyMatchErrorReason and sandboxForwardProxyBodyRejectReason helpers to derive stable reason codes from error details.
Connector operations tests with source validation
internal/app/handlers_connector_operations_test.go
Extends RunConnectorOperation and RecordSandboxProxyRequest test cases to capture and verify aileron.proxy.source in audit payloads across credential injection, failure, and matching scenarios.
Forward proxy tests with audit and rejection validation
internal/app/handlers_sandbox_forward_proxy_test.go
Adds in-memory audit store to capture and verify sandbox forward proxy audit events; updates successful proxied test to verify event type and source while ensuring payload does not leak credentials or queries; adds test for no-match rejection auditing and reason helper functions.
Documentation for proxy source audit events and schema
docs/src/content/docs/adr/0019-v4-https-data-plane.md, docs/src/content/docs/guides/observability.md
ADR updated to document sandbox.proxy.rejected event for early transparent-proxy failures; observability guide extended to list Sandbox HTTPS data plane as fifth audit event family with detailed span-attribute table for aileron.proxy.* and rejection metadata fields.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • ALRubinger/aileron#896: This PR directly implements the audit-schema changes for HTTPS data-plane event sources and sandbox.proxy.rejected events described in #896.

Possibly related PRs

  • ALRubinger/aileron#903: Both PRs modify internal/app/handlers_connector_operations.go connector operations handler logic, with #935 extending connector operation endpoint to tag sandbox HTTPS proxy rejections via aileron.proxy.source.
  • ALRubinger/aileron#918: Both PRs modify the sandbox HTTPS proxy execution path in handlers_connector_operations.go including executeSandboxProxyRequest and audit recording for proxied/rejected outcomes.
  • ALRubinger/aileron#931: Both PRs enhance the sandbox forward proxy CONNECT/TLS rejection handling in handlers_sandbox_forward_proxy.go to emit and validate rejection audit behavior with sandbox.proxy.rejected events and stable reject reasons.

Poem

🐰 A source now tagged with every proxy trace,
Through shims and boundaries, each request finds its place,
Rejections logged with reasons clean and clear,
From credential checks to forward proxies near!
Audit trails bloom—transparency wins the day. 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Audit sandbox proxy mediation sources' directly corresponds to the main objective of adding audit instrumentation for sandbox HTTPS proxy mediation and the new aileron.proxy.source attribute to distinguish between different proxy sources.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/sandbox-proxy-audit-schema

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 @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
internal/app/handlers_connector_operations_test.go (1)

772-781: ⚡ Quick win

Assert event type in this audit check as well.

This block validates payload source, but a wrong event type could still pass. Add an explicit EventTypeConnectorProxyProxied assertion 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 win

Prefer sentinel errors with errors.Is for reason mapping.

connector_specs_* and request_body_* audit reasons are currently derived by substring-matching err.Error() ("invalid", "read failed"), and the tests assert those exact message strings—small message wording changes can silently flip reason codes. Use sentinel errors + %w wrapping and switch the helpers to errors.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

📥 Commits

Reviewing files that changed from the base of the PR and between e9ccb25 and fd0dcba.

📒 Files selected for processing (7)
  • docs/src/content/docs/adr/0019-v4-https-data-plane.md
  • docs/src/content/docs/guides/observability.md
  • internal/app/handlers_connector_operations.go
  • internal/app/handlers_connector_operations_test.go
  • internal/app/handlers_sandbox_forward_proxy.go
  • internal/app/handlers_sandbox_forward_proxy_test.go
  • internal/model/model.go

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