824 lines
42 KiB
Go
824 lines
42 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
|
|
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
|
|
}
|