This page was machine-translated. Spotted a mistake?Help improve it.
Skip to content

Sicurezza e rafforzamento

SnapOtter elabora i file interamente sulla tua infrastruttura. Invia analytics di prodotto anonime e prive di contenuti e report di crash per impostazione predefinita, per aiutare a migliorare il progetto. Non invia mai i tuoi file, i nomi dei file, il contenuto dei file, l'output OCR, i metadati delle immagini o il testo dei documenti. Il feedback facoltativo viene inviato solo dopo che un utente lo ha inviato, solo quando le analytics sono abilitate, e i campi di contatto sono inclusi solo con esplicito consenso al contatto. Un amministratore può disattivare la raccolta di analytics e feedback con un solo clic in Impostazioni > Sistema > Privacy, senza bisogno di ricostruzione. L'elaborazione dei file resta sempre all'interno del tuo container.

Il container gira come utente non-root dedicato (snapotter) con tutte le capacità Linux rimosse tranne il set minimo richiesto. Per la policy completa di divulgazione delle vulnerabilità e l'architettura di sicurezza, vedi SECURITY.md su GitHub.

Rafforzamento del container

Il docker-compose.yml predefinito include il rafforzamento della sicurezza per la produzione. Ecco una descrizione di ciascuna opzione e del perché è importante:

yaml
services:
  SnapOtter:
    image: snapotter/snapotter:latest
    ports:
      # Bind to localhost only for internet-facing deployments:
      - "127.0.0.1:1349:1349"
    volumes:
      - SnapOtter-data:/data
      - SnapOtter-workspace:/tmp/workspace
    environment:
      - AUTH_ENABLED=true
      - DEFAULT_PASSWORD=change-me-immediately
      - RATE_LIMIT_PER_MIN=1000
      - DATABASE_URL=postgres://snapotter:snapotter@postgres:5432/snapotter
      - REDIS_URL=redis://redis:6379
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy

    # --- Resource limits ---
    mem_limit: 6g            # Prevents runaway memory from crashing the host
    memswap_limit: 6g        # No swap - fail fast instead of degrading the host
    cpus: 4                  # Cap CPU usage to 4 cores
    pids_limit: 512          # Prevents fork bombs

    # --- Capability restrictions ---
    cap_drop:
      - ALL                  # Drop ALL Linux capabilities first
    cap_add:
      - CHOWN                # Needed for volume permission setup
      - SETUID               # Needed for gosu privilege drop (root -> snapotter)
      - SETGID               # Needed for gosu privilege drop
      - DAC_OVERRIDE         # Needed for volume permission setup
      - FOWNER               # Needed for volume permission setup

    # --- Logging ---
    logging:
      driver: json-file
      options:
        max-size: "50m"      # Rotate logs at 50 MB
        max-file: "5"        # Keep 5 rotated log files

    # --- Health check ---
    healthcheck:
      test: ["CMD", "curl", "-sf", "--max-time", "5", "http://localhost:1349/api/v1/health"]
      interval: 30s
      timeout: 5s
      start_period: 60s
      retries: 3

    shm_size: "2gb"          # Required for Python ML shared memory
    restart: unless-stopped

  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_USER: snapotter
      POSTGRES_PASSWORD: snapotter
      POSTGRES_DB: snapotter
    volumes:
      - SnapOtter-pgdata:/var/lib/postgresql/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U snapotter"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 15s

  redis:
    image: redis:8-alpine
    command: ["redis-server", "--maxmemory-policy", "noeviction", "--appendonly", "yes"]
    volumes:
      - SnapOtter-redisdata:/data
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 12
      start_period: 10s

volumes:
  SnapOtter-data:
  SnapOtter-workspace:
  SnapOtter-pgdata:
  SnapOtter-redisdata:

Perché no-new-privileges non è impostato

security_opt: [no-new-privileges:true] è volutamente omesso. L'entrypoint parte come root per correggere la proprietà dei volumi, poi scende all'utente snapotter tramite gosu, che richiede setuid. Una volta completata la riduzione dei privilegi, il processo gira come snapotter con tutte le capacità rimosse tranne le cinque elencate sopra.

Se usi Kubernetes o il flag --user di Docker per eseguire direttamente come non-root (bypassando gosu), no-new-privileges può essere abilitato in sicurezza.

Perché read_only non è impostato

read_only: true non è impostato perché il rimappaggio PUID/PGID scrive su /etc/passwd e /etc/group all'avvio. Se usi il flag --user di Docker o runAsUser di Kubernetes invece di PUID/PGID, puoi abilitare in sicurezza un filesystem root di sola lettura.

Isolamento di rete

Durante il normale funzionamento, il container effettua zero connessioni di rete in uscita. Tutta l'elaborazione dei file avviene localmente usando librerie integrate.

Browser  -->  Reverse Proxy (TLS)  -->  SnapOtter container  -->  (nothing)

L'unica eccezione sono i download dei modelli AI: quando un utente installa un bundle di funzionalità AI tramite l'interfaccia, il container scarica l'archivio del bundle pre-costruito da Hugging Face, più alcuni singoli file di modelli da GitHub Releases, Google Storage e PyPI. Questi download avvengono una volta per bundle e sono memorizzati nel volume /data.

Raccomandazioni sul firewall:

ScenarioRegola in uscita
Air-gapped (senza AI)Blocca tutto il traffico in uscita dal container
Bundle AI necessariConsenti HTTPS verso huggingface.co, *.xethub.hf.co, cdn-lfs.huggingface.co, github.com, objects.githubusercontent.com, storage.googleapis.com, pypi.org, files.pythonhosted.org durante l'installazione, poi blocca
Dopo l'installazione AIBlocca tutto il traffico in uscita, i modelli sono memorizzati nella cache locale

Gli archivi dei bundle vengono serviti dall'archiviazione Xet di Hugging Face, che trasferisce in parallelo attraverso gli endpoint *.xethub.hf.co ed è ciò che rende veloci i download di bundle da diversi GB. Se il tuo firewall consente huggingface.co ma blocca *.xethub.hf.co, le installazioni riescono comunque ma ripiegano su un download a flusso singolo più lento, quindi metti in allowlist gli host Xet per restare sul percorso veloce. Le installazioni completamente offline possono saltare tutto questo e usare invece l'Importazione di bundle offline.

Per la configurazione del reverse proxy (Nginx, Traefik, Caddy, Cloudflare Tunnels), vedi la guida alla distribuzione.

Docker Secrets

Per le distribuzioni in produzione, evita di passare i segreti come variabili d'ambiente in chiaro. L'entrypoint supporta la convenzione _FILE di Docker: monta un segreto come file e imposta la corrispondente variabile _FILE sul suo percorso.

Segreti supportati:

VariabileEquivalente _FILE
DEFAULT_PASSWORDDEFAULT_PASSWORD_FILE
COOKIE_SECRETCOOKIE_SECRET_FILE
OIDC_CLIENT_SECRETOIDC_CLIENT_SECRET_FILE
S3_ACCESS_KEY_IDS3_ACCESS_KEY_ID_FILE
S3_SECRET_ACCESS_KEYS3_SECRET_ACCESS_KEY_FILE
SNAPOTTER_LICENSE_KEYSNAPOTTER_LICENSE_KEY_FILE

Esempio con i secrets di Docker Compose:

yaml
services:
  SnapOtter:
    image: snapotter/snapotter:latest
    environment:
      - AUTH_ENABLED=true
      - DEFAULT_USERNAME=admin
      - DEFAULT_PASSWORD_FILE=/run/secrets/snapotter_password
      - COOKIE_SECRET_FILE=/run/secrets/cookie_secret
    secrets:
      - snapotter_password
      - cookie_secret

secrets:
  snapotter_password:
    file: ./secrets/snapotter_password.txt
  cookie_secret:
    file: ./secrets/cookie_secret.txt

TIP

I secrets di Docker Compose (senza Swarm) richiedono Compose v2.23 o successivo.

Distribuzione Kubernetes

L'entrypoint rileva quando il container è già in esecuzione come non-root (ad es. tramite runAsUser di Kubernetes) e salta automaticamente la riduzione dei privilegi gosu. In quel caso non può fare il chown dei volumi montati da solo, quindi verifica che siano scrivibili ed esce subito con indicazioni pratiche se non lo sono, vedi Permessi di archiviazione per fsGroup e le configurazioni con UID estraneo (TrueNAS, OpenShift).

SecurityContext del pod consigliato:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: snapotter
spec:
  replicas: 1
  selector:
    matchLabels:
      app: snapotter
  template:
    metadata:
      labels:
        app: snapotter
    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 999
        runAsGroup: 999
        fsGroup: 999
      containers:
        - name: snapotter
          image: snapotter/snapotter:latest
          ports:
            - containerPort: 1349
          securityContext:
            allowPrivilegeEscalation: false
            capabilities:
              drop: [ALL]
          resources:
            requests:
              cpu: "1"
              memory: 2Gi
            limits:
              cpu: "4"
              memory: 6Gi
          livenessProbe:
            httpGet:
              path: /api/v1/health
              port: 1349
            initialDelaySeconds: 60
            periodSeconds: 30
            timeoutSeconds: 5
          readinessProbe:
            httpGet:
              path: /api/v1/health
              port: 1349
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 5
          volumeMounts:
            - name: data
              mountPath: /data
            - name: workspace
              mountPath: /tmp/workspace
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: snapotter-data
        - name: workspace
          emptyDir:
            medium: Memory
            sizeLimit: 2Gi

Poiché runAsUser: 999 è impostato a livello di pod, l'entrypoint salta del tutto gosu. Questo consente le capacità allowPrivilegeEscalation: false e drop: [ALL] senza conflitti.

Per il dimensionamento delle risorse, vedi Requisiti hardware.

Backup e ripristino

Lo stato persistente è suddiviso su due volumi:

VolumeContenutiCritico?
SnapOtter-pgdataDatabase PostgreSQL (utenti, impostazioni, pipeline, job, log di audit)
/data (volume app)File caricati dagli utenti, modelli AI, venv PythonParzialmente (vedi sotto)

All'interno del volume /data:

PercorsoContenutiCritico?
/data/uploads/, /data/outputs/File utente e risultati di elaborazione
/data/ai/File di modelli AI scaricatiNo (riscaricabili)
/data/venv/Ambiente virtuale PythonNo (ricostruito all'avvio)

Backup del database

Usa pg_dump per eseguire il backup del database mentre lo stack è in esecuzione:

bash
# Dump the database
docker exec SnapOtter-postgres pg_dump -U snapotter snapotter > backup.sql

# Restore into a fresh database
cat backup.sql | docker exec -i SnapOtter-postgres psql -U snapotter snapotter

In alternativa, ferma lo stack e crea uno snapshot del volume SnapOtter-pgdata:

bash
docker compose down
docker run --rm -v SnapOtter-pgdata:/data -v $(pwd)/backup:/backup \
  alpine tar czf /backup/snapotter-pgdata.tar.gz -C /data .

Backup dei file utente

bash
# Snapshot the app data volume (excluding re-downloadable AI models)
docker run --rm -v SnapOtter-data:/data -v $(pwd)/backup:/backup \
  alpine tar czf /backup/snapotter-files.tar.gz \
    --exclude='ai' --exclude='venv' -C /data .

I modelli AI ammontano fino a circa 24 GB su tutti i bundle. Poiché sono riscaricabili, escludi /data/ai/ e /data/venv/ dai backup per risparmiare spazio. Solo il database e i file utente sono critici.

Artefatti di conformità

Ogni release di SnapOtter include i seguenti artefatti di sicurezza:

ArtefattoFormatoDove trovarlo
SBOM (CycloneDX)JSONAsset della release GitHub: snapotter-v{version}-sbom.cdx.json
SBOM (SPDX)JSONAsset della release GitHub: snapotter-v{version}-sbom.spdx.json
Scansione delle vulnerabilitàTrivy JSONAsset della release GitHub: snapotter-v{version}-trivy.json
Scansione delle vulnerabilitàSARIFScheda GitHub Security
Analisi staticaCodeQL (JS/TS + Python)Scheda GitHub Security, eseguita settimanalmente + per PR
Revisione delle dipendenzeNativa di GitHubControllo per PR, fallisce sulle aggiunte ad alta gravità
Audit delle dipendenze Pythonpip-auditLog di esecuzione CI a ogni push
Policy di sicurezzaMarkdownSECURITY.md nel repository
Aggiornamenti delle dipendenzeDependabotPR settimanali automatiche per npm, pip, Docker, Actions

Eseguire la tua scansione:

Scarica l'SBOM dalla release e scansionalo con lo strumento che preferisci:

bash
# Scan with Grype using the CycloneDX SBOM
grype sbom:snapotter-v1.17.2-sbom.cdx.json

# Scan with Trivy using the SPDX SBOM
trivy sbom snapotter-v1.17.2-sbom.spdx.json

# Scan the Docker image directly
trivy image snapotter/snapotter:1.17.2

INFO

L'SBOM e la scansione delle vulnerabilità riflettono l'immagine esatta pubblicata per quella release. I bundle di modelli AI installati dopo la distribuzione non sono inclusi nell'SBOM poiché vengono scaricati a runtime.