fix(19.3-06): refuse protected uploads into a public_url bucket (WR-07)

- attach.Store returns attach.ErrProtectedNeedsPrivateBucket for an
  is_public false upload while storage.uploads.public_url is set, before
  any blob is written
- OpenBucket's public_url line becomes a warning naming the refusal
This commit is contained in:
Jakub Zych
2026-10-15 22:30:00 +02:00
parent f705343904
commit caa5b5d8ff
5 changed files with 46 additions and 6 deletions

View File

@@ -35,6 +35,11 @@ var (
// ErrNotImage is returned by Store in image mode when the bytes are not
// an image IsAllowedImage accepts.
ErrNotImage = errors.New("attach: file is not an allowed image")
// ErrProtectedNeedsPrivateBucket is returned by Store for a protected
// (Upload.Public false) upload while storage.uploads.public_url is set:
// the host behind public_url serves every key of the bucket without the
// is_public check, so a protected file needs a private bucket.
ErrProtectedNeedsPrivateBucket = errors.New("attach: storage.uploads.public_url is set, so the bucket is served publicly and a protected (is_public false) upload needs a private bucket: unset storage.uploads.public_url or make the relation public")
)
// DefaultImageExtensions is the extension list of an image upload when
@@ -88,6 +93,10 @@ type Limits struct {
// Store saves an upload as an unattached system_files row.
//
// A protected upload (Upload.Public false) while storage.uploads.public_url
// is set returns ErrProtectedNeedsPrivateBucket before the body is read or
// any blob is written.
//
// It accepts the client extension, lower-cased, only when it matches
// [a-z0-9]{1,10} and is allowed by Limits (else ErrFileType). It reads up to
// 1 MiB ahead to sniff the content type from the bytes; in image mode those
@@ -123,6 +132,9 @@ func Store(ctx context.Context, db *gorm.DB, bucket *blob.Bucket, in Upload, lim
if lim.MaxBytes < 0 {
return nil, fmt.Errorf("attach: negative size limit %d", lim.MaxBytes)
}
if !in.Public && PublicURLBase() != "" {
return nil, ErrProtectedNeedsPrivateBucket
}
name := clientBaseName(in.FileName)
ext := strings.ToLower(strings.TrimPrefix(path.Ext(name), "."))
if !extPattern.MatchString(ext) || !slices.Contains(allowedExtensions(lim), ext) {