Skip to content

Commit b3c53fa

Browse files
committed
validation external file
1 parent 38913b5 commit b3c53fa

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

internal/file/file_manager_service.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package file
77

88
import (
99
"bufio"
10+
"bytes"
1011
"context"
1112
"encoding/json"
1213
"errors"
@@ -862,6 +863,7 @@ func (fms *FileManagerService) downloadExternalFile(ctx context.Context, fileAct
862863

863864
fileName := fileAction.File.GetFileMeta().GetName()
864865
fms.externalFileHeaders[fileName] = headers
866+
expectedType := determineExpectedContentType(fileName)
865867

866868
writeErr := fms.fileOperator.Write(
867869
ctx,
@@ -874,6 +876,16 @@ func (fms *FileManagerService) downloadExternalFile(ctx context.Context, fileAct
874876
return fmt.Errorf("failed to write downloaded content to temp file %s: %w", filePath, writeErr)
875877
}
876878

879+
validationErr := fms.validateFileContent(ctx, contentToWrite, fileName, expectedType)
880+
if validationErr != nil {
881+
slog.ErrorContext(ctx, "Security alert: Content validation failed, rejecting file download",
882+
"file", fileName,
883+
"path", filePath,
884+
"error", validationErr)
885+
886+
return validationErr
887+
}
888+
877889
return nil
878890
}
879891

@@ -1033,3 +1045,80 @@ func isMatchesWildcardDomain(hostname, pattern string) bool {
10331045

10341046
return false
10351047
}
1048+
1049+
const (
1050+
expectedTypeBinaryModule = "binary_module"
1051+
expectedTypeCertOrKey = "cert_or_key_file"
1052+
expectedTypeNginxConfig = "nginx_config_or_text"
1053+
expectedTypeUnknownText = "unknown_text_default"
1054+
)
1055+
1056+
//nolint:revive,cyclop // calculated cyclomatic is 13
1057+
func (fms *FileManagerService) validateFileContent(
1058+
ctx context.Context,
1059+
content []byte,
1060+
fileName string,
1061+
expectedType string,
1062+
) error {
1063+
contentType := http.DetectContentType(content)
1064+
slog.DebugContext(ctx, "Detected file content type", "file", fileName, "type",
1065+
contentType, "expected", expectedType)
1066+
1067+
if strings.Contains(contentType, "application/x-executable") ||
1068+
strings.Contains(contentType, "application/x-mach-binary") ||
1069+
strings.Contains(contentType, "application/x-elf") {
1070+
if expectedType != expectedTypeBinaryModule {
1071+
return fmt.Errorf("security violation: file is a binary executable (%s), but expected a safe file type",
1072+
contentType)
1073+
}
1074+
}
1075+
if expectedType != expectedTypeBinaryModule && strings.Contains(contentType, "application/octet-stream") {
1076+
return fmt.Errorf("security violation: file is an unrecognized binary (%s), but expected a text file",
1077+
contentType)
1078+
}
1079+
1080+
switch expectedType {
1081+
case expectedTypeCertOrKey:
1082+
if !strings.HasPrefix(contentType, "text/") {
1083+
return fmt.Errorf("expected certificate/key file (text), but detected non-text content (%s)", contentType)
1084+
}
1085+
1086+
if !bytes.Contains(content, []byte("-----BEGIN")) {
1087+
return errors.New("expected PEM file, but missing the required '-----BEGIN' header")
1088+
}
1089+
1090+
case expectedTypeNginxConfig:
1091+
if !strings.HasPrefix(contentType, "text/") {
1092+
return fmt.Errorf("expected NGINX configuration/text file, but detected non-text content (%s)",
1093+
contentType)
1094+
}
1095+
1096+
case expectedTypeBinaryModule:
1097+
if strings.HasPrefix(contentType, "text/") || strings.Contains(contentType, "application/octet-stream") {
1098+
return fmt.Errorf("expected binary module, but file is not a recognized executable format (%s)",
1099+
contentType)
1100+
}
1101+
1102+
case expectedTypeUnknownText:
1103+
if !strings.HasPrefix(contentType, "text/") {
1104+
return fmt.Errorf("expected general text file, but detected non-text content (%s)", contentType)
1105+
}
1106+
}
1107+
1108+
return nil
1109+
}
1110+
1111+
func determineExpectedContentType(fileName string) string {
1112+
ext := strings.ToLower(filepath.Ext(fileName))
1113+
1114+
switch ext {
1115+
case ".pem", ".crt", ".key":
1116+
return expectedTypeCertOrKey
1117+
case ".conf", ".cfg", ".txt", ".yaml", ".yml":
1118+
return expectedTypeNginxConfig
1119+
case ".so":
1120+
return expectedTypeBinaryModule
1121+
default:
1122+
return expectedTypeUnknownText
1123+
}
1124+
}

0 commit comments

Comments
 (0)