package service import ( "crypto/sha256" "crypto/subtle" "encoding/hex" "errors" "fmt" "os" "strings" "browser.local/platform/domain" "browser.local/platform/repo" "browser.local/platform/validator" ) func (svc *CoreService) GenerateRunDistributionForSession(sessionID string, request domain.RunDistributionGenerateRequest) (domain.RunDistribution, error) { request = domain.CopyRunDistributionGenerateRequest(request) if strings.TrimSpace(request.IdempotencyKey) == "" { request.IdempotencyKey = "run-" + request.ServerInstanceID + "-" + request.TargetOS + "-" + request.TargetArch } if err := validator.ValidateRunDistributionGenerateRequest(request); err != nil { return domain.RunDistribution{}, err } user, err := svc.GetCurrentUser(sessionID) if err != nil { return domain.RunDistribution{}, err } instance, err := svc.GetServerInstanceForSession(sessionID, request.ServerInstanceID) if err != nil { return domain.RunDistribution{}, err } plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { return domain.RunDistribution{}, err } if err := svc.validateDistributionPluginPermission(user.ID, plugin, instance.ID, "server.run.distribution", "run.generate.denied"); err != nil { return domain.RunDistribution{}, err } if err := validatePluginTarget(plugin, request.TargetOS); err != nil { _ = svc.recordAuditEvent(user.ID, "run.generate.denied", "server-instance", instance.ID, domain.AuditResultDenied, "run generation denied: unsupported target") return domain.RunDistribution{}, err } if err := svc.requireCompleteRuntimeBindings(user.ID, instance.ID, "run.generate.denied"); err != nil { return domain.RunDistribution{}, err } endpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID) if err != nil { return domain.RunDistribution{}, err } if err := svc.validateRunnableEndpoint(endpoint, domain.JobCapabilityDistributionBuild); err != nil { return domain.RunDistribution{}, err } key, plainKey, err := svc.ensureActiveComponentKey(instance.ID, domain.DistributionComponentRun, "") if err != nil { return domain.RunDistribution{}, err } distributionID := distributionID("run-dist", instance.ID, request.TargetOS, request.TargetArch, key.Generation, request.IdempotencyKey) if existing, err := svc.store.RunDistributions().Get(distributionID); err == nil { return domain.CopyRunDistribution(existing), nil } else if !errors.Is(err, repo.ErrNotFound) { return domain.RunDistribution{}, err } _ = plainKey artifactID := artifactIDForDistribution(distributionID + "-binary") buildJobID := jobIDFromParts("job-distribution-build", instance.ID, distributionID) stamp := svc.now() distribution := domain.RunDistribution{ ID: distributionID, ServerInstanceID: instance.ID, PluginID: plugin.ID, RunEndpointID: instance.RunEndpointID, TargetOS: request.TargetOS, TargetArch: request.TargetArch, PackageFormat: runPackageFormatForTarget(request.TargetOS), BuildJobID: buildJobID, ArtifactID: artifactID, KeyGeneration: key.Generation, SecretRef: key.SecretRef, Status: domain.DistributionStatusBuilding, CreatedAt: stamp, UpdatedAt: stamp, } if err := validator.ValidateRunDistribution(distribution); err != nil { return domain.RunDistribution{}, err } if err := svc.store.RunDistributions().Create(distribution); err != nil { if errors.Is(err, repo.ErrDuplicate) { existing, getErr := svc.store.RunDistributions().Get(distribution.ID) if getErr != nil { return domain.RunDistribution{}, getErr } return domain.CopyRunDistribution(existing), nil } return domain.RunDistribution{}, err } job, err := svc.CreateJob(domain.Job{ ID: buildJobID, ServerInstanceID: instance.ID, RunEndpointID: instance.RunEndpointID, Capability: domain.JobCapabilityDistributionBuild, TargetKey: "distribution/run", InputRef: "input://distribution-build/" + distribution.ID, IdempotencyKey: "distribution-build:" + distribution.ID, Progress: domain.JobProgress{Percent: 0, Message: "build queued"}, }) if err != nil { distribution.Status = domain.DistributionStatusFailed distribution.UpdatedAt = svc.now() _ = svc.store.RunDistributions().Update(distribution) return domain.RunDistribution{}, err } if job.ID != buildJobID || job.Capability != domain.JobCapabilityDistributionBuild { return domain.RunDistribution{}, validationError("distribution build idempotency key conflicts with another job") } if err := svc.recordAuditEvent(user.ID, "run.generate", "server-instance", instance.ID, domain.AuditResultQueued, "queued run binary build job with redacted runtime key ref"); err != nil { return domain.RunDistribution{}, err } return domain.CopyRunDistribution(distribution), nil } func (svc *CoreService) GenerateClientManagerDistributionForSession(sessionID string, request domain.ClientManagerBuildRequest) (domain.ClientManagerDistribution, error) { request = domain.CopyClientManagerBuildRequest(request) if strings.TrimSpace(request.IdempotencyKey) == "" { request.IdempotencyKey = "client-manager-" + request.ServerInstanceID + "-" + request.ProfileKey + "-" + request.TargetOS + "-" + request.TargetArch } if err := validator.ValidateClientManagerBuildRequest(request); err != nil { return domain.ClientManagerDistribution{}, err } user, err := svc.GetCurrentUser(sessionID) if err != nil { return domain.ClientManagerDistribution{}, err } instance, err := svc.GetServerInstanceForSession(sessionID, request.ServerInstanceID) if err != nil { return domain.ClientManagerDistribution{}, err } plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { return domain.ClientManagerDistribution{}, err } if err := svc.validateDistributionPluginPermission(user.ID, plugin, instance.ID, "server.client-manager.manage", "client-manager.build.denied"); err != nil { return domain.ClientManagerDistribution{}, err } if err := validatePluginTarget(plugin, request.TargetOS); err != nil { _ = svc.recordAuditEvent(user.ID, "client-manager.build.denied", "server-instance", instance.ID, domain.AuditResultDenied, "client-manager build denied: unsupported target") return domain.ClientManagerDistribution{}, err } profile, err := findRuntimeClientManagerProfile(plugin, request.ProfileKey) if err != nil { _ = svc.recordAuditEvent(user.ID, "client-manager.build.denied", "server-instance", instance.ID, domain.AuditResultDenied, "client-manager build denied: profile is not declared") return domain.ClientManagerDistribution{}, err } if strings.TrimSpace(request.SourceRevision) == "" { request.SourceRevision = clientManagerProfileRevision(profile) } if !clientManagerProfileSupportsTarget(profile, request.TargetOS, request.TargetArch) || request.RepositoryURL != profile.RepositoryURL || !clientManagerProfileAllowsRevision(profile, request.SourceRevision) { _ = svc.recordAuditEvent(user.ID, "client-manager.build.denied", "server-instance", instance.ID, domain.AuditResultDenied, "client-manager build denied: repository, revision, or target is not declared") return domain.ClientManagerDistribution{}, validationError("client-manager build must match the declared profile repository, revision, and target") } if err := svc.requireCompleteRuntimeBindings(user.ID, instance.ID, "client-manager.build.denied"); err != nil { return domain.ClientManagerDistribution{}, err } endpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID) if err != nil { return domain.ClientManagerDistribution{}, err } if err := svc.validateRunnableEndpoint(endpoint, domain.JobCapabilityDistributionBuild); err != nil { return domain.ClientManagerDistribution{}, err } key, plainKey, err := svc.ensureActiveComponentKey(instance.ID, domain.DistributionComponentClientManager, request.ProfileKey) if err != nil { return domain.ClientManagerDistribution{}, err } clientDistributionID := distributionID("client-manager-dist", instance.ID, request.ProfileKey, request.TargetOS, request.TargetArch, key.Generation, request.IdempotencyKey) if existing, err := svc.store.ClientManagerDistributions().Get(clientDistributionID); err == nil { return domain.CopyClientManagerDistribution(existing), nil } else if !errors.Is(err, repo.ErrNotFound) { return domain.ClientManagerDistribution{}, err } _ = plainKey artifactID := artifactIDForDistribution(clientDistributionID + "-binary") stamp := svc.now() buildJobID := jobIDFromParts("job-distribution-build", instance.ID, clientDistributionID) buildJob := domain.ClientManagerBuildJob{ ID: buildJobID, ServerInstanceID: instance.ID, PluginID: plugin.ID, ProfileKey: request.ProfileKey, Version: profile.Version, TargetOS: request.TargetOS, TargetArch: request.TargetArch, RepositoryURL: request.RepositoryURL, SourceRevision: request.SourceRevision, ArtifactID: artifactID, KeyGeneration: key.Generation, Status: domain.DistributionJobStatusQueued, CreatedAt: stamp, UpdatedAt: stamp, } if err := validator.ValidateClientManagerBuildJob(buildJob); err != nil { return domain.ClientManagerDistribution{}, err } if err := svc.store.ClientManagerBuildJobs().Create(buildJob); err != nil { if !errors.Is(err, repo.ErrDuplicate) { return domain.ClientManagerDistribution{}, err } existing, getErr := svc.store.ClientManagerBuildJobs().Get(buildJob.ID) if getErr != nil { return domain.ClientManagerDistribution{}, getErr } if !sameClientManagerBuildJobArtifacts(existing, buildJob) { return domain.ClientManagerDistribution{}, validationError("client-manager build job already exists with different artifacts") } buildJob = existing } distribution := domain.ClientManagerDistribution{ ID: clientDistributionID, ServerInstanceID: instance.ID, PluginID: plugin.ID, ProfileKey: request.ProfileKey, Version: profile.Version, TargetOS: request.TargetOS, TargetArch: request.TargetArch, RepositoryURL: request.RepositoryURL, SourceRevision: request.SourceRevision, BuildJobID: buildJob.ID, ArtifactID: artifactID, KeyGeneration: key.Generation, SecretRef: key.SecretRef, Status: domain.DistributionStatusBuilding, CreatedAt: stamp, UpdatedAt: stamp, } if err := validator.ValidateClientManagerDistribution(distribution); err != nil { return domain.ClientManagerDistribution{}, err } if err := svc.store.ClientManagerDistributions().Create(distribution); err != nil { if errors.Is(err, repo.ErrDuplicate) { existing, getErr := svc.store.ClientManagerDistributions().Get(distribution.ID) if getErr != nil { return domain.ClientManagerDistribution{}, getErr } return domain.CopyClientManagerDistribution(existing), nil } return domain.ClientManagerDistribution{}, err } if err := svc.ProjectClientManagerDistribution(distribution); err != nil { return domain.ClientManagerDistribution{}, err } job, err := svc.CreateJob(domain.Job{ ID: buildJobID, ServerInstanceID: instance.ID, RunEndpointID: instance.RunEndpointID, Capability: domain.JobCapabilityDistributionBuild, TargetKey: "distribution/client-manager/" + request.ProfileKey, InputRef: "input://distribution-build/" + distribution.ID, IdempotencyKey: "distribution-build:" + distribution.ID, Progress: domain.JobProgress{Percent: 0, Message: "build queued"}, }) if err != nil { buildJob.Status = domain.DistributionJobStatusFailed buildJob.UpdatedAt = svc.now() distribution.Status = domain.DistributionStatusFailed distribution.UpdatedAt = buildJob.UpdatedAt _ = svc.store.ClientManagerBuildJobs().Update(buildJob) _ = svc.store.ClientManagerDistributions().Update(distribution) _ = svc.ProjectClientManagerDistribution(distribution) return domain.ClientManagerDistribution{}, err } if job.ID != buildJobID || job.Capability != domain.JobCapabilityDistributionBuild { return domain.ClientManagerDistribution{}, validationError("distribution build idempotency key conflicts with another job") } if err := svc.recordAuditEvent(user.ID, "client-manager.build", "server-instance", instance.ID, domain.AuditResultQueued, "queued client-manager source build with redacted runtime key ref"); err != nil { return domain.ClientManagerDistribution{}, err } return domain.CopyClientManagerDistribution(distribution), nil } func (svc *CoreService) OpenLatestRunDistributionDownloadForSession(sessionID string, serverInstanceID string) (domain.ArtifactDownloadReference, error) { instance, err := svc.GetServerInstanceForSession(sessionID, serverInstanceID) if err != nil { return domain.ArtifactDownloadReference{}, err } distributions, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{ServerInstanceID: instance.ID, Status: domain.DistributionStatusAvailable}) if err != nil { return domain.ArtifactDownloadReference{}, err } var latest domain.RunDistribution for _, distribution := range distributions { if latest.ID == "" || distribution.KeyGeneration > latest.KeyGeneration || (distribution.KeyGeneration == latest.KeyGeneration && distribution.UpdatedAt.After(latest.UpdatedAt)) { latest = distribution } } if latest.ID == "" { return domain.ArtifactDownloadReference{}, repo.ErrNotFound } return svc.OpenArtifactDownloadForSession(sessionID, domain.ArtifactDownloadReferenceRequest{ArtifactID: latest.ArtifactID}) } func (svc *CoreService) OpenLatestClientManagerDistributionDownloadForSession(sessionID string, serverInstanceID string, profileKey string) (domain.ArtifactDownloadReference, error) { instance, err := svc.GetServerInstanceForSession(sessionID, serverInstanceID) if err != nil { return domain.ArtifactDownloadReference{}, err } filter := domain.ClientManagerDistributionFilter{ServerInstanceID: instance.ID, Status: domain.DistributionStatusAvailable} if strings.TrimSpace(profileKey) != "" { filter.ProfileKey = strings.TrimSpace(profileKey) } distributions, err := svc.store.ClientManagerDistributions().List(filter) if err != nil { return domain.ArtifactDownloadReference{}, err } var latest domain.ClientManagerDistribution for _, distribution := range distributions { if latest.ID == "" || distribution.KeyGeneration > latest.KeyGeneration || (distribution.KeyGeneration == latest.KeyGeneration && distribution.UpdatedAt.After(latest.UpdatedAt)) { latest = distribution } } if latest.ID == "" { return domain.ArtifactDownloadReference{}, repo.ErrNotFound } return svc.OpenArtifactDownloadForSession(sessionID, domain.ArtifactDownloadReferenceRequest{ArtifactID: latest.ArtifactID}) } func (svc *CoreService) ResetComponentKeyForSession(sessionID string, request domain.ComponentKeyResetRequest) (domain.EncryptedComponentKey, error) { request = domain.CopyComponentKeyResetRequest(request) if err := validator.ValidateComponentKeyResetRequest(request); err != nil { return domain.EncryptedComponentKey{}, err } user, instance, err := svc.requireServerOwner(sessionID, request.ServerInstanceID) if err != nil { return domain.EncryptedComponentKey{}, err } _ = user filter := domain.EncryptedComponentKeyFilter{ ServerInstanceID: request.ServerInstanceID, ComponentKind: request.ComponentKind, ComponentKey: normalizedComponentKey(request.ComponentKind, request.ComponentKey), } keys, err := svc.store.EncryptedComponentKeys().List(filter) if err != nil { return domain.EncryptedComponentKey{}, err } nextGeneration := 1 for _, key := range keys { if key.Generation >= nextGeneration { nextGeneration = key.Generation + 1 } if key.Status == domain.ComponentKeyStatusActive { key.Status = domain.ComponentKeyStatusRevoked key.UpdatedAt = svc.now() key.ResetAt = svc.now() if err := validator.ValidateEncryptedComponentKey(key); err != nil { return domain.EncryptedComponentKey{}, err } if err := svc.store.EncryptedComponentKeys().Update(key); err != nil { return domain.EncryptedComponentKey{}, err } } } newKey, _, err := svc.createEncryptedComponentKey(instance.ID, request.ComponentKind, request.ComponentKey, nextGeneration) if err != nil { return domain.EncryptedComponentKey{}, err } if err := svc.revokeComponentDistributions(instance.ID, request.ComponentKind, normalizedComponentKey(request.ComponentKind, request.ComponentKey), nextGeneration); err != nil { return domain.EncryptedComponentKey{}, err } if request.ComponentKind == domain.DistributionComponentClientManager { if err := svc.fenceClientManagerAfterKeyReset(instance.ID, normalizedComponentKey(request.ComponentKind, request.ComponentKey), nextGeneration); err != nil { return domain.EncryptedComponentKey{}, err } } if request.ComponentKind == domain.DistributionComponentRun { if err := svc.revokeRunControlSessionForInstance(instance); err != nil { return domain.EncryptedComponentKey{}, err } } if err := svc.recordAuditEvent(user.ID, "runtime-key.reset", "server-instance", instance.ID, domain.AuditResultSuccess, "reset "+string(request.ComponentKind)+" key; previous packages revoked"); err != nil { return domain.EncryptedComponentKey{}, err } return domain.CopyEncryptedComponentKey(newKey), nil } func (svc *CoreService) AuthenticateComponent(request domain.ComponentAuthenticationRequest) (domain.ComponentAuthenticationResult, error) { request = domain.CopyComponentAuthenticationRequest(request) if err := validator.ValidateComponentAuthenticationRequest(request); err != nil { return domain.ComponentAuthenticationResult{}, err } componentKey := normalizedComponentKey(request.ComponentKind, request.ComponentKey) result := domain.ComponentAuthenticationResult{ ServerInstanceID: request.ServerInstanceID, ComponentKind: request.ComponentKind, ComponentKey: componentKey, Generation: request.Generation, } key, err := svc.activeComponentKey(request.ServerInstanceID, request.ComponentKind, componentKey) if err != nil { if errors.Is(err, repo.ErrNotFound) { result.Reason = "active key not found" return domain.CopyComponentAuthenticationResult(result), nil } return domain.ComponentAuthenticationResult{}, err } if key.Generation != request.Generation { result.Reason = "key generation is no longer current" _ = svc.recordAuditEvent("runtime", "runtime-key.auth", "server-instance", request.ServerInstanceID, domain.AuditResultDenied, "component authentication denied: stale generation") return domain.CopyComponentAuthenticationResult(result), nil } plainKey, err := svc.decryptRuntimeKey(key.EncryptedKey) if err != nil { return domain.ComponentAuthenticationResult{}, err } if subtle.ConstantTimeCompare([]byte(plainKey), []byte(request.Key)) != 1 { result.Reason = "key is not current" _ = svc.recordAuditEvent("runtime", "runtime-key.auth", "server-instance", request.ServerInstanceID, domain.AuditResultDenied, "component authentication denied: key mismatch") return domain.CopyComponentAuthenticationResult(result), nil } result.Allowed = true result.Reason = "current key accepted" return domain.CopyComponentAuthenticationResult(result), nil } func (svc *CoreService) GetServerRuntimeActionsForSession(sessionID string, serverInstanceID string) (domain.ServerRuntimeActions, error) { instance, err := svc.GetServerInstanceForSession(sessionID, serverInstanceID) if err != nil { return domain.ServerRuntimeActions{}, err } plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { return domain.ServerRuntimeActions{}, err } endpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID) if err != nil { return domain.ServerRuntimeActions{}, err } hasAvailableRunPackage := false runDistributions, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{ServerInstanceID: instance.ID, Status: domain.DistributionStatusAvailable}) if err != nil { return domain.ServerRuntimeActions{}, err } for _, distribution := range runDistributions { if distribution.ArtifactID != "" { hasAvailableRunPackage = true break } } hasAvailableClientPackage := false clientDistributions, err := svc.store.ClientManagerDistributions().List(domain.ClientManagerDistributionFilter{ServerInstanceID: instance.ID, Status: domain.DistributionStatusAvailable}) if err != nil { return domain.ServerRuntimeActions{}, err } for _, distribution := range clientDistributions { if distribution.ArtifactID != "" { hasAvailableClientPackage = true break } } bindingsComplete, bindingReason := svc.runtimeBindingReadiness(instance.ID) dependencyPermissionDeclared := pluginDeclares(plugin, "server.dependencies.manage") actions := domain.ServerRuntimeActions{ ServerInstanceID: instance.ID, PluginID: plugin.ID, RunEndpointID: endpoint.ID, RunStatus: endpoint.Status, Actions: []domain.ServerRuntimeAction{ runtimeAction("generate-run", "Generate run", pluginDeclares(plugin, "server.run.distribution") && svc.endpointSupports(endpoint, domain.JobCapabilityDistributionBuild) && bindingsComplete, fallbackReason(!pluginDeclares(plugin, "server.run.distribution") || !svc.endpointSupports(endpoint, domain.JobCapabilityDistributionBuild), "run endpoint cannot build distributions", bindingReason)), runtimeAction("download-run", "Download run", hasAvailableRunPackage, "run package has not been generated"), runtimeAction("push-run-update", "Push run update", pluginDeclares(plugin, "server.run.distribution") && svc.endpointSupports(endpoint, domain.JobCapabilityRunSelfUpdate) && bindingsComplete, fallbackReason(!pluginDeclares(plugin, "server.run.distribution") || !svc.endpointSupports(endpoint, domain.JobCapabilityRunSelfUpdate), "run endpoint cannot self-update", bindingReason)), runtimeAction("reset-run-key", "Reset run key", pluginDeclares(plugin, "server.run.distribution"), "plugin permission is not declared"), runtimeAction("generate-client-manager", "Generate client manager", pluginDeclares(plugin, "server.client-manager.manage") && svc.endpointSupports(endpoint, domain.JobCapabilityDistributionBuild) && bindingsComplete, fallbackReason(!pluginDeclares(plugin, "server.client-manager.manage") || !svc.endpointSupports(endpoint, domain.JobCapabilityDistributionBuild), "run endpoint cannot build distributions", bindingReason)), runtimeAction("download-client-manager", "Download client manager", hasAvailableClientPackage, "client-manager package has not been generated"), runtimeAction("reset-client-manager-key", "Reset client-manager key", pluginDeclares(plugin, "server.client-manager.manage"), "client-manager permission is not declared"), runtimeAction("dependencies-check", "Check dependencies", dependencyPermissionDeclared && svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesCheck) && bindingsComplete, fallbackReason(!dependencyPermissionDeclared, "plugin permission is not declared", fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesCheck), "run endpoint cannot check dependencies", bindingReason))), runtimeAction("dependencies-install", "Install dependencies", dependencyPermissionDeclared && svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesInstall) && bindingsComplete, fallbackReason(!dependencyPermissionDeclared, "plugin permission is not declared", fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityDependenciesInstall), "run endpoint cannot install dependencies", bindingReason))), runtimeAction("live-logs", "Live logs", pluginSupports(plugin, "logs.read"), "plugin does not declare live logs"), runtimeAction("historical-logs", "Historical logs", svc.endpointSupports(endpoint, domain.JobCapabilityLogsBackfill) && bindingsComplete, fallbackReason(!svc.endpointSupports(endpoint, domain.JobCapabilityLogsBackfill), "run endpoint cannot backfill logs", bindingReason)), }, } actions.Actions = append(actions.Actions, svc.clientManagerRuntimeActionProjection(instance, plugin, endpoint, bindingsComplete, bindingReason)...) return domain.CopyServerRuntimeActions(actions), nil } func (svc *CoreService) PushRunUpdateForSession(sessionID string, request domain.RunUpdateRequest) (domain.RunUpdateJob, error) { request = domain.CopyRunUpdateRequest(request) if strings.TrimSpace(request.IdempotencyKey) == "" { request.IdempotencyKey = "run-update-" + request.ServerInstanceID + "-" + request.ArtifactID } if err := validateRunUpdateRequest(request); err != nil { return domain.RunUpdateJob{}, err } user, instance, err := svc.requireServerOwner(sessionID, request.ServerInstanceID) if err != nil { return domain.RunUpdateJob{}, err } plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { return domain.RunUpdateJob{}, err } if err := svc.validateDistributionPluginPermission(user.ID, plugin, instance.ID, "server.run.distribution", "run.update.denied"); err != nil { return domain.RunUpdateJob{}, err } if err := svc.requireCompleteRuntimeBindings(user.ID, instance.ID, "run.update.denied"); err != nil { return domain.RunUpdateJob{}, err } artifact, err := svc.GetArtifactForSession(sessionID, request.ArtifactID) if err != nil { return domain.RunUpdateJob{}, err } if artifact.State != domain.ArtifactStateAvailable { _ = svc.recordAuditEvent(user.ID, "run.update.denied", "server-instance", instance.ID, domain.AuditResultDenied, "run update denied: artifact is unavailable") return domain.RunUpdateJob{}, validationError("artifact must be available") } if request.Checksum == "" { request.Checksum = artifact.Checksum } if request.Checksum != artifact.Checksum { _ = svc.recordAuditEvent(user.ID, "run.update.denied", "server-instance", instance.ID, domain.AuditResultDenied, "run update denied: checksum mismatch") return domain.RunUpdateJob{}, validationError("checksum must match artifact") } endpoint, err := svc.store.RunEndpoints().Get(instance.RunEndpointID) if err != nil { return domain.RunUpdateJob{}, err } if endpoint.Platform == "" || endpoint.Architecture == "" { return domain.RunUpdateJob{}, validationError("Run endpoint target is not registered") } distributions, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{ServerInstanceID: instance.ID, Status: domain.DistributionStatusAvailable}) if err != nil { return domain.RunUpdateJob{}, err } distribution, err := findRunDistributionForArtifact(distributions, artifact.ID) if err != nil || distribution.RunEndpointID != endpoint.ID || distribution.TargetOS != endpoint.Platform || distribution.TargetArch != endpoint.Architecture || distribution.Checksum != artifact.Checksum || artifact.OwnerKind != domain.ArtifactOwnerKindJob || artifact.OwnerID != distribution.BuildJobID { _ = svc.recordAuditEvent(user.ID, "run.update.denied", "server-instance", instance.ID, domain.AuditResultDenied, "run update denied: artifact is not an approved target-matched Run distribution") return domain.RunUpdateJob{}, validationError("artifact must be an approved target-matched Run distribution") } job, err := svc.CreateJob(domain.Job{ ID: jobIDFromParts("job-run-update", request.ServerInstanceID, request.IdempotencyKey), ServerInstanceID: instance.ID, RunEndpointID: instance.RunEndpointID, Capability: domain.JobCapabilityRunSelfUpdate, TargetKey: "run/update", InputRef: "artifact://" + artifact.ID, IdempotencyKey: request.IdempotencyKey, Progress: domain.JobProgress{Percent: 0, Message: "run self-update queued"}, }) if err != nil { _ = svc.recordAuditEvent(user.ID, "run.update.denied", "server-instance", instance.ID, domain.AuditResultDenied, "run update denied: endpoint unsupported or offline") return domain.RunUpdateJob{}, err } stamp := svc.now() updateJob := domain.RunUpdateJob{ ID: distributionID("run-update", instance.ID, artifact.ID, request.IdempotencyKey), ServerInstanceID: instance.ID, RunEndpointID: instance.RunEndpointID, ArtifactID: artifact.ID, Checksum: artifact.Checksum, TargetOS: distribution.TargetOS, TargetArch: distribution.TargetArch, TargetRelease: distribution.ID, PreviousVersion: endpoint.Version, JobID: job.ID, IdempotencyKey: request.IdempotencyKey, Status: domain.DistributionJobStatusQueued, Phase: domain.RunUpdatePhaseQueued, Message: "Run update queued", CreatedAt: stamp, UpdatedAt: stamp, } if err := validator.ValidateRunUpdateJob(updateJob); err != nil { return domain.RunUpdateJob{}, err } if err := svc.store.RunUpdateJobs().Create(updateJob); err != nil { if errors.Is(err, repo.ErrDuplicate) { existing, getErr := svc.store.RunUpdateJobs().Get(updateJob.ID) if getErr != nil { return domain.RunUpdateJob{}, getErr } if !sameRunUpdateTarget(existing, updateJob) { return domain.RunUpdateJob{}, validationError("run update job already exists with different target") } return domain.CopyRunUpdateJob(existing), nil } return domain.RunUpdateJob{}, err } if err := svc.recordAuditEvent(user.ID, "run.update", "server-instance", instance.ID, domain.AuditResultQueued, "queued run self-update job with artifact checksum"); err != nil { return domain.RunUpdateJob{}, err } return domain.CopyRunUpdateJob(updateJob), nil } func (svc *CoreService) QueueDependencyJobForSession(sessionID string, request domain.DependencyJobRequest) (domain.Job, error) { request = domain.CopyDependencyJobRequest(request) if strings.TrimSpace(request.IdempotencyKey) == "" { operation := "check" if request.Install { operation = "install-" + request.InstallPlanKey } request.IdempotencyKey = "dependencies-" + operation + "-" + request.ServerInstanceID + "-" + request.ProbeKey } if err := validateDependencyJobRequest(request); err != nil { return domain.Job{}, err } user, instance, err := svc.requireServerOwner(sessionID, request.ServerInstanceID) if err != nil { return domain.Job{}, err } plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { return domain.Job{}, err } if !pluginDeclares(plugin, "server.dependencies.manage") { _ = svc.recordAuditEvent(user.ID, "dependency.install.denied", "server-instance", instance.ID, domain.AuditResultDenied, "dependency operation denied: plugin permission is not declared") return domain.Job{}, forbiddenError("plugin does not declare required permission: server.dependencies.manage") } if err := svc.requireCompleteRuntimeBindings(user.ID, instance.ID, "dependency.install.denied"); err != nil { return domain.Job{}, err } resolution, err := svc.resolveDependencyContext(instance.ID) if err != nil { return domain.Job{}, err } if request.TargetOS != "" && request.TargetOS != resolution.endpoint.Platform || request.TargetArch != "" && request.TargetArch != resolution.endpoint.Architecture { return domain.Job{}, validationError("dependency request target does not match Run endpoint") } request.TargetOS = resolution.endpoint.Platform request.TargetArch = resolution.endpoint.Architecture probe, err := declaredDependencyProbe(plugin, request.ProbeKey, request.TargetOS) if err != nil { return domain.Job{}, err } var plan domain.RuntimeInstallPlan if request.Install { plan, err = declaredInstallPlan(plugin, request.InstallPlanKey, request.TargetOS) if err != nil { return domain.Job{}, err } if !planTargetsProbe(plan, probe) { return domain.Job{}, validationError("install plan does not target requested dependency probe") } } expectedDigest := dependencyPlanDigest(resolution, probe, plan) if request.Install && request.PlanDigest != expectedDigest { _ = svc.recordAuditEvent(user.ID, "dependency.install.denied", "server-instance", instance.ID, domain.AuditResultDenied, "dependency install denied: reviewed plan digest is stale or missing") return domain.Job{}, validationError("planDigest must match the current reviewed install plan") } request.PlanDigest = expectedDigest capability := domain.JobCapabilityDependenciesCheck targetKey := "dependencies/" + request.ProbeKey message := "dependency check queued" auditAction := "dependency.check" state := domain.DependencyStateUnknown if request.Install { capability = domain.JobCapabilityDependenciesInstall targetKey = "dependencies/install/" + request.InstallPlanKey message = "dependency install queued" auditAction = "dependency.install" state = domain.DependencyStateInstalling } job, err := svc.CreateJob(domain.Job{ ID: jobIDFromParts("job-dependencies", request.ServerInstanceID, request.IdempotencyKey), ServerInstanceID: instance.ID, RunEndpointID: instance.RunEndpointID, Capability: capability, TargetKey: targetKey, IdempotencyKey: request.IdempotencyKey, Progress: domain.JobProgress{Percent: 0, Message: message}, }) if err != nil { _ = svc.recordAuditEvent(user.ID, auditAction+".denied", "server-instance", instance.ID, domain.AuditResultDenied, "dependency operation denied: endpoint unsupported or offline") return domain.Job{}, err } if err := svc.upsertDependencyStatus(instance, request, job.ID, probe.Required, state, "queued through platform job"); err != nil { return domain.Job{}, err } if err := svc.recordAuditEvent(user.ID, auditAction, "server-instance", instance.ID, domain.AuditResultQueued, message); err != nil { return domain.Job{}, err } return domain.CopyJob(job), nil } func (svc *CoreService) QueueLogBackfillForSession(sessionID string, request domain.LogBackfillRequest) (domain.Job, error) { request = domain.CopyLogBackfillRequest(request) if strings.TrimSpace(request.IdempotencyKey) == "" { request.IdempotencyKey = "logs-backfill-" + request.ServerInstanceID + "-" + request.SourceKey } if err := validateLogBackfillRequest(request); err != nil { return domain.Job{}, err } user, err := svc.GetCurrentUser(sessionID) if err != nil { return domain.Job{}, err } instance, err := svc.GetServerInstanceForSession(sessionID, request.ServerInstanceID) if err != nil { return domain.Job{}, err } plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { return domain.Job{}, err } if !pluginSupports(plugin, domain.JobCapabilityLogsBackfill) { _ = svc.recordAuditEvent(user.ID, "logs.backfill.denied", "server-instance", instance.ID, domain.AuditResultDenied, "log backfill denied: plugin capability is not declared") return domain.Job{}, ErrForbidden } if err := svc.requireCompleteRuntimeBindings(user.ID, instance.ID, "logs.backfill.denied"); err != nil { return domain.Job{}, err } job, err := svc.CreateJob(domain.Job{ ID: jobIDFromParts("job-logs-backfill", request.ServerInstanceID, request.IdempotencyKey), ServerInstanceID: instance.ID, RunEndpointID: instance.RunEndpointID, Capability: domain.JobCapabilityLogsBackfill, TargetKey: "logs/" + request.SourceKey, InputRef: request.CheckpointRef, IdempotencyKey: request.IdempotencyKey, Progress: domain.JobProgress{Percent: 0, Message: "historical log backfill queued"}, }) if err != nil { _ = svc.recordAuditEvent(user.ID, "logs.backfill.denied", "server-instance", instance.ID, domain.AuditResultDenied, "log backfill denied: endpoint unsupported or offline") return domain.Job{}, err } if err := svc.recordAuditEvent(user.ID, "logs.backfill", "server-instance", instance.ID, domain.AuditResultQueued, "queued historical log backfill without log bodies in job result"); err != nil { return domain.Job{}, err } return domain.CopyJob(job), nil } func (svc *CoreService) validateDistributionPluginPermission(actorID string, plugin domain.GamePlugin, serverInstanceID string, permission string, deniedAction string) error { if plugin.Status != domain.GamePluginStatusInstalled { _ = svc.recordAuditEvent(actorID, deniedAction, "server-instance", serverInstanceID, domain.AuditResultDenied, "distribution operation denied: plugin is not installed") return forbiddenError("plugin is not installed") } if !containsString(plugin.DeclaredPermissions, permission) { _ = svc.recordAuditEvent(actorID, deniedAction, "server-instance", serverInstanceID, domain.AuditResultDenied, "distribution operation denied: plugin permission is not declared") return forbiddenError("plugin does not declare required permission: " + permission) } return nil } func (svc *CoreService) ensureActiveComponentKey(serverInstanceID string, kind domain.DistributionComponentKind, componentKey string) (domain.EncryptedComponentKey, string, error) { normalized := normalizedComponentKey(kind, componentKey) key, err := svc.activeComponentKey(serverInstanceID, kind, normalized) if err == nil { plainKey, err := svc.decryptRuntimeKey(key.EncryptedKey) return key, plainKey, err } if !errors.Is(err, repo.ErrNotFound) { return domain.EncryptedComponentKey{}, "", err } return svc.createEncryptedComponentKey(serverInstanceID, kind, normalized, 1) } func (svc *CoreService) activeComponentKey(serverInstanceID string, kind domain.DistributionComponentKind, componentKey string) (domain.EncryptedComponentKey, error) { keys, err := svc.store.EncryptedComponentKeys().List(domain.EncryptedComponentKeyFilter{ ServerInstanceID: serverInstanceID, ComponentKind: kind, ComponentKey: normalizedComponentKey(kind, componentKey), Status: domain.ComponentKeyStatusActive, }) if err != nil { return domain.EncryptedComponentKey{}, err } if len(keys) == 0 { return domain.EncryptedComponentKey{}, repo.ErrNotFound } active := keys[0] for _, candidate := range keys[1:] { if candidate.Generation > active.Generation { active = candidate } } return domain.CopyEncryptedComponentKey(active), nil } func (svc *CoreService) createEncryptedComponentKey(serverInstanceID string, kind domain.DistributionComponentKind, componentKey string, generation int) (domain.EncryptedComponentKey, string, error) { plainKey, err := randomToken() if kind == domain.DistributionComponentRun { plainKey, err = randomRunComponentKey() } if err != nil { return domain.EncryptedComponentKey{}, "", err } encryptedKey, err := svc.encryptRuntimeKey(plainKey) if err != nil { return domain.EncryptedComponentKey{}, "", err } componentKey = normalizedComponentKey(kind, componentKey) stamp := svc.now() key := domain.EncryptedComponentKey{ ID: componentKeyID(serverInstanceID, kind, componentKey, generation), ServerInstanceID: serverInstanceID, ComponentKind: kind, ComponentKey: componentKey, EncryptedKey: encryptedKey, KeyHash: checksumForString(plainKey), Fingerprint: fingerprintForString(plainKey), SecretRef: secretRefForComponent(serverInstanceID, kind, componentKey), Generation: generation, Status: domain.ComponentKeyStatusActive, CreatedAt: stamp, UpdatedAt: stamp, } if err := validator.ValidateEncryptedComponentKey(key); err != nil { return domain.EncryptedComponentKey{}, "", err } if err := svc.store.EncryptedComponentKeys().Create(key); err != nil { return domain.EncryptedComponentKey{}, "", err } return domain.CopyEncryptedComponentKey(key), plainKey, nil } func (svc *CoreService) revokeComponentDistributions(serverInstanceID string, kind domain.DistributionComponentKind, componentKey string, latestGeneration int) error { switch kind { case domain.DistributionComponentRun: distributions, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{ServerInstanceID: serverInstanceID}) if err != nil { return err } for _, distribution := range distributions { if distribution.KeyGeneration >= latestGeneration || distribution.Status == domain.DistributionStatusRevoked { continue } distribution.Status = domain.DistributionStatusRevoked distribution.UpdatedAt = svc.now() if err := validator.ValidateRunDistribution(distribution); err != nil { return err } if err := svc.store.RunDistributions().Update(distribution); err != nil { return err } if err := svc.expireDistributionArtifact(distribution.ArtifactID); err != nil { return err } } case domain.DistributionComponentClientManager: distributions, err := svc.store.ClientManagerDistributions().List(domain.ClientManagerDistributionFilter{ServerInstanceID: serverInstanceID, ProfileKey: componentKey}) if err != nil { return err } for _, distribution := range distributions { if distribution.KeyGeneration >= latestGeneration || distribution.Status == domain.DistributionStatusRevoked { continue } distribution.Status = domain.DistributionStatusRevoked distribution.UpdatedAt = svc.now() if err := validator.ValidateClientManagerDistribution(distribution); err != nil { return err } if err := svc.store.ClientManagerDistributions().Update(distribution); err != nil { return err } if err := svc.expireDistributionArtifact(distribution.ArtifactID); err != nil { return err } } } return nil } func (svc *CoreService) expireDistributionArtifact(artifactID string) error { artifact, err := svc.store.Artifacts().Get(artifactID) if errors.Is(err, repo.ErrNotFound) { return nil } if err != nil { return err } artifact.State = domain.ArtifactStateExpired artifact.UpdatedAt = svc.now() if err := validator.ValidateArtifact(artifact); err != nil { return err } return svc.store.Artifacts().Update(artifact) } func (svc *CoreService) createPlatformArtifactPayload(artifactID string, ownerKind domain.ArtifactOwnerKind, ownerID string, payload []byte) (domain.Artifact, error) { artifact := domain.Artifact{ ID: artifactID, OwnerKind: ownerKind, OwnerID: ownerID, SizeBytes: int64(len(payload)), Checksum: validator.BytesChecksum(payload), State: domain.ArtifactStateAvailable, CreatedAt: svc.now(), UpdatedAt: svc.now(), } if err := validator.ValidateArtifact(artifact); err != nil { return domain.Artifact{}, err } if err := svc.store.Artifacts().Create(artifact); err != nil { if !errors.Is(err, repo.ErrDuplicate) { return domain.Artifact{}, err } existing, getErr := svc.store.Artifacts().Get(artifact.ID) if getErr != nil { return domain.Artifact{}, getErr } if err := validateReusablePlatformArtifact(existing, artifact); err != nil { return domain.Artifact{}, err } if err := svc.ensureArtifactPayload(artifact.ID, payload, existing); err != nil { return domain.Artifact{}, err } return domain.CopyArtifact(existing), nil } if err := svc.ensureArtifactPayload(artifact.ID, payload, artifact); err != nil { return domain.Artifact{}, err } return domain.CopyArtifact(artifact), nil } func validateReusablePlatformArtifact(existing domain.Artifact, expected domain.Artifact) error { if existing.OwnerKind != expected.OwnerKind || existing.OwnerID != expected.OwnerID { return validationError("artifact already exists with different owner") } if existing.SizeBytes != expected.SizeBytes || existing.Checksum != expected.Checksum { return validationError("artifact already exists with different content") } if existing.State != domain.ArtifactStateAvailable { return validationError("artifact already exists but is not available") } return nil } func (svc *CoreService) ensureArtifactPayload(artifactID string, payload []byte, artifact domain.Artifact) error { svc.artifactMu.Lock() defer svc.artifactMu.Unlock() if existingPayload, exists := svc.artifactPayloads[artifactID]; exists { if int64(len(existingPayload)) != artifact.SizeBytes || validator.BytesChecksum(existingPayload) != artifact.Checksum { return validationError("artifact payload does not match metadata") } return nil } if existingPayload, err := svc.artifactStore.GetPayload(artifactID); err == nil { if int64(len(existingPayload)) != artifact.SizeBytes || validator.BytesChecksum(existingPayload) != artifact.Checksum { return validationError("artifact payload does not match metadata") } svc.artifactPayloads[artifactID] = domain.CopyBytes(existingPayload) return nil } else if !errors.Is(err, repo.ErrNotFound) { return err } if int64(len(payload)) != artifact.SizeBytes || validator.BytesChecksum(payload) != artifact.Checksum { return validationError("artifact payload does not match metadata") } if err := svc.artifactStore.PutPayload(artifactID, payload); err != nil { return err } svc.artifactPayloads[artifactID] = domain.CopyBytes(payload) return nil } func sameClientManagerBuildJobArtifacts(existing domain.ClientManagerBuildJob, expected domain.ClientManagerBuildJob) bool { return existing.ServerInstanceID == expected.ServerInstanceID && existing.PluginID == expected.PluginID && existing.ProfileKey == expected.ProfileKey && existing.Version == expected.Version && existing.TargetOS == expected.TargetOS && existing.TargetArch == expected.TargetArch && existing.RepositoryURL == expected.RepositoryURL && existing.SourceRevision == expected.SourceRevision && existing.ArtifactID == expected.ArtifactID && existing.Checksum == expected.Checksum && existing.KeyGeneration == expected.KeyGeneration && existing.LogsRef == expected.LogsRef && existing.Status == expected.Status } func (svc *CoreService) upsertDependencyStatus(instance domain.ServerInstance, request domain.DependencyJobRequest, jobID string, required bool, state domain.DependencyState, message string) error { statusID := distributionID("dependency-status", instance.ID, request.ProbeKey) stamp := svc.now() status := domain.DependencyStatus{ ID: statusID, ServerInstanceID: instance.ID, PluginID: instance.PluginID, ProbeKey: request.ProbeKey, TargetOS: request.TargetOS, TargetArch: request.TargetArch, State: state, Required: required, InstallPlanKey: request.InstallPlanKey, PlanDigest: request.PlanDigest, JobID: jobID, Message: message, CheckedAt: stamp, UpdatedAt: stamp, } if err := validator.ValidateDependencyStatus(status); err != nil { return err } if _, err := svc.store.DependencyStatuses().Get(status.ID); err == nil { return svc.store.DependencyStatuses().Update(status) } else if !errors.Is(err, repo.ErrNotFound) { return err } return svc.store.DependencyStatuses().Create(status) } func (svc *CoreService) recordAuditEvent(actorID string, action string, resourceKind string, resourceID string, result domain.AuditResult, summary string) error { _, err := svc.recordAuditEventWithID(actorID, action, resourceKind, resourceID, result, summary) return err } func (svc *CoreService) recordAuditEventWithID(actorID string, action string, resourceKind string, resourceID string, result domain.AuditResult, summary string) (string, error) { svc.auditMu.Lock() svc.auditSeq++ seq := svc.auditSeq svc.auditMu.Unlock() stamp := svc.now() event := domain.AuditEvent{ ID: fmt.Sprintf("audit-%s-%d-%d", strings.ReplaceAll(action, ".", "-"), stamp.UnixNano(), seq), ActorID: actorID, Action: action, ResourceKind: resourceKind, ResourceID: resourceID, Result: result, Summary: safeBridgeReason(summary), CreatedAt: stamp, } if err := validator.ValidateAuditEvent(event); err != nil { return "", err } if err := svc.store.AuditEvents().Create(event); err != nil { return "", err } return event.ID, nil } func (svc *CoreService) auditArtifactDownload(sessionID string, artifact domain.Artifact) error { user, err := svc.GetCurrentUser(sessionID) if err != nil { return err } runDistributions, err := svc.store.RunDistributions().List(domain.RunDistributionFilter{}) if err != nil { return err } for _, distribution := range runDistributions { if distribution.ArtifactID == artifact.ID { return svc.recordAuditEvent(user.ID, "run.download", "server-instance", distribution.ServerInstanceID, domain.AuditResultSuccess, "downloaded run package artifact with redacted runtime key ref") } } clientDistributions, err := svc.store.ClientManagerDistributions().List(domain.ClientManagerDistributionFilter{}) if err != nil { return err } for _, distribution := range clientDistributions { if distribution.ArtifactID == artifact.ID { return svc.recordAuditEvent(user.ID, "client-manager.download", "server-instance", distribution.ServerInstanceID, domain.AuditResultSuccess, "downloaded client-manager package artifact with redacted runtime key ref") } } return svc.recordAuditEvent(user.ID, "artifact.download", string(artifact.OwnerKind), artifact.OwnerID, domain.AuditResultSuccess, "downloaded platform artifact") } func validatePluginTarget(plugin domain.GamePlugin, targetOS string) error { if len(plugin.SupportedOS) == 0 || containsString(plugin.SupportedOS, targetOS) { return nil } return validationError("targetOs is not declared by plugin") } func runPackageFormatForTarget(targetOS string) string { _ = targetOS return "raw-executable" } func packageFormatForTarget(targetOS string) string { if targetOS == "windows" { return "zip" } return "tar.gz" } func runReleasePlatformURL() string { if value := strings.TrimSpace(os.Getenv("PLATFORM_RUN_RELEASE_URL")); value != "" { return value } return "https://scum.npc0.com" } func distributionID(prefix string, parts ...interface{}) string { values := make([]string, 0, len(parts)) for _, part := range parts { values = append(values, fmt.Sprint(part)) } raw := strings.Join(values, "-") return prefix + "-" + sanitizeIDPart(raw) + "-" + fmt.Sprint(stableStringNumber(raw)) } func artifactIDForDistribution(distributionID string) string { sum := sha256.Sum256([]byte(distributionID)) return "artifact-" + sanitizeIDPart(distributionID)[:minInt(len(sanitizeIDPart(distributionID)), 48)] + "-" + hex.EncodeToString(sum[:])[:16] } func componentKeyID(serverInstanceID string, kind domain.DistributionComponentKind, componentKey string, generation int) string { return distributionID("runtime-key", serverInstanceID, kind, normalizedComponentKey(kind, componentKey), generation) } func secretRefForComponent(serverInstanceID string, kind domain.DistributionComponentKind, componentKey string) string { component := string(kind) if normalized := normalizedComponentKey(kind, componentKey); normalized != "" { component += "/" + normalized } return "secret://runtime-keys/" + sanitizeIDPart(serverInstanceID) + "/" + component + "/current" } func normalizedComponentKey(kind domain.DistributionComponentKind, componentKey string) string { componentKey = strings.TrimSpace(componentKey) if kind == domain.DistributionComponentRun { return "" } return componentKey } func sanitizeIDPart(value string) string { value = strings.TrimSpace(strings.ToLower(value)) var builder strings.Builder for _, char := range value { switch { case char >= 'a' && char <= 'z': builder.WriteRune(char) case char >= '0' && char <= '9': builder.WriteRune(char) case char == '-' || char == '_' || char == '.': builder.WriteRune(char) default: builder.WriteByte('-') } } sanitized := strings.Trim(builder.String(), "-") if sanitized == "" { return "value" } if len(sanitized) > 80 { return sanitized[:80] } return sanitized } func checksumForString(value string) string { sum := sha256.Sum256([]byte(value)) return "sha256:" + hex.EncodeToString(sum[:]) } func fingerprintForString(value string) string { sum := sha256.Sum256([]byte(value)) return hex.EncodeToString(sum[:])[:12] } func clientManagerOutputName(profileKey string, targetOS string) string { name := sanitizeIDPart(profileKey) if targetOS == "windows" { return name + ".exe" } return name } func minInt(a int, b int) int { if a < b { return a } return b } func runtimeAction(key string, label string, available bool, reason string) domain.ServerRuntimeAction { action := domain.ServerRuntimeAction{Key: key, Label: label, Available: available} if !available { action.Reason = reason } return action } func fallbackReason(primary bool, primaryReason string, fallback string) string { if primary { return primaryReason } return fallback } func (svc *CoreService) runtimeBindingsComplete(serverInstanceID string) bool { complete, _ := svc.runtimeBindingReadiness(serverInstanceID) return complete } func (svc *CoreService) runtimeBindingReadiness(serverInstanceID string) (bool, string) { binding, err := svc.runtimeBindingForServer(serverInstanceID) if errors.Is(err, repo.ErrNotFound) { return false, "runtime profile is not configured" } if err != nil { return false, "runtime binding cannot be verified" } instance, err := svc.store.ServerInstances().Get(serverInstanceID) if err != nil || binding.PluginID != instance.PluginID || binding.PluginVersion != instance.PluginVersion { return false, "runtime binding does not match the server plugin" } plugin, err := svc.store.GamePlugins().Get(instance.PluginID) if err != nil { return false, "runtime profile cannot be verified" } binding, err = normalizeRuntimeBinding(plugin, binding) if err != nil { return false, "runtime binding cannot be verified" } if binding.Status != domain.RuntimeBindingStatusComplete { return false, "missing logical bindings: " + strings.Join(binding.MissingKeys, ", ") } return true, "" } func (svc *CoreService) requireCompleteRuntimeBindings(actorID string, serverInstanceID string, deniedAction string) error { complete, reason := svc.runtimeBindingReadiness(serverInstanceID) if complete { return nil } _ = svc.recordAuditEvent(actorID, deniedAction, "server-instance", serverInstanceID, domain.AuditResultDenied, "operation denied: "+reason) return validationError(reason) } func pluginDeclares(plugin domain.GamePlugin, permission string) bool { return containsString(plugin.DeclaredPermissions, permission) } func pluginSupports(plugin domain.GamePlugin, capability string) bool { return containsString(plugin.RequiredRunCapabilities, capability) } func (svc *CoreService) endpointSupports(endpoint domain.RunEndpoint, capability string) bool { if endpoint.Status != domain.RunEndpointStatusOnline && endpoint.Status != domain.RunEndpointStatusDegraded { return false } if !svc.runEndpointHeartbeatCurrent(endpoint) { return false } return containsString(endpoint.Capabilities, capability) } func validateRunUpdateRequest(request domain.RunUpdateRequest) error { if strings.TrimSpace(request.ServerInstanceID) == "" { return validationError("serverInstanceId is required") } if strings.TrimSpace(request.ArtifactID) == "" { return validationError("artifactId is required") } if request.Checksum != "" && !strings.HasPrefix(request.Checksum, "sha256:") { return validationError("checksum must be sha256:") } if containsUnsafeRequestText(request.IdempotencyKey) { return validationError("idempotencyKey is unsafe") } return nil } func validateDependencyJobRequest(request domain.DependencyJobRequest) error { if strings.TrimSpace(request.ServerInstanceID) == "" { return validationError("serverInstanceId is required") } if !safeDistributionKey(request.ProbeKey) { return validationError("probeKey is invalid") } if request.Install && !safeDistributionKey(request.InstallPlanKey) { return validationError("installPlanKey is invalid") } if request.Install && (request.PlanDigest == "" || !strings.HasPrefix(request.PlanDigest, "sha256:") || len(request.PlanDigest) != len("sha256:")+64) { return validationError("planDigest must be a sha256 digest") } if containsUnsafeRequestText(request.IdempotencyKey) || containsUnsafeRequestText(request.TargetOS) || containsUnsafeRequestText(request.TargetArch) { return validationError("dependency request contains unsafe content") } return nil } func validateLogBackfillRequest(request domain.LogBackfillRequest) error { if strings.TrimSpace(request.ServerInstanceID) == "" { return validationError("serverInstanceId is required") } if !safeDistributionKey(request.SourceKey) { return validationError("sourceKey is invalid") } if request.CheckpointRef != "" && (!strings.HasPrefix(request.CheckpointRef, "input://") && !strings.HasPrefix(request.CheckpointRef, "artifact://")) { return validationError("checkpointRef is not allowed") } if request.Limit < 0 || request.Limit > 10000 { return validationError("limit must be between 0 and 10000") } if containsUnsafeRequestText(request.IdempotencyKey) || containsUnsafeRequestText(request.CheckpointRef) { return validationError("log backfill request contains unsafe content") } return nil } func safeDistributionKey(value string) bool { value = strings.TrimSpace(value) if value == "" || len(value) > 96 || strings.Contains(value, "..") || strings.Contains(value, "://") || strings.HasPrefix(value, "/") { return false } for _, char := range value { if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '_' || char == '-' || char == '.' || char == '/' { continue } return false } return !containsUnsafeRequestText(value) } func containsUnsafeRequestText(value string) bool { lowered := strings.ToLower(value) return strings.Contains(lowered, "bash -c") || strings.Contains(lowered, "powershell -") || strings.Contains(lowered, "cmd.exe") || strings.Contains(lowered, "curl |") || strings.Contains(lowered, "password=") || strings.Contains(lowered, "mysql://") || strings.Contains(lowered, "sqlite://") || strings.Contains(lowered, "unix://") || strings.Contains(lowered, "tcp://") || strings.Contains(lowered, "/users/") }