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

Seguridad y fortalecimiento

SnapOtter procesa los archivos íntegramente en tu infraestructura. Envía por defecto analítica de producto e informes de fallos anónimos y sin contenido para ayudar a mejorar el proyecto. Nunca envía tus archivos, nombres de archivo, contenido de archivos, salida de OCR, metadatos de imagen ni texto de documentos. Los comentarios opcionales se envían solo después de que un usuario los envíe, solo cuando la analítica está habilitada, y los campos de contacto se incluyen solo con consentimiento de contacto explícito. Un administrador puede desactivar la captura de analítica y comentarios con un solo clic en Ajustes > Sistema > Privacidad, sin necesidad de recompilar. El procesamiento de archivos siempre permanece dentro de tu contenedor.

El contenedor se ejecuta como un usuario dedicado sin root (snapotter) con todas las capacidades de Linux eliminadas excepto el conjunto mínimo requerido. Para la política completa de divulgación de vulnerabilidades y la arquitectura de seguridad, consulta SECURITY.md en GitHub.

Fortalecimiento del contenedor

El docker-compose.yml por defecto incluye fortalecimiento de seguridad para producción. Aquí tienes un desglose de cada opción y por qué importa:

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:

Por qué no se establece no-new-privileges

security_opt: [no-new-privileges:true] se omite intencionadamente. El punto de entrada arranca como root para corregir la propiedad de los volúmenes, luego cae al usuario snapotter mediante gosu, que requiere setuid. Una vez que se completa la reducción de privilegios, el proceso se ejecuta como snapotter con todas las capacidades excepto las cinco enumeradas arriba eliminadas.

Si usas Kubernetes o el indicador --user de Docker para ejecutar directamente sin root (evitando gosu), es seguro habilitar no-new-privileges.

Por qué no se establece read_only

read_only: true no se establece porque la reasignación de PUID/PGID escribe en /etc/passwd y /etc/group al arrancar. Si usas el indicador --user de Docker o runAsUser de Kubernetes en lugar de PUID/PGID, puedes habilitar de forma segura un sistema de archivos raíz de solo lectura.

Aislamiento de red

Durante el funcionamiento normal, el contenedor realiza cero conexiones de red salientes. Todo el procesamiento de archivos ocurre localmente usando bibliotecas empaquetadas.

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

La única excepción son las descargas de modelos de IA: cuando un usuario instala un paquete de funciones de IA a través de la interfaz, el contenedor descarga el archivo del paquete precompilado desde Hugging Face, más unos pocos archivos de modelo individuales desde GitHub Releases, Google Storage y PyPI. Estas descargas ocurren una vez por paquete y se almacenan en el volumen /data.

Recomendaciones de firewall:

EscenarioRegla saliente
Aislado de red (sin IA)Bloquea todo el tráfico saliente del contenedor
Se necesitan paquetes de IAPermite HTTPS a huggingface.co, *.xethub.hf.co, cdn-lfs.huggingface.co, github.com, objects.githubusercontent.com, storage.googleapis.com, pypi.org, files.pythonhosted.org durante la instalación, luego bloquea
Tras la instalación de IABloquea todo el tráfico saliente, los modelos se almacenan en caché localmente

Los archivos de paquete se sirven desde el almacenamiento Xet de Hugging Face, que transfiere a través de los endpoints *.xethub.hf.co en paralelo y es lo que hace rápidas las descargas de paquetes de varios GB. Si tu firewall permite huggingface.co pero bloquea *.xethub.hf.co, las instalaciones aún tienen éxito pero recurren a una descarga más lenta de un solo flujo, así que incluye los hosts de Xet en la lista de permitidos para mantenerte en la vía rápida. Las instalaciones completamente sin conexión pueden saltarse todo esto y usar la Importación de paquetes sin conexión en su lugar.

Para la configuración del proxy inverso (Nginx, Traefik, Caddy, túneles de Cloudflare), consulta la guía de Despliegue.

Secretos de Docker

Para los despliegues de producción, evita pasar secretos como variables de entorno en texto plano. El punto de entrada admite la convención _FILE de Docker: monta un secreto como archivo y establece la variable _FILE correspondiente a su ruta.

Secretos admitidos:

VariableEquivalente _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

Ejemplo con secretos de 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

Los secretos de Docker Compose (sin Swarm) requieren Compose v2.23 o posterior.

Despliegue en Kubernetes

El punto de entrada detecta cuándo el contenedor ya se está ejecutando sin root (p. ej., mediante runAsUser de Kubernetes) y omite la reducción de privilegios de gosu automáticamente. En ese caso no puede hacer chown de los volúmenes montados por sí mismo, así que verifica que sean escribibles y sale pronto con orientación práctica si no lo son; consulta Permisos de almacenamiento para configuraciones de fsGroup y de UID ajeno (TrueNAS, OpenShift).

SecurityContext de pod recomendado:

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

Como runAsUser: 999 se establece a nivel de pod, el punto de entrada omite gosu por completo. Esto permite las capacidades allowPrivilegeEscalation: false y drop: [ALL] sin conflicto.

Para el dimensionamiento de recursos, consulta Requisitos de hardware.

Copia de seguridad y recuperación

El estado persistente se divide entre dos volúmenes:

VolumenContenido¿Crítico?
SnapOtter-pgdataBase de datos PostgreSQL (usuarios, ajustes, canalizaciones, trabajos, registro de auditoría)
/data (volumen de la app)Archivos subidos por usuarios, modelos de IA, venv de PythonParcialmente (ver abajo)

Dentro del volumen /data:

RutaContenido¿Crítico?
/data/uploads/, /data/outputs/Archivos de usuario y resultados de procesamiento
/data/ai/Archivos de modelo de IA descargadosNo (redescargables)
/data/venv/Entorno virtual de PythonNo (se reconstruye al iniciar)

Copia de seguridad de la base de datos

Usa pg_dump para hacer una copia de seguridad de la base de datos mientras la pila está en ejecución:

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

Como alternativa, detén la pila y haz una instantánea del volumen 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 .

Copia de seguridad de los archivos de usuario

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 .

Los modelos de IA suman hasta unos 24 GB en total entre todos los paquetes. Como son redescargables, excluye /data/ai/ y /data/venv/ de las copias de seguridad para ahorrar espacio. Solo la base de datos y los archivos de usuario son críticos.

Artefactos de cumplimiento

Cada versión de SnapOtter incluye los siguientes artefactos de seguridad:

ArtefactoFormatoDónde encontrarlo
SBOM (CycloneDX)JSONRecurso de la versión de GitHub: snapotter-v{version}-sbom.cdx.json
SBOM (SPDX)JSONRecurso de la versión de GitHub: snapotter-v{version}-sbom.spdx.json
Escaneo de vulnerabilidadesTrivy JSONRecurso de la versión de GitHub: snapotter-v{version}-trivy.json
Escaneo de vulnerabilidadesSARIFPestaña GitHub Security
Análisis estáticoCodeQL (JS/TS + Python)Pestaña GitHub Security, se ejecuta semanalmente + por PR
Revisión de dependenciasNativo de GitHubComprobación por PR, falla ante adiciones de alta gravedad
Auditoría de dependencias de Pythonpip-auditRegistro de ejecución de CI en cada push
Política de seguridadMarkdownSECURITY.md en el repositorio
Actualizaciones de dependenciasDependabotPRs semanales automatizados para npm, pip, Docker, Actions

Ejecutar tu propio escaneo:

Descarga el SBOM de la versión y escanéalo con la herramienta que prefieras:

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

El SBOM y el escaneo de vulnerabilidades reflejan la imagen exacta publicada para esa versión. Los paquetes de modelos de IA instalados tras el despliegue no se incluyen en el SBOM, ya que se descargan en tiempo de ejecución.