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 ) 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 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 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 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 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 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 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: return true default: return scumDataTargetSafeErrorPattern.MatchString(string(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 }