package runtime import ( "context" "errors" "fmt" "strings" "sync" "time" "browser.local/run/protocol" ) const ( ProtectedRequestStatusSucceeded = "succeeded" ProtectedRequestStatusFailed = "failed" ProtectedRequestStatusUnknown = "unknown" ) // ErrProtectedRequestUnknown lets a Run-owned transport report that a request // is syntactically safe but not one of its declared operations. It is terminal // and affects only this request. var ErrProtectedRequestUnknown = errors.New("protected request outcome unknown") // ProtectedRequest contains only Platform-authorized text and logical binding. // Handlers resolve their own private transport configuration locally; they must // not return it in an outcome, error, log line, or result. type ProtectedRequest struct { JobID string ServerInstanceID string FencingToken uint64 Kind string TransportKey string TargetKey string RequestText string } type ProtectedRequestOutcome struct { Status string Stdout string Stderr string } type ProtectedRequestHandler interface { ExecuteProtectedRequest(context.Context, ProtectedRequest) (ProtectedRequestOutcome, error) } type ProtectedRequestHandlerFunc func(context.Context, ProtectedRequest) (ProtectedRequestOutcome, error) func (fn ProtectedRequestHandlerFunc) ExecuteProtectedRequest(ctx context.Context, request ProtectedRequest) (ProtectedRequestOutcome, error) { return fn(ctx, request) } // ProtectedRequestRegistry is configured by the local Run package owner. It // uses only logical lookup keys, so plugin and Platform payloads cannot select // an arbitrary program, DSN, socket, or host path. type ProtectedRequestRegistry struct { mu sync.RWMutex handlers map[string]ProtectedRequestHandler } func NewProtectedRequestRegistry() *ProtectedRequestRegistry { return &ProtectedRequestRegistry{handlers: map[string]ProtectedRequestHandler{}} } func (registry *ProtectedRequestRegistry) Register(kind string, transportKey string, handler ProtectedRequestHandler) error { if !validProtectedRequestKind(kind) || !protocol.ValidLogicalFileKey(transportKey) || handler == nil { return fmt.Errorf("protected request kind, transport key, and handler are required") } registry.mu.Lock() defer registry.mu.Unlock() if registry.handlers == nil { registry.handlers = map[string]ProtectedRequestHandler{} } registry.handlers[protectedRequestRegistryKey(kind, transportKey)] = handler return nil } func (registry *ProtectedRequestRegistry) handler(kind string, transportKey string) (ProtectedRequestHandler, bool) { if registry == nil { return nil, false } registry.mu.RLock() defer registry.mu.RUnlock() handler, exists := registry.handlers[protectedRequestRegistryKey(kind, transportKey)] return handler, exists } func protectedRequestRegistryKey(kind string, transportKey string) string { return kind + "\x00" + transportKey } func (executor LifecycleExecutor) ExecuteProtectedRequest(ctx context.Context, assignment protocol.RunJobAssignment, input protocol.ProtectedRequestExecutionInputResponse) LifecycleExecutionResult { if err := protocol.ValidateRunJobAssignment(assignment); err != nil || !protectedRequestInputMatchesExecutor(input, assignment) { return protectedRequestFailure(assignment.Capability, ProtectedRequestStatusFailed, "protected_request_binding_invalid") } handler, exists := executor.protectedRequests.handler(input.Kind, input.TransportKey) if !exists { if input.Kind == "rcon" && assignment.ExecutionInput.SourceRCON != nil { return executor.executeProtectedSourceRCON(ctx, assignment, input) } return protectedRequestFailure(assignment.Capability, ProtectedRequestStatusUnknown, "protected_request_transport_unknown") } executionCtx, cancel := context.WithTimeout(ctx, time.Duration(assignment.ExecutionInput.TimeoutSeconds)*time.Second) defer cancel() outcome, err := handler.ExecuteProtectedRequest(executionCtx, ProtectedRequest{JobID: input.JobID, ServerInstanceID: input.ServerInstanceID, FencingToken: input.FencingToken, Kind: input.Kind, TransportKey: input.TransportKey, TargetKey: input.TargetKey, RequestText: input.RequestText}) if input.Kind == "program" { executor.writeProtectedProgramLogs(ctx, assignment, outcome) } if errors.Is(executionCtx.Err(), context.Canceled) { return LifecycleExecutionResult{State: lifecycleResultStateCancelled, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "protected request cancelled"}, Message: "protected request cancelled", ErrorCode: "protected_request_cancelled"} } if errors.Is(executionCtx.Err(), context.DeadlineExceeded) { return protectedRequestFailure(assignment.Capability, ProtectedRequestStatusFailed, "protected_request_timeout") } if errors.Is(err, ErrProtectedRequestUnknown) || outcome.Status == ProtectedRequestStatusUnknown { return protectedRequestFailure(assignment.Capability, ProtectedRequestStatusUnknown, "protected_request_unknown") } if err != nil || outcome.Status == ProtectedRequestStatusFailed || outcome.Status != "" && outcome.Status != ProtectedRequestStatusSucceeded { return protectedRequestFailure(assignment.Capability, ProtectedRequestStatusFailed, "protected_request_failed") } return LifecycleExecutionResult{State: lifecycleResultStateSucceeded, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "protected request completed"}, Message: "protected request completed", ExecutionResult: protocol.RunJobExecutionResult{Kind: "protected." + input.Kind, Summary: "approved protected request executed through logical Run transport"}} } func (executor LifecycleExecutor) executeProtectedSourceRCON(ctx context.Context, assignment protocol.RunJobAssignment, input protocol.ProtectedRequestExecutionInputResponse) LifecycleExecutionResult { result := executor.ExecuteSourceRCON(ctx, assignment, input.RequestText) if result.State != lifecycleResultStateSucceeded { return result } return LifecycleExecutionResult{State: lifecycleResultStateSucceeded, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "protected RCON request delivered"}, Message: "protected RCON request delivered", ExecutionResult: protocol.RunJobExecutionResult{Kind: "protected.rcon", Summary: "approved protected RCON request delivered through Source RCON"}} } func protectedRequestInputMatchesExecutor(input protocol.ProtectedRequestExecutionInputResponse, assignment protocol.RunJobAssignment) bool { return protocol.ValidProtectedRequestExecutionInput(input) && input.JobID == assignment.JobID && input.ServerInstanceID == assignment.ServerInstanceID && input.FencingToken == assignment.FencingToken && input.TargetKey == assignment.TargetKey && input.TransportKey == assignment.ExecutionInput.RemoteAdapterKey && input.Kind == protectedRequestKindForCapability(assignment.Capability) } func protectedRequestKindForCapability(capability string) string { switch capability { case protocol.RunCapabilityRemoteRunProtectedSQL: return "sql" case protocol.RunCapabilityRemoteRunProtectedRCON: return "rcon" case protocol.RunCapabilityRemoteRunProgram: return "program" default: return "" } } func validProtectedRequestKind(kind string) bool { return kind == "sql" || kind == "rcon" || kind == "program" } func (executor LifecycleExecutor) writeProtectedProgramLogs(ctx context.Context, assignment protocol.RunJobAssignment, outcome ProtectedRequestOutcome) { for _, item := range []struct{ stream, body string }{{"management-program.stdout", outcome.Stdout}, {"management-program.stderr", outcome.Stderr}} { for _, line := range splitProtectedProgramLines(item.body) { _ = executor.logSink.Append(ctx, assignment, item.stream, sanitizeProtectedProgramLogLine(line)) } } } func splitProtectedProgramLines(value string) []string { value = strings.ToValidUTF8(strings.TrimSpace(value), "") if len(value) > maxLifecycleOutputBytes { value = strings.ToValidUTF8(value[:maxLifecycleOutputBytes], "") } lines := strings.Split(value, "\n") bounded := make([]string, 0, len(lines)) for _, line := range lines { line = strings.TrimRight(line, "\r") if strings.TrimSpace(line) != "" { bounded = append(bounded, line) } } return bounded } func sanitizeProtectedProgramLogLine(line string) string { if containsProtectedProgramPrivateText(line) { return "[redacted protected management-program output]" } return RedactText(line) } func containsProtectedProgramPrivateText(value string) bool { lower := strings.ToLower(value) for _, marker := range []string{"password=", "password:", "secret=", "secret:", "token=", "token:", "credential", "bearer ", "api_key", "apikey", "dsn=", "path=", "file=", "database=", "://", "mysql:", "postgres:", "sqlite:", "file:", "socket", "named pipe"} { if strings.Contains(lower, marker) { return true } } for _, field := range strings.Fields(value) { field = strings.Trim(field, "\"'()[]{}<>,;") if strings.HasPrefix(field, "/") || strings.HasPrefix(field, "./") || strings.HasPrefix(field, "../") || strings.HasPrefix(field, `\\`) || len(field) >= 3 && ((field[0] >= 'a' && field[0] <= 'z') || (field[0] >= 'A' && field[0] <= 'Z')) && field[1] == ':' && (field[2] == '/' || field[2] == '\\') { return true } } return false } func protectedRequestFailure(capability string, status string, code string) LifecycleExecutionResult { kind := protectedRequestKindForCapability(capability) if kind == "" { kind = "unknown" } if status == ProtectedRequestStatusUnknown { return LifecycleExecutionResult{State: lifecycleResultStateFailed, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "protected request outcome is unknown"}, Message: "protected request outcome is unknown", ErrorCode: "protected_request_unknown", ExecutionResult: protocol.RunJobExecutionResult{Kind: "protected." + kind + ".unknown", Summary: "protected request outcome is unknown"}} } return LifecycleExecutionResult{State: lifecycleResultStateFailed, Progress: protocol.RunJobProgressReport{Percent: 100, Message: "protected request failed"}, Message: "protected request failed", ErrorCode: code, ExecutionResult: protocol.RunJobExecutionResult{Kind: "protected." + kind + ".failed", Summary: "protected request failed safely"}} }