Skip to content

File uploads missing Content-Type headers across all storage providers (S3, GCP, Supabase) #214

@hamchowderr

Description

@hamchowderr

Description

The NCAT API uploads files to cloud storage without specifying Content-Type headers across
all storage providers (S3-compatible and Google Cloud Storage). This causes upload failures
with Supabase Storage and prevents inline media viewing in browsers.

Environment

  • NCAT Version: stephengpope/no-code-architects-toolkit:latest
  • Build Number: 206
  • Python Version: 3.9.23
  • Deployment: Docker
  • Tested Storage Backends:
    • Supabase Storage (S3-compatible) ❌ Fails with InvalidMimeType
    • AWS S3 ⚠️ Defaults to application/octet-stream
    • DigitalOcean Spaces ⚠️ Defaults to application/octet-stream
    • Google Cloud Storage ⚠️ Auto-detects (unreliable)

Problem Details

Error from production logs:
ERROR:services.s3_toolkit:Error uploading file to S3: An error occurred (InvalidMimeType)
when calling the PutObject operation: mime type application/octet-stream is not supported

Failure Statistics (Oct 15-21, 2025):

  • 42 unique job failures over 7 days
  • 41 failures today alone (accelerating issue)
  • Affected endpoints:
    • /v1/image/convert/video - 41 failures (98%)
    • /v1/media/download - 1 failure (2%)

User Impact:

  1. ❌ Supabase Storage rejects uploads entirely
  2. ❌ MP4 videos download instead of playing in browser
  3. ❌ MP3 audio files download instead of streaming
  4. ❌ Images download instead of displaying inline

Root Cause

1. S3-Compatible Provider (services/s3_toolkit.py)

def upload_to_s3(file_path, s3_url, access_key, secret_key, bucket_name, region):
    # ...
    client.upload_fileobj(data, bucket_name,
        os.path.basename(file_path),
        ExtraArgs={'ACL': 'public-read'})  # ❌ No ContentType

Problem: boto3 defaults to application/octet-stream when ContentType is not specified.

2. Google Cloud Storage Provider (services/gcp_toolkit.py)

def upload_to_gcs(file_path, bucket_name=GCP_BUCKET_NAME):
    # ...
    blob = bucket.blob(os.path.basename(file_path))
    blob.upload_from_filename(file_path)  # ❌ No content_type parameter

Problem: GCP auto-detects MIME type, but this is unreliable.

Proposed Solution

Use Python's built-in mimetypes library to auto-detect Content-Type from file extensions:

Fix for services/s3_toolkit.py

import mimetypes

def upload_to_s3(file_path, s3_url, access_key, secret_key, bucket_name, region):
    session = boto3.Session(
        aws_access_key_id=access_key,
        aws_secret_access_key=secret_key,
        region_name=region
    )

    client = session.client('s3', endpoint_url=s3_url)

    try:
        # Auto-detect MIME type from file extension
        content_type, _ = mimetypes.guess_type(file_path)
        if not content_type:
            content_type = 'application/octet-stream'

        logger.info(f"Uploading {os.path.basename(file_path)} with Content-Type:
{content_type}")

        with open(file_path, 'rb') as data:
            client.upload_fileobj(data, bucket_name,
                os.path.basename(file_path),
                ExtraArgs={
                    'ACL': 'public-read',
                    'ContentType': content_type  # ✅ Add this
                })

        encoded_filename = quote(os.path.basename(file_path))
        file_url = f"{s3_url}/{bucket_name}/{encoded_filename}"
        return file_url
    except Exception as e:
        logger.error(f"Error uploading file to S3: {e}")
        raise

Fix for services/gcp_toolkit.py

import mimetypes

def upload_to_gcs(file_path, bucket_name=GCP_BUCKET_NAME):
    # ... existing validation ...

    # Auto-detect MIME type from file extension
    content_type, _ = mimetypes.guess_type(file_path)
    if not content_type:
        content_type = 'application/octet-stream'

    logger.info(f"Uploading {os.path.basename(file_path)} with Content-Type: {content_type}")

    bucket = gcs_client.bucket(bucket_name)
    blob = bucket.blob(os.path.basename(file_path))
    blob.upload_from_filename(file_path, content_type=content_type)  # ✅ Add content_type

    # ... rest of function ...

BenefitsEliminates InvalidMimeType errors with Supabase StorageEnables inline viewing of media
files in browsersConsistent behavior across all storage backendsZero breaking changes -
fully backwards compatibleNo new dependencies - uses Python standard library

Additional Notes

I'm happy to submit a PR with both fixes if this approach is acceptable. This issue affects all
 users regardless of which storage backend they choose.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions