Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
ea14280c
Commit
ea14280c
authored
Dec 17, 2025
by
Johnny
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: enhance attachment handling with MIME type validation
parent
642271a8
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
59 additions
and
4 deletions
+59
-4
attachment_service.go
server/router/api/v1/attachment_service.go
+15
-0
fileserver.go
server/router/fileserver/fileserver.go
+44
-4
No files found.
server/router/api/v1/attachment_service.go
View file @
ea14280c
...
...
@@ -64,6 +64,9 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
if
request
.
Attachment
.
Type
==
""
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"type is required"
)
}
if
!
isValidMimeType
(
request
.
Attachment
.
Type
)
{
return
nil
,
status
.
Errorf
(
codes
.
InvalidArgument
,
"invalid MIME type format"
)
}
// Use provided attachment_id or generate a new one
attachmentUID
:=
request
.
AttachmentId
...
...
@@ -457,3 +460,15 @@ func validateFilename(filename string) bool {
return
true
}
func
isValidMimeType
(
mimeType
string
)
bool
{
// Reject empty or excessively long MIME types
if
mimeType
==
""
||
len
(
mimeType
)
>
255
{
return
false
}
// MIME type must match the pattern: type/subtype
// Allow common characters in MIME types per RFC 2045
matched
,
_
:=
regexp
.
MatchString
(
`^[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]{0,126}/[a-zA-Z0-9][a-zA-Z0-9!#$&^_.+-]{0,126}$`
,
mimeType
)
return
matched
}
server/router/fileserver/fileserver.go
View file @
ea14280c
...
...
@@ -118,15 +118,39 @@ func (s *FileServerService) serveAttachmentFile(c echo.Context) error {
contentType
+=
"; charset=utf-8"
}
// Prevent XSS attacks by serving potentially unsafe files as octet-stream
if
strings
.
EqualFold
(
contentType
,
"image/svg+xml"
)
||
strings
.
EqualFold
(
contentType
,
"text/html"
)
||
strings
.
EqualFold
(
contentType
,
"application/xhtml+xml"
)
{
contentType
=
"application/octet-stream"
unsafeTypes
:=
[]
string
{
"text/html"
,
"text/javascript"
,
"application/javascript"
,
"application/x-javascript"
,
"text/xml"
,
"application/xml"
,
"application/xhtml+xml"
,
"image/svg+xml"
,
}
for
_
,
unsafeType
:=
range
unsafeTypes
{
if
strings
.
EqualFold
(
contentType
,
unsafeType
)
{
contentType
=
"application/octet-stream"
break
}
}
// Set common headers
c
.
Response
()
.
Header
()
.
Set
(
"Content-Type"
,
contentType
)
c
.
Response
()
.
Header
()
.
Set
(
"Cache-Control"
,
"public, max-age=3600"
)
// Prevent MIME-type sniffing which could lead to XSS
c
.
Response
()
.
Header
()
.
Set
(
"X-Content-Type-Options"
,
"nosniff"
)
// Defense-in-depth: prevent embedding in frames and restrict content loading
c
.
Response
()
.
Header
()
.
Set
(
"X-Frame-Options"
,
"DENY"
)
c
.
Response
()
.
Header
()
.
Set
(
"Content-Security-Policy"
,
"default-src 'none'; style-src 'unsafe-inline';"
)
// Force download for non-media files to prevent XSS execution
if
!
strings
.
HasPrefix
(
contentType
,
"image/"
)
&&
!
strings
.
HasPrefix
(
contentType
,
"video/"
)
&&
!
strings
.
HasPrefix
(
contentType
,
"audio/"
)
&&
contentType
!=
"application/pdf"
{
c
.
Response
()
.
Header
()
.
Set
(
"Content-Disposition"
,
fmt
.
Sprintf
(
"attachment; filename=%q"
,
attachment
.
Filename
))
}
// For video/audio: Use http.ServeContent for automatic range request support
// This is critical for Safari which REQUIRES range request support
...
...
@@ -169,6 +193,18 @@ func (s *FileServerService) serveUserAvatar(c echo.Context) error {
return
echo
.
NewHTTPError
(
http
.
StatusInternalServerError
,
"failed to extract image info"
)
.
SetInternal
(
err
)
}
// Validate avatar MIME type to prevent XSS
allowedAvatarTypes
:=
map
[
string
]
bool
{
"image/png"
:
true
,
"image/jpeg"
:
true
,
"image/jpg"
:
true
,
"image/gif"
:
true
,
"image/webp"
:
true
,
}
if
!
allowedAvatarTypes
[
imageType
]
{
return
echo
.
NewHTTPError
(
http
.
StatusBadRequest
,
"invalid avatar image type"
)
}
// Decode base64 data
imageData
,
err
:=
base64
.
StdEncoding
.
DecodeString
(
base64Data
)
if
err
!=
nil
{
...
...
@@ -178,6 +214,10 @@ func (s *FileServerService) serveUserAvatar(c echo.Context) error {
// Set cache headers for avatars
c
.
Response
()
.
Header
()
.
Set
(
"Content-Type"
,
imageType
)
c
.
Response
()
.
Header
()
.
Set
(
"Cache-Control"
,
"public, max-age=3600"
)
c
.
Response
()
.
Header
()
.
Set
(
"X-Content-Type-Options"
,
"nosniff"
)
// Defense-in-depth: prevent embedding in frames
c
.
Response
()
.
Header
()
.
Set
(
"X-Frame-Options"
,
"DENY"
)
c
.
Response
()
.
Header
()
.
Set
(
"Content-Security-Policy"
,
"default-src 'none'; style-src 'unsafe-inline';"
)
return
c
.
Blob
(
http
.
StatusOK
,
imageType
,
imageData
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment