Files
browser/platform/validator/scum_live_data.go
T

659 lines
32 KiB
Go

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
}