Skip to content

feat: Platform file storage service with secure one-time download links (FILES-001) #295

Description

@vybe

Summary

Add a platform-level file storage service that agents can upload files to and receive secure one-time download URLs. This enables agents to share files with users via Slack, email, or any channel without requiring direct container access.

Motivation

Agents generate files (reports, images, exports, code artifacts) but have no way to share them externally. Current workarounds (base64 in chat, Slack file upload) are fragile and channel-specific. A platform file storage service provides a universal mechanism: upload a file, get a URL, share it anywhere.

Design

Architecture

Agent → Upload file via MCP tool → Platform storage → Returns one-time URL
User clicks URL → File downloaded → Link expires/invalidated

Storage

  • Platform-level storage at /data/files/ (bind mount, survives restarts)
  • Files stored with UUID filenames (no path traversal risk)
  • Metadata in SQLite: original filename, uploader agent, content type, size, expiration, download status

MCP Tool

upload_file(
  file_path: string,        # Path in agent container to upload
  filename: string,          # Display filename for download
  content_type?: string,     # MIME type (auto-detected if omitted)
  expires_in?: number,       # Seconds until link expires (default: 24h)
  one_time?: boolean         # Invalidate after first download (default: true)
) → { url: string, file_id: string, expires_at: string }

REST Endpoints

Method Path Auth Description
POST /api/files/upload JWT/MCP Upload file (multipart or from agent path)
GET /api/files/{file_id}/{token} None (token) Download file (public, one-time)
GET /api/files JWT List uploaded files (admin/owner)
DELETE /api/files/{file_id} JWT Delete file

Download URL Format

https://{domain}/api/files/{file_id}/{download_token}
  • download_token = HMAC-SHA256(file_id + expiration, secret)
  • No authentication required — the token IS the auth
  • One-time: marked as downloaded after first successful GET
  • Expired files return 410 Gone

Database

CREATE TABLE platform_files (
    id TEXT PRIMARY KEY,
    agent_name TEXT NOT NULL,
    original_filename TEXT NOT NULL,
    stored_filename TEXT NOT NULL,      -- UUID on disk
    content_type TEXT,
    size_bytes INTEGER,
    download_token TEXT NOT NULL,
    one_time INTEGER DEFAULT 1,
    downloaded INTEGER DEFAULT 0,
    downloaded_at TEXT,
    expires_at TEXT,
    created_at TEXT NOT NULL,
    created_by TEXT                     -- user or agent
);

Cleanup

  • Background task purges expired files (both DB record and disk file)
  • Configurable max storage size per agent and platform-wide
  • Files older than 7 days auto-expire regardless of download status

Security

  • HMAC token prevents URL guessing
  • One-time download prevents link sharing/reuse
  • File size limit (default: 100MB per file)
  • Content-type validation (block executables by default)
  • Files stored outside web root, served via streaming endpoint
  • No directory listing or traversal possible

Acceptance Criteria

  • Agents can upload files via MCP tool and receive a download URL
  • One-time download links work without authentication
  • Links expire after configured duration
  • Downloaded/expired files return appropriate HTTP status
  • Cleanup task removes expired files from disk and DB
  • File list visible in UI for admins/owners
  • File size limits enforced
  • MCP tool available for agent-to-platform file upload

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions