package validator import ( "encoding/json" "fmt" "regexp" "strings" "browser.local/platform/domain" ) const ( maxSCUMProbeObjects = 512 maxSCUMProbeColumnsPerObject = 256 maxSCUMProbeIndexesPerObject = 128 maxSCUMProbeForeignKeys = 128 maxSCUMProbeSamples = 3 maxSCUMProbeTimeoutMS = 10000 maxSCUMProbeResultBytes = 1024 * 1024 maxSCUMTemplateParameters = 64 maxSCUMTemplateRows = 1000 maxSCUMTemplateBusyTimeoutMS = 1000 maxSCUMTemplateValueBytes = 4096 maxSCUMRCONPayloadBytes = 4096 maxSCUMRCONResponseBytes = 64 * 1024 maxSCUMRCONConfirmRecords = 128 maxSCUMMutationPayloadBytes = 4096 maxSCUMMutationReadbackBytes = 64 * 1024 ) var scumHashPattern = regexp.MustCompile(`^sha256:[a-fA-F0-9]{64}$|^[a-fA-F0-9]{16,128}$`) var scumDataTargetSafeErrorPattern = regexp.MustCompile(`^data_target_[a-z0-9_]{1,80}$`) var scumTemplateKeyPattern = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_.-]{0,127}$`) func ValidateSCUMSchemaProbeRequest(request domain.SCUMSchemaProbeRequest) error { var violations []string violations = appendRequired(violations, "requestId", request.RequestID) violations = appendRequired(violations, "jobId", request.JobID) violations = append(violations, validateSCUMBindingIdentity("binding", request.Binding)...) violations = append(violations, validateSCUMSchemaProbeBounds("bounds", request.Bounds)...) return finish(violations) } func ValidateSCUMSQLiteTemplateRequest(request domain.SCUMSQLiteTemplateRequest) error { var violations []string violations = appendRequired(violations, "requestId", request.RequestID) violations = appendRequired(violations, "jobId", request.JobID) violations = append(violations, validateSCUMBindingIdentity("binding", request.Binding)...) if !validSCUMReadCapability(request.Capability) { violations = append(violations, "capability must be a read capability") } violations = append(violations, validateSCUMTemplateKey("targetKey", request.TargetKey)...) violations = append(violations, validateSCUMTemplateKey("templateKey", request.TemplateKey)...) violations = appendRequired(violations, "adapterVersion", request.AdapterVersion) if request.AdapterVersion != "" && containsSCUMProtectedMaterial(request.AdapterVersion) { violations = append(violations, "adapterVersion contains protected material") } if request.AdapterVersion != "" && request.Binding.AdapterVersion != "" && request.AdapterVersion != request.Binding.AdapterVersion { violations = append(violations, "adapterVersion must match binding.adapterVersion") } if !validSCUMFingerprint(request.RequiredSchemaFingerprint) { violations = append(violations, "requiredSchemaFingerprint must be a digest/fingerprint") } if !validSCUMDigest(request.AssetDigest) { violations = append(violations, "assetDigest must be sha256 digest") } if !validSCUMDigest(request.ParameterDigest) { violations = append(violations, "parameterDigest must be sha256 digest") } violations = append(violations, validateSCUMSQLiteTemplateBounds("bounds", request.Bounds)...) violations = append(violations, validateSCUMValueMap("parameters", request.Parameters, request.Bounds.MaxParameters)...) return finish(violations) } func ValidateSCUMTypedRCONTemplateRequest(request domain.SCUMTypedRCONTemplateRequest) error { var violations []string violations = appendRequired(violations, "requestId", request.RequestID) violations = appendRequired(violations, "jobId", request.JobID) violations = append(violations, validateSCUMBindingIdentity("binding", request.Binding)...) if !validSCUMRCONWriteCapability(request.Capability) { violations = append(violations, "capability must be a typed RCON write capability") } violations = append(violations, validateSCUMTemplateKey("transportKey", request.TransportKey)...) violations = append(violations, validateSCUMTemplateKey("targetKey", request.TargetKey)...) violations = append(violations, validateSCUMTemplateKey("templateKey", request.TemplateKey)...) violations = appendRequired(violations, "adapterVersion", request.AdapterVersion) if request.AdapterVersion != "" && containsSCUMProtectedMaterial(request.AdapterVersion) { violations = append(violations, "adapterVersion contains protected material") } if request.AdapterVersion != "" && request.Binding.AdapterVersion != "" && request.AdapterVersion != request.Binding.AdapterVersion { violations = append(violations, "adapterVersion must match binding.adapterVersion") } if request.RequiredSchemaFingerprint != "" && !validSCUMFingerprint(request.RequiredSchemaFingerprint) { violations = append(violations, "requiredSchemaFingerprint must be a digest/fingerprint") } for _, item := range []struct{ name, value string }{{"assetDigest", request.AssetDigest}, {"payloadDigest", request.PayloadDigest}, {"confirmationDigest", request.ConfirmationDigest}, {"targetIdentityDigest", request.TargetIdentityDigest}} { if !validSCUMDigest(item.value) { violations = append(violations, item.name+" must be sha256 digest") } } violations = append(violations, validateSCUMTemplateKey("idempotencyKey", request.IdempotencyKey)...) if strings.TrimSpace(request.ReviewReason) == "" || len(request.ReviewReason) > 320 || containsSCUMProtectedMaterial(request.ReviewReason) { violations = append(violations, "reviewReason is unsafe") } violations = append(violations, validateSCUMTypedRCONTemplateBounds("bounds", request.Bounds)...) violations = append(violations, validateSCUMValueMap("payload", request.Payload, 64)...) violations = append(violations, validateSCUMJSONSize("payload", request.Payload, request.Bounds.MaxPayloadBytes)...) return finish(violations) } func ValidateSCUMGuardedMutationRequest(request domain.SCUMGuardedMutationRequest) error { var violations []string violations = appendRequired(violations, "requestId", request.RequestID) violations = appendRequired(violations, "jobId", request.JobID) violations = append(violations, validateSCUMBindingIdentity("binding", request.Binding)...) if !validSCUMGuardedMutationCapability(request.Capability) { violations = append(violations, "capability must be a guarded database/XML write capability") } violations = append(violations, validateSCUMTemplateKey("targetKey", request.TargetKey)...) violations = append(violations, validateSCUMTemplateKey("templateKey", request.TemplateKey)...) violations = appendRequired(violations, "adapterVersion", request.AdapterVersion) if request.AdapterVersion != "" && containsSCUMProtectedMaterial(request.AdapterVersion) { violations = append(violations, "adapterVersion contains protected material") } if request.AdapterVersion != "" && request.Binding.AdapterVersion != "" && request.AdapterVersion != request.Binding.AdapterVersion { violations = append(violations, "adapterVersion must match binding.adapterVersion") } if !validSCUMFingerprint(request.RequiredSchemaFingerprint) { violations = append(violations, "requiredSchemaFingerprint must be a digest/fingerprint") } for _, item := range scumGuardedMutationRequestDigests(request) { if !validSCUMDigest(item.value) { violations = append(violations, item.name+" must be sha256 digest") } } violations = append(violations, validateSCUMTemplateKey("idempotencyKey", request.IdempotencyKey)...) if strings.TrimSpace(request.ReviewReason) == "" || len(request.ReviewReason) > 320 || containsSCUMProtectedMaterial(request.ReviewReason) || containsSCUMRawXML(request.ReviewReason) { violations = append(violations, "reviewReason is unsafe") } violations = append(violations, validateSCUMGuardedMutationBounds("bounds", request.Bounds)...) violations = append(violations, validateSCUMGuardedMutationPayload("payload", request.Payload)...) violations = append(violations, validateSCUMJSONSize("payload", request.Payload, request.Bounds.MaxPayloadBytes)...) return finish(violations) } func ValidateSCUMSchemaProbeResult(result domain.SCUMSchemaProbeResult) error { var violations []string violations = appendRequired(violations, "requestId", result.RequestID) violations = appendRequired(violations, "jobId", result.JobID) violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...) if !validSCUMSchemaProbeResultStatus(result.Status) { violations = append(violations, "status is invalid") } if result.SourceFingerprint != "" && !validSCUMFingerprint(result.SourceFingerprint) { violations = append(violations, "sourceFingerprint must be a digest/fingerprint") } if result.SchemaFingerprint != "" && !validSCUMFingerprint(result.SchemaFingerprint) { violations = append(violations, "schemaFingerprint must be a digest/fingerprint") } if result.ResultDigest != "" && !validSCUMFingerprint(result.ResultDigest) { violations = append(violations, "resultDigest must be a digest/fingerprint") } violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...) violations = append(violations, validateSCUMSchemaProbeBounds("limits", result.Limits)...) if len(result.Objects) > result.Limits.MaxObjects && result.Limits.MaxObjects > 0 { violations = append(violations, "objects exceeds declared limit") } for i, object := range result.Objects { field := fmt.Sprintf("objects[%d]", i) violations = append(violations, validateSCUMSchemaObjectEvidence(field, object)...) } return finish(violations) } func ValidateSCUMSQLiteTemplateResult(result domain.SCUMSQLiteTemplateResult) error { var violations []string violations = appendRequired(violations, "requestId", result.RequestID) violations = appendRequired(violations, "jobId", result.JobID) violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...) if !validSCUMTerminalResultStatus(result.Status) { violations = append(violations, "status is invalid") } if !validSCUMReadCapability(result.Capability) { violations = append(violations, "capability must be a read capability") } violations = append(violations, validateSCUMTemplateKey("targetKey", result.TargetKey)...) violations = append(violations, validateSCUMTemplateKey("templateKey", result.TemplateKey)...) violations = appendRequired(violations, "adapterVersion", result.AdapterVersion) if result.AdapterVersion != "" && containsSCUMProtectedMaterial(result.AdapterVersion) { violations = append(violations, "adapterVersion contains protected material") } if result.AdapterVersion != "" && result.Binding.AdapterVersion != "" && result.AdapterVersion != result.Binding.AdapterVersion { violations = append(violations, "adapterVersion must match binding.adapterVersion") } if result.SchemaFingerprint != "" && !validSCUMFingerprint(result.SchemaFingerprint) { violations = append(violations, "schemaFingerprint must be a digest/fingerprint") } if !validSCUMDigest(result.AssetDigest) { violations = append(violations, "assetDigest must be sha256 digest") } if !validSCUMDigest(result.ParameterDigest) { violations = append(violations, "parameterDigest must be sha256 digest") } if result.SourceFingerprint != "" && !validSCUMFingerprint(result.SourceFingerprint) { violations = append(violations, "sourceFingerprint must be a digest/fingerprint") } if !validSCUMFingerprint(result.ResultDigest) { violations = append(violations, "resultDigest must be a digest/fingerprint") } violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...) violations = append(violations, validateSCUMSQLiteTemplateBounds("limits", result.Limits)...) if result.RowCount != len(result.Rows) { violations = append(violations, "rowCount must match returned rows") } if len(result.Rows) > result.Limits.MaxRows && result.Limits.MaxRows > 0 { violations = append(violations, "rows exceeds declared limit") } for i, row := range result.Rows { violations = append(violations, validateSCUMValueMap(fmt.Sprintf("rows[%d]", i), row, 256)...) } if result.Status == domain.SCUMTerminalResultSucceeded { if result.SchemaFingerprint == "" || result.SourceFingerprint == "" { violations = append(violations, "succeeded result requires schema/source fingerprints") } if result.SafeError.Code != "" && result.SafeError.Code != domain.SCUMSafeErrorNone { violations = append(violations, "succeeded result must not carry an error code") } } else if result.SafeError.Code == "" || result.SafeError.Code == domain.SCUMSafeErrorNone { violations = append(violations, "non-succeeded result requires a safe error code") } return finish(violations) } func ValidateSCUMTypedRCONTemplateResult(result domain.SCUMTypedRCONTemplateResult) error { var violations []string violations = appendRequired(violations, "requestId", result.RequestID) violations = appendRequired(violations, "jobId", result.JobID) violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...) if !validSCUMTerminalResultStatus(result.Status) { violations = append(violations, "status is invalid") } if !validSCUMRCONWriteCapability(result.Capability) { violations = append(violations, "capability must be a typed RCON write capability") } violations = append(violations, validateSCUMTemplateKey("transportKey", result.TransportKey)...) violations = append(violations, validateSCUMTemplateKey("targetKey", result.TargetKey)...) violations = append(violations, validateSCUMTemplateKey("templateKey", result.TemplateKey)...) violations = appendRequired(violations, "adapterVersion", result.AdapterVersion) if result.AdapterVersion != "" && containsSCUMProtectedMaterial(result.AdapterVersion) { violations = append(violations, "adapterVersion contains protected material") } if result.AdapterVersion != "" && result.Binding.AdapterVersion != "" && result.AdapterVersion != result.Binding.AdapterVersion { violations = append(violations, "adapterVersion must match binding.adapterVersion") } if result.SchemaFingerprint != "" && !validSCUMFingerprint(result.SchemaFingerprint) { violations = append(violations, "schemaFingerprint must be a digest/fingerprint") } for _, item := range []struct{ name, value string }{{"assetDigest", result.AssetDigest}, {"payloadDigest", result.PayloadDigest}, {"confirmationDigest", result.ConfirmationDigest}, {"targetIdentityDigest", result.TargetIdentityDigest}, {"resultDigest", result.ResultDigest}} { if !validSCUMDigest(item.value) { violations = append(violations, item.name+" must be sha256 digest") } } if result.ResponseDigest != "" && !validSCUMDigest(result.ResponseDigest) { violations = append(violations, "responseDigest must be sha256 digest") } if result.ConfirmationDigestID != "" && !validSCUMDigest(result.ConfirmationDigestID) { violations = append(violations, "confirmationDigestId must be sha256 digest") } if !validSCUMRCONConfirmationStatus(result.ConfirmationStatus) { violations = append(violations, "confirmationStatus is invalid") } if len(result.SafeSummary) > 320 || containsSCUMProtectedMaterial(result.SafeSummary) { violations = append(violations, "safeSummary is unsafe") } violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...) violations = append(violations, validateSCUMTypedRCONTemplateBounds("limits", result.Limits)...) if result.Status == domain.SCUMTerminalResultSucceeded { if result.ConfirmationStatus != domain.SCUMRCONConfirmationConfirmed || result.ResponseDigest == "" || result.ConfirmationDigestID == "" { violations = append(violations, "succeeded result requires confirmed response and confirmation digests") } if result.SafeError.Code != "" && result.SafeError.Code != domain.SCUMSafeErrorNone { violations = append(violations, "succeeded result must not carry an error code") } } else if result.SafeError.Code == "" || result.SafeError.Code == domain.SCUMSafeErrorNone { violations = append(violations, "non-succeeded result requires a safe error code") } return finish(violations) } func ValidateSCUMGuardedMutationResult(result domain.SCUMGuardedMutationResult) error { var violations []string violations = appendRequired(violations, "requestId", result.RequestID) violations = appendRequired(violations, "jobId", result.JobID) violations = append(violations, validateSCUMBindingIdentity("binding", result.Binding)...) if !validSCUMTerminalResultStatus(result.Status) { violations = append(violations, "status is invalid") } if !validSCUMGuardedMutationCapability(result.Capability) { violations = append(violations, "capability must be a guarded database/XML write capability") } violations = append(violations, validateSCUMTemplateKey("targetKey", result.TargetKey)...) violations = append(violations, validateSCUMTemplateKey("templateKey", result.TemplateKey)...) violations = appendRequired(violations, "adapterVersion", result.AdapterVersion) if result.AdapterVersion != "" && containsSCUMProtectedMaterial(result.AdapterVersion) { violations = append(violations, "adapterVersion contains protected material") } if result.AdapterVersion != "" && result.Binding.AdapterVersion != "" && result.AdapterVersion != result.Binding.AdapterVersion { violations = append(violations, "adapterVersion must match binding.adapterVersion") } if !validSCUMFingerprint(result.SchemaFingerprint) { violations = append(violations, "schemaFingerprint must be a digest/fingerprint") } if result.SourceFingerprint != "" && !validSCUMFingerprint(result.SourceFingerprint) { violations = append(violations, "sourceFingerprint must be a digest/fingerprint") } for _, item := range scumGuardedMutationResultDigests(result) { if !validSCUMDigest(item.value) { violations = append(violations, item.name+" must be sha256 digest") } } if !validSCUMMutationReadbackStatus(result.ReadbackStatus) { violations = append(violations, "readbackStatus is invalid") } if len(result.SafeSummary) > 320 || containsSCUMProtectedMaterial(result.SafeSummary) || containsSCUMRawXML(result.SafeSummary) { violations = append(violations, "safeSummary is unsafe") } violations = append(violations, validateSCUMSafeError("safeError", result.SafeError)...) violations = append(violations, validateSCUMGuardedMutationBounds("limits", result.Limits)...) if result.Status == domain.SCUMTerminalResultSucceeded { if result.AffectedRows != 1 || result.Limits.MaxAffectedRows != 1 { violations = append(violations, "succeeded result requires exactly one affected row") } if result.ReadbackStatus != domain.SCUMMutationReadbackConfirmed || result.SourceFingerprint == "" || result.BeforeDigest == "" || result.AfterDigest == "" || result.ReadbackDigest == "" { violations = append(violations, "succeeded result requires conclusive before/after/readback digests") } if result.SafeError.Code != "" && result.SafeError.Code != domain.SCUMSafeErrorNone { violations = append(violations, "succeeded result must not carry an error code") } } else if result.SafeError.Code == "" || result.SafeError.Code == domain.SCUMSafeErrorNone { violations = append(violations, "non-succeeded result requires a safe error code") } return finish(violations) } func scumGuardedMutationRequestDigests(request domain.SCUMGuardedMutationRequest) []struct{ name, value string } { return []struct{ name, value string }{{"assetDigest", request.AssetDigest}, {"targetIdentityDigest", request.TargetIdentityDigest}, {"expectedRowDigest", request.ExpectedRowDigest}, {"expectedValueDigest", request.ExpectedValueDigest}, {"expectedXmlDigest", request.ExpectedXMLDigest}, {"patchDigest", request.PatchDigest}, {"backupEvidenceDigest", request.BackupEvidenceDigest}, {"offlineEvidenceDigest", request.OfflineEvidenceDigest}, {"dangerConfirmationDigest", request.DangerConfirmationDigest}, {"readbackExpectationDigest", request.ReadbackExpectationDigest}} } func scumGuardedMutationResultDigests(result domain.SCUMGuardedMutationResult) []struct{ name, value string } { items := []struct{ name, value string }{{"assetDigest", result.AssetDigest}, {"targetIdentityDigest", result.TargetIdentityDigest}, {"expectedRowDigest", result.ExpectedRowDigest}, {"expectedValueDigest", result.ExpectedValueDigest}, {"expectedXmlDigest", result.ExpectedXMLDigest}, {"patchDigest", result.PatchDigest}, {"backupEvidenceDigest", result.BackupEvidenceDigest}, {"offlineEvidenceDigest", result.OfflineEvidenceDigest}, {"dangerConfirmationDigest", result.DangerConfirmationDigest}, {"readbackExpectationDigest", result.ReadbackExpectationDigest}, {"resultDigest", result.ResultDigest}} for _, item := range []struct{ name, value string }{{"beforeDigest", result.BeforeDigest}, {"afterDigest", result.AfterDigest}, {"readbackDigest", result.ReadbackDigest}} { if item.value != "" { items = append(items, item) } } return items } func ValidateSCUMCapabilityEvidence(evidence domain.SCUMCapabilityEvidence) error { var violations []string if !validSCUMDataCapability(evidence.Capability) { violations = append(violations, "capability is invalid") } if !validSCUMCapabilityEvidenceStatus(evidence.Status) { violations = append(violations, "status is invalid") } violations = append(violations, validateSCUMBindingIdentity("binding", evidence.Binding)...) violations = appendRequired(violations, "adapterVersion", evidence.AdapterVersion) if evidence.SchemaFingerprint != "" && !validSCUMFingerprint(evidence.SchemaFingerprint) { violations = append(violations, "schemaFingerprint must be a digest/fingerprint") } if evidence.ProbeResultDigest != "" && !validSCUMFingerprint(evidence.ProbeResultDigest) { violations = append(violations, "probeResultDigest must be a digest/fingerprint") } for i, digest := range evidence.AssetDigests { if !validSCUMDigest(digest) { violations = append(violations, fmt.Sprintf("assetDigests[%d] must be sha256 digest", i)) } } violations = append(violations, validateSCUMSafeError("safeError", evidence.SafeError)...) return finish(violations) } func validateSCUMLiveDataManifest(prefix string, value domain.SCUMLiveDataManifest, capabilities []string, remoteAccess domain.GamePluginRemoteAccess, runtimeProfiles domain.GamePluginRuntimeProfiles) []string { if value.SchemaVersion == "" && value.Probe.Capability == "" && len(value.CapabilityGates) == 0 { return nil } var violations []string if value.SchemaVersion != "1" { violations = append(violations, prefix+".schemaVersion must be 1") } if value.Probe.Capability != domain.JobCapabilityRemoteRunDBSQLiteProbe { violations = append(violations, prefix+".probe.capability must be "+domain.JobCapabilityRemoteRunDBSQLiteProbe) } if !containsString(capabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe) || !containsString(remoteAccess.RunCapabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe) { violations = append(violations, prefix+".probe requires declared remote.run.db.sqlite.probe capability") } probeTransportFound := false probeDataTargetFound := false expectedWorkspaceKey := "databases/" + strings.TrimPrefix(value.Probe.TargetKey, "databases/") for _, transport := range runtimeProfiles.TransportProfiles { if transport.Key != value.Probe.TargetKey && transport.TargetKey != value.Probe.TargetKey { continue } probeTransportFound = true if transport.Kind != "sqlite" || !containsString(transport.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe) { violations = append(violations, prefix+".probe.targetKey must reference sqlite transport with remote.run.db.sqlite.probe") } } if !probeTransportFound { violations = append(violations, prefix+".probe.targetKey must reference a declared transport") } for _, target := range runtimeProfiles.DataTargets { if target.Key != value.Probe.TargetKey { continue } probeDataTargetFound = true transport, ok := transportByKey(runtimeProfiles.TransportProfiles, target.TransportKey) if target.Kind != "sqlite.snapshot" || target.WorkspaceKey != expectedWorkspaceKey || !ok || transport.Kind != "sqlite" || !containsString(transport.Capabilities, domain.JobCapabilityRemoteRunDBSQLiteProbe) { violations = append(violations, prefix+".probe.targetKey must reference a sqlite snapshot data target for the generated Run workspace") } } if !probeDataTargetFound { violations = append(violations, prefix+".probe.targetKey must reference a declared runtime data target") } violations = append(violations, validateSCUMSchemaProbeBounds(prefix+".probe.bounds", value.Probe.Bounds)...) seen := map[domain.SCUMDataCapability]struct{}{} for i, gate := range value.CapabilityGates { field := fmt.Sprintf("%s.capabilityGates[%d]", prefix, i) if !validSCUMDataCapability(gate.Capability) { violations = append(violations, field+".capability is invalid") } if _, exists := seen[gate.Capability]; exists { violations = append(violations, field+".capability is duplicated") } seen[gate.Capability] = struct{}{} if gate.Gate != domain.SCUMCapabilityGateDisabled && gate.Gate != domain.SCUMCapabilityGateEnabled { violations = append(violations, field+".gate is invalid") } violations = appendRequired(violations, field+".adapterVersion", gate.AdapterVersion) if !validSCUMCapabilityEvidenceStatus(gate.EvidenceStatus) { violations = append(violations, field+".evidenceStatus is invalid") } if containsSCUMProtectedMaterial(gate.SafeReason) || len(gate.SafeReason) > 240 || strings.TrimSpace(gate.SafeReason) == "" { violations = append(violations, field+".safeReason is unsafe") } if gate.Gate == domain.SCUMCapabilityGateEnabled { if gate.EvidenceStatus != domain.SCUMCapabilityEvidenceCompatible { violations = append(violations, field+".evidenceStatus must be compatible when enabled") } if !validSCUMFingerprint(gate.RequiredSchemaFingerprint) { violations = append(violations, field+".requiredSchemaFingerprint is required when enabled") } if gate.Capability != domain.SCUMDataCapabilitySchemaProbe && len(gate.RequiredAssetDigests) == 0 { violations = append(violations, field+".requiredAssetDigests is required when enabled") } } if gate.Gate == domain.SCUMCapabilityGateDisabled && gate.EvidenceStatus == domain.SCUMCapabilityEvidenceCompatible { violations = append(violations, field+".evidenceStatus must not claim compatibility while disabled") } for digestIndex, digest := range gate.RequiredAssetDigests { if !validSCUMDigest(digest) { violations = append(violations, fmt.Sprintf("%s.requiredAssetDigests[%d] must be sha256 digest", field, digestIndex)) } } } return violations } func validateSCUMBindingIdentity(prefix string, value domain.SCUMBindingIdentity) []string { var violations []string for _, item := range []struct{ name, value string }{{"serverInstanceId", value.ServerInstanceID}, {"runBindingId", value.RunBindingID}, {"runEndpointId", value.RunEndpointID}, {"pluginId", value.PluginID}, {"pluginVersion", value.PluginVersion}, {"adapterVersion", value.AdapterVersion}, {"databaseIdentity", value.DatabaseIdentity}} { violations = appendRequired(violations, prefix+"."+item.name, item.value) if containsSCUMProtectedMaterial(item.value) { violations = append(violations, prefix+"."+item.name+" contains protected connection material") } } if value.GameVersion != "" && containsSCUMProtectedMaterial(value.GameVersion) { violations = append(violations, prefix+".gameVersion contains protected connection material") } return violations } func validateSCUMSchemaProbeBounds(prefix string, value domain.SCUMSchemaProbeBounds) []string { var violations []string if value.MaxObjects < 1 || value.MaxObjects > maxSCUMProbeObjects { violations = append(violations, prefix+".maxObjects is out of bounds") } if value.MaxColumnsPerObject < 1 || value.MaxColumnsPerObject > maxSCUMProbeColumnsPerObject { violations = append(violations, prefix+".maxColumnsPerObject is out of bounds") } if value.MaxIndexesPerObject < 0 || value.MaxIndexesPerObject > maxSCUMProbeIndexesPerObject { violations = append(violations, prefix+".maxIndexesPerObject is out of bounds") } if value.MaxForeignKeys < 0 || value.MaxForeignKeys > maxSCUMProbeForeignKeys { violations = append(violations, prefix+".maxForeignKeys is out of bounds") } if value.MaxCardinalityReads < 0 || value.MaxCardinalityReads > maxSCUMProbeObjects { violations = append(violations, prefix+".maxCardinalityReads is out of bounds") } if value.MaxSampleRows < 0 || value.MaxSampleRows > maxSCUMProbeSamples { violations = append(violations, prefix+".maxSampleRows is out of bounds") } if value.TimeoutMS < 1 || value.TimeoutMS > maxSCUMProbeTimeoutMS { violations = append(violations, prefix+".timeoutMs is out of bounds") } if value.MaxResultBytes < 1 || value.MaxResultBytes > maxSCUMProbeResultBytes { violations = append(violations, prefix+".maxResultBytes is out of bounds") } return violations } func validateSCUMSQLiteTemplateBounds(prefix string, value domain.SCUMSQLiteTemplateBounds) []string { var violations []string if value.MaxParameters < 0 || value.MaxParameters > maxSCUMTemplateParameters { violations = append(violations, prefix+".maxParameters is out of bounds") } if value.MaxRows < 1 || value.MaxRows > maxSCUMTemplateRows { violations = append(violations, prefix+".maxRows is out of bounds") } if value.TimeoutMS < 1 || value.TimeoutMS > maxSCUMProbeTimeoutMS { violations = append(violations, prefix+".timeoutMs is out of bounds") } if value.BusyTimeoutMS < 0 || value.BusyTimeoutMS > maxSCUMTemplateBusyTimeoutMS { violations = append(violations, prefix+".busyTimeoutMs is out of bounds") } if value.MaxResultBytes < 1 || value.MaxResultBytes > maxSCUMProbeResultBytes { violations = append(violations, prefix+".maxResultBytes is out of bounds") } return violations } func validateSCUMTypedRCONTemplateBounds(prefix string, value domain.SCUMTypedRCONTemplateBounds) []string { var violations []string if value.MaxPayloadBytes < 1 || value.MaxPayloadBytes > maxSCUMRCONPayloadBytes { violations = append(violations, prefix+".maxPayloadBytes is out of bounds") } if value.TimeoutMS < 1 || value.TimeoutMS > maxSCUMProbeTimeoutMS { violations = append(violations, prefix+".timeoutMs is out of bounds") } if value.MaxResponseBytes < 1 || value.MaxResponseBytes > maxSCUMRCONResponseBytes { violations = append(violations, prefix+".maxResponseBytes is out of bounds") } if value.MaxConfirmRecords < 1 || value.MaxConfirmRecords > maxSCUMRCONConfirmRecords { violations = append(violations, prefix+".maxConfirmRecords is out of bounds") } return violations } func validateSCUMGuardedMutationBounds(prefix string, value domain.SCUMGuardedMutationBounds) []string { var violations []string if value.MaxPayloadBytes < 1 || value.MaxPayloadBytes > maxSCUMMutationPayloadBytes { violations = append(violations, prefix+".maxPayloadBytes is out of bounds") } if value.TimeoutMS < 1 || value.TimeoutMS > maxSCUMProbeTimeoutMS { violations = append(violations, prefix+".timeoutMs is out of bounds") } if value.BusyTimeoutMS < 0 || value.BusyTimeoutMS > maxSCUMTemplateBusyTimeoutMS { violations = append(violations, prefix+".busyTimeoutMs is out of bounds") } if value.MaxReadbackBytes < 1 || value.MaxReadbackBytes > maxSCUMMutationReadbackBytes { violations = append(violations, prefix+".maxReadbackBytes is out of bounds") } if value.MaxAffectedRows != 1 { violations = append(violations, prefix+".maxAffectedRows must be 1") } return violations } func validateSCUMTemplateKey(prefix, value string) []string { var violations []string violations = appendRequired(violations, prefix, value) if value != "" && (!scumTemplateKeyPattern.MatchString(value) || strings.Contains(value, "..")) { violations = append(violations, prefix+" is unsafe") } return violations } func validateSCUMValueMap(prefix string, values map[string]any, maxItems int) []string { var violations []string if maxItems >= 0 && len(values) > maxItems { violations = append(violations, prefix+" exceeds declared limit") } for key, value := range values { loweredKey := strings.ToLower(key) if !scumTemplateKeyPattern.MatchString(key) || containsSCUMProtectedMaterial(key) || strings.Contains(key, "..") || strings.Contains(loweredKey, "command") || strings.Contains(loweredKey, "rcon") { violations = append(violations, prefix+" key is unsafe") } field := prefix + ".value" violations = append(violations, validateSCUMValue(field, value)...) } return violations } func validateSCUMGuardedMutationPayload(prefix string, values map[string]any) []string { violations := validateSCUMValueMap(prefix, values, 64) for key, value := range values { loweredKey := strings.ToLower(key) if strings.Contains(loweredKey, "sql") || strings.Contains(loweredKey, "xml") || strings.Contains(loweredKey, "path") || strings.Contains(loweredKey, "table") || strings.Contains(loweredKey, "column") || strings.Contains(loweredKey, "query") || strings.Contains(loweredKey, "raw") || strings.Contains(loweredKey, "855") { violations = append(violations, prefix+" key is unsafe") } if text, ok := value.(string); ok { trimmed := strings.TrimSpace(strings.ToLower(text)) if containsSCUMRawXML(text) || trimmed == "855" || strings.Contains(trimmed, "fieldkey=855") || strings.Contains(trimmed, "prisoner.value") { violations = append(violations, prefix+" value is unsafe") } } } return violations } func validateSCUMValue(prefix string, value any) []string { var violations []string switch item := value.(type) { case nil, bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64: return nil case string: if len([]byte(item)) > maxSCUMTemplateValueBytes { violations = append(violations, prefix+" is too large") } if containsSCUMProtectedMaterial(item) { violations = append(violations, prefix+" contains protected material") } default: violations = append(violations, prefix+" must be a scalar value") } return violations } func validateSCUMJSONSize(prefix string, value any, maxBytes int) []string { if maxBytes <= 0 { return []string{prefix + " max byte limit is required"} } payload, err := json.Marshal(value) if err != nil { return []string{prefix + " must be JSON serializable"} } if len(payload) > maxBytes { return []string{prefix + " exceeds declared byte limit"} } return nil } func validateSCUMSchemaObjectEvidence(prefix string, value domain.SCUMSchemaObjectEvidence) []string { var violations []string if !validSCUMFingerprint(value.ObjectHash) { violations = append(violations, prefix+".objectHash must be a digest/fingerprint") } if value.Kind != "table" && value.Kind != "view" && value.Kind != "index" && value.Kind != "trigger" { violations = append(violations, prefix+".kind is invalid") } if !validSCUMFingerprint(value.NameFingerprint) || containsSCUMProtectedMaterial(value.NameFingerprint) { violations = append(violations, prefix+".nameFingerprint must be redacted") } for i, column := range value.DeclaredColumns { field := fmt.Sprintf("%s.declaredColumns[%d]", prefix, i) if !validSCUMFingerprint(column.NameFingerprint) || containsSCUMProtectedMaterial(column.NameFingerprint) { violations = append(violations, field+".nameFingerprint must be redacted") } if containsSCUMProtectedMaterial(column.DeclaredType) { violations = append(violations, field+".declaredType contains protected material") } } for i, index := range value.Indexes { field := fmt.Sprintf("%s.indexes[%d]", prefix, i) if !validSCUMFingerprint(index.NameFingerprint) || containsSCUMProtectedMaterial(index.NameFingerprint) { violations = append(violations, field+".nameFingerprint must be redacted") } for columnIndex, hash := range index.ColumnHashes { if !validSCUMFingerprint(hash) { violations = append(violations, fmt.Sprintf("%s.columnHashes[%d] must be a digest/fingerprint", field, columnIndex)) } } } for i, fk := range value.ForeignKeys { field := fmt.Sprintf("%s.foreignKeys[%d]", prefix, i) for _, item := range []struct{ name, value string }{{"fromColumnHash", fk.FromColumnHash}, {"toObjectHash", fk.ToObjectHash}, {"toColumnHash", fk.ToColumnHash}} { if !validSCUMFingerprint(item.value) { violations = append(violations, field+"."+item.name+" must be a digest/fingerprint") } } } for i, sample := range value.SampleFingerprints { if !validSCUMFingerprint(sample) || containsSCUMProtectedMaterial(sample) { violations = append(violations, fmt.Sprintf("%s.sampleFingerprints[%d] must be a redacted fingerprint", prefix, i)) } } return violations } func validateSCUMSafeError(prefix string, value domain.SCUMSafeError) []string { var violations []string if !validSCUMSafeErrorCode(value.Code) { violations = append(violations, prefix+".code is invalid") } if containsSCUMProtectedMaterial(value.Message) { violations = append(violations, prefix+".message contains protected material") } if len(value.Message) > 320 { violations = append(violations, prefix+".message is too long") } return violations } func validSCUMDataCapability(value domain.SCUMDataCapability) bool { switch value { case domain.SCUMDataCapabilitySchemaProbe, domain.SCUMDataCapabilityPlayerRead, domain.SCUMDataCapabilityPlayerDetailRead, domain.SCUMDataCapabilitySquadRead, domain.SCUMDataCapabilitySquadMemberRead, domain.SCUMDataCapabilityVehicleRead, domain.SCUMDataCapabilityFlagRead, domain.SCUMDataCapabilityPositionRead, domain.SCUMDataCapabilityProfileXMLWrite, domain.SCUMDataCapabilityEconomyCommand, domain.SCUMDataCapabilityGiftCommand: return true default: return false } } func validSCUMReadCapability(value domain.SCUMDataCapability) bool { switch value { case domain.SCUMDataCapabilityPlayerRead, domain.SCUMDataCapabilityPlayerDetailRead, domain.SCUMDataCapabilitySquadRead, domain.SCUMDataCapabilitySquadMemberRead, domain.SCUMDataCapabilityVehicleRead, domain.SCUMDataCapabilityFlagRead, domain.SCUMDataCapabilityPositionRead: return true default: return false } } func validSCUMRCONWriteCapability(value domain.SCUMDataCapability) bool { switch value { case domain.SCUMDataCapabilityEconomyCommand, domain.SCUMDataCapabilityGiftCommand: return true default: return false } } func validSCUMGuardedMutationCapability(value domain.SCUMDataCapability) bool { switch value { case domain.SCUMDataCapabilityProfileXMLWrite: return true default: return false } } func validSCUMTerminalResultStatus(value domain.SCUMTerminalResultStatus) bool { switch value { case domain.SCUMTerminalResultSucceeded, domain.SCUMTerminalResultFailed, domain.SCUMTerminalResultCancelled: return true default: return false } } func validSCUMRCONConfirmationStatus(value domain.SCUMRCONConfirmationStatus) bool { switch value { case domain.SCUMRCONConfirmationConfirmed, domain.SCUMRCONConfirmationFailed, domain.SCUMRCONConfirmationUnknown: return true default: return false } } func validSCUMMutationReadbackStatus(value domain.SCUMMutationReadbackStatus) bool { switch value { case domain.SCUMMutationReadbackConfirmed, domain.SCUMMutationReadbackFailed, domain.SCUMMutationReadbackConflict, domain.SCUMMutationReadbackUnknown: return true default: return false } } func validSCUMCapabilityEvidenceStatus(value domain.SCUMCapabilityEvidenceStatus) bool { switch value { case domain.SCUMCapabilityEvidenceMissing, domain.SCUMCapabilityEvidenceCompatible, domain.SCUMCapabilityEvidenceIncompatible, domain.SCUMCapabilityEvidenceFailed: return true default: return false } } func validSCUMSchemaProbeResultStatus(value domain.SCUMCapabilityEvidenceStatus) bool { switch value { case domain.SCUMSchemaProbeStatusSucceeded, domain.SCUMCapabilityEvidenceCompatible, domain.SCUMCapabilityEvidenceFailed: return true default: return false } } func validSCUMSafeErrorCode(value domain.SCUMSafeErrorCode) bool { switch value { case "", domain.SCUMSafeErrorNone, domain.SCUMSafeErrorProbeExecutorAbsent, domain.SCUMSafeErrorProbeMissing, domain.SCUMSafeErrorProbeFailed, domain.SCUMSafeErrorSchemaIncompatible, domain.SCUMSafeErrorBindingMismatch, domain.SCUMSafeErrorAdapterMismatch, domain.SCUMSafeErrorFingerprintMismatch, domain.SCUMSafeErrorDigestMismatch, domain.SCUMSafeErrorEvidenceExpired, domain.SCUMSafeErrorInvalidProbePayload, domain.SCUMSafeErrorInvalidRequest, domain.SCUMSafeErrorTargetUnavailable, domain.SCUMSafeErrorSourceUnavailable, domain.SCUMSafeErrorSQLiteOpenFailed, domain.SCUMSafeErrorSQLiteReadFailed, domain.SCUMSafeErrorDatabaseBusy, domain.SCUMSafeErrorTimeout, domain.SCUMSafeErrorCancelled, domain.SCUMSafeErrorSourceChanged, domain.SCUMSafeErrorResultLimitExceeded, domain.SCUMSafeErrorTemplateMissing, domain.SCUMSafeErrorTemplateMismatch, domain.SCUMSafeErrorParameterInvalid, domain.SCUMSafeErrorRowLimitExceeded, domain.SCUMSafeErrorResultSchemaInvalid, domain.SCUMSafeErrorMutationGuardMismatch, domain.SCUMSafeErrorMutationBackupUnavailable, domain.SCUMSafeErrorMutationOfflineRequired, domain.SCUMSafeErrorMutationConfirmationMissing, domain.SCUMSafeErrorMutationPatchInvalid, domain.SCUMSafeErrorAffectedRowsMismatch, domain.SCUMSafeErrorReadbackMismatch, domain.SCUMSafeErrorRollbackFailed: return true default: return scumDataTargetSafeErrorPattern.MatchString(string(value)) } } func containsSCUMRawXML(value string) bool { return regexp.MustCompile(`<\s*/?\s*[A-Za-z][^>]*>`).MatchString(value) } func validSCUMFingerprint(value string) bool { return scumHashPattern.MatchString(value) } func validSCUMDigest(value string) bool { return regexp.MustCompile(`^sha256:[a-fA-F0-9]{64}$`).MatchString(value) } func containsSCUMProtectedMaterial(value string) bool { trimmed := strings.TrimSpace(value) lowered := strings.ToLower(trimmed) if trimmed == "" { return false } if strings.Contains(lowered, "select ") || strings.Contains(lowered, "insert into") || strings.Contains(lowered, "update ") || strings.Contains(lowered, "delete from") || strings.Contains(lowered, "pragma ") || strings.Contains(lowered, "attach database") { return true } if strings.Contains(lowered, "dsn") || strings.Contains(lowered, "password") || strings.Contains(lowered, "credential") || strings.Contains(lowered, "token") || strings.Contains(lowered, "socket") || strings.Contains(lowered, "rcon") { return true } if strings.HasPrefix(lowered, "sqlite://") || strings.HasPrefix(lowered, "mysql://") || strings.HasPrefix(lowered, "file://") || strings.HasPrefix(lowered, "tcp://") || strings.HasPrefix(lowered, "unix://") { return true } if strings.HasPrefix(trimmed, "/") || strings.HasPrefix(trimmed, "\\\\") || regexp.MustCompile(`[A-Za-z]:[\\/]`).MatchString(trimmed) { return true } return false }