Files
flaskpaste/documentation/api.md
Username 202e927918 add content-hash dedup for abuse prevention
Throttle repeated submissions of identical content using SHA256 hash
tracking. Configurable via FLASKPASTE_DEDUP_WINDOW and FLASKPASTE_DEDUP_MAX.
2025-12-20 03:31:20 +01:00

7.5 KiB

FlaskPaste API Reference

Overview

FlaskPaste provides a RESTful API for creating, retrieving, and deleting text and binary pastes.

Base URL: http://your-server:5000/

Content Types:

  • Requests: application/json, text/plain, application/octet-stream, or any binary type
  • Responses: application/json for metadata, original MIME type for raw content

Authentication

Authentication is optional and uses client certificate fingerprints passed via the X-SSL-Client-SHA1 header.

X-SSL-Client-SHA1: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

The fingerprint must be exactly 40 lowercase hexadecimal characters (SHA1).

Benefits of authentication:

  • Larger upload limit (50 MiB vs 3 MiB)
  • Ability to delete owned pastes

Endpoints

GET /health

Health check endpoint for load balancers and monitoring.

Request:

GET /health HTTP/1.1
Host: localhost:5000

Response (200 OK):

{
  "status": "healthy",
  "database": "ok"
}

Response (503 Service Unavailable):

{
  "status": "unhealthy",
  "database": "error"
}

GET /

Returns API information and usage instructions.

Request:

GET / HTTP/1.1
Host: localhost:5000

Response:

{
  "name": "FlaskPaste",
  "version": "1.0.0",
  "endpoints": {
    "GET /": "API information",
    "GET /health": "Health check",
    "POST /": "Create paste",
    "GET /<id>": "Retrieve paste metadata",
    "GET /<id>/raw": "Retrieve raw paste content",
    "DELETE /<id>": "Delete paste"
  },
  "usage": {
    "raw": "curl --data-binary @file.txt http://host/",
    "pipe": "cat file.txt | curl --data-binary @- http://host/",
    "json": "curl -H 'Content-Type: application/json' -d '{\"content\":\"...\"}' http://host/"
  },
  "note": "Use --data-binary (not -d) to preserve newlines"
}

POST /

Create a new paste.

Request (Raw Binary):

POST / HTTP/1.1
Host: localhost:5000
Content-Type: application/octet-stream

<binary content>

Request (JSON):

POST / HTTP/1.1
Host: localhost:5000
Content-Type: application/json

{"content": "Hello, World!"}

Response (201 Created):

{
  "id": "abc12345",
  "url": "/abc12345",
  "raw": "/abc12345/raw",
  "mime_type": "text/plain",
  "created_at": 1700000000,
  "owner": "a1b2c3..."  // Only present if authenticated
}

Errors:

Code Description
400 No content provided
413 Paste too large
429 Duplicate content rate limit exceeded

Size Limits:

  • Anonymous: 3 MiB (configurable via FLASKPASTE_MAX_ANON)
  • Authenticated: 50 MiB (configurable via FLASKPASTE_MAX_AUTH)

GET /{id}

Retrieve paste metadata.

Request:

GET /abc12345 HTTP/1.1
Host: localhost:5000

Response (200 OK):

{
  "id": "abc12345",
  "mime_type": "text/plain",
  "size": 1234,
  "created_at": 1700000000,
  "raw": "/abc12345/raw"
}

Errors:

Code Description
400 Invalid paste ID format
404 Paste not found

GET /{id}/raw

Retrieve raw paste content with correct MIME type.

Request:

GET /abc12345/raw HTTP/1.1
Host: localhost:5000

Response (200 OK):

HTTP/1.1 200 OK
Content-Type: image/png
Content-Disposition: inline

<binary content>
  • Content-Disposition: inline is set for image/* and text/* types
  • Content-Type matches the detected/stored MIME type

Errors:

Code Description
400 Invalid paste ID format
404 Paste not found

DELETE /{id}

Delete a paste. Requires authentication and ownership.

Request:

DELETE /abc12345 HTTP/1.1
Host: localhost:5000
X-SSL-Client-SHA1: a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Response (200 OK):

{
  "message": "Paste deleted"
}

Errors:

Code Description
400 Invalid paste ID format
401 Authentication required
403 Permission denied (not owner)
404 Paste not found

MIME Type Detection

FlaskPaste automatically detects MIME types using:

  1. Magic byte signatures (highest priority)

    • PNG: \x89PNG\r\n\x1a\n
    • JPEG: \xff\xd8\xff
    • GIF: GIF87a or GIF89a
    • WebP: RIFF....WEBP
    • ZIP: PK\x03\x04
    • PDF: %PDF
    • GZIP: \x1f\x8b
  2. Explicit Content-Type header (if not generic)

  3. UTF-8 detection (falls back to text/plain)

  4. Binary fallback (application/octet-stream)


Paste Expiry

Pastes expire based on last access time (default: 5 days).

  • Every GET /{id} or GET /{id}/raw updates the last access timestamp
  • Cleanup runs automatically (hourly, throttled)
  • Configurable via FLASKPASTE_EXPIRY environment variable

Abuse Prevention

FlaskPaste includes content-hash based deduplication to prevent spam and abuse.

How it works:

  • Each paste's SHA256 content hash is tracked
  • Repeated submissions of identical content are throttled
  • After exceeding the threshold, further duplicates are rejected with 429

Default limits:

  • Window: 1 hour (FLASKPASTE_DEDUP_WINDOW)
  • Maximum: 3 identical submissions per window (FLASKPASTE_DEDUP_MAX)

Response (429 Too Many Requests):

{
  "error": "Duplicate content rate limit exceeded",
  "count": 3,
  "window_seconds": 3600
}

Configuration:

export FLASKPASTE_DEDUP_WINDOW=3600  # Window in seconds (default: 1 hour)
export FLASKPASTE_DEDUP_MAX=3        # Max duplicates per window (default: 3)

Notes:

  • Different content is not affected by other content's limits
  • Counter resets after the window expires
  • Hash records are cleaned up periodically

Error Response Format

All errors return JSON:

{
  "error": "Description of the error"
}

For size limit errors (413):

{
  "error": "Paste too large",
  "size": 5000000,
  "max_size": 3145728,
  "authenticated": false
}

Security Headers

All responses include the following security headers:

Header Value
X-Content-Type-Options nosniff
X-Frame-Options DENY
X-XSS-Protection 1; mode=block
Referrer-Policy strict-origin-when-cross-origin
Content-Security-Policy default-src 'none'; frame-ancestors 'none'
Permissions-Policy geolocation=(), microphone=(), camera=()
Strict-Transport-Security max-age=31536000; includeSubDomains
Cache-Control no-store, no-cache, must-revalidate, private
Pragma no-cache

Request Tracing

All requests include an X-Request-ID header for log correlation:

  • If the client provides X-Request-ID, it is passed through
  • If not provided, a UUID is generated
  • The ID is echoed back in the response
  • All log entries include [rid=<request-id>]

Example:

# Client-provided ID
curl -H "X-Request-ID: my-trace-123" https://paste.example.com/health

# Response includes:
# X-Request-ID: my-trace-123

Proxy Trust Validation

When FLASKPASTE_PROXY_SECRET is configured, the application validates that requests come from a trusted reverse proxy by checking the X-Proxy-Secret header.

This provides defense-in-depth against header spoofing if an attacker bypasses the reverse proxy.

Configuration:

export FLASKPASTE_PROXY_SECRET="your-secret-value"

Proxy Configuration (HAProxy):

http-request set-header X-Proxy-Secret your-secret-value

If the secret doesn't match, authentication headers (X-SSL-Client-SHA1) are ignored and the request is treated as anonymous.