# Cipher · Barcode Studio A self-contained, browser-based barcode generator that converts text or numeric sequences into 22 different barcode symbologies and exports them as downloadable JPG (or PNG) images. Designed to run as a single hardened Docker container with no backend, no database, and no telemetry — everything happens in the user's browser. ![status](https://img.shields.io/badge/status-stable-success) ![docker](https://img.shields.io/badge/docker-compose-blue) ![license](https://img.shields.io/badge/license-MIT-lightgrey) --- ## Table of Contents 1. [Features](#features) 2. [Supported Barcode Formats](#supported-barcode-formats) 3. [Architecture](#architecture) 4. [Requirements](#requirements) 5. [Quick Start](#quick-start) 6. [Configuration](#configuration) 7. [Project Structure](#project-structure) 8. [Security](#security) 9. [Dependency Versions & Vulnerability Status](#dependency-versions--vulnerability-status) 10. [Operations](#operations) 11. [Updating & Maintenance](#updating--maintenance) 12. [Troubleshooting](#troubleshooting) 13. [Development](#development) 14. [License](#license) --- ## Features * **22 barcode symbologies** spanning retail/GS1, linear/industrial, pharma, postal and 2D matrix codes. * **Live example preview** — every symbology shows a sample render the moment it's selected. * **Batch generation** — paste lines into the input area and each line becomes its own barcode card. * **Per-card text toggle** — show or hide the human-readable caption beneath each barcode, then download it in that state. * **JPG and PNG export** — individual downloads per barcode plus a one-click "Download All as JPG" for the whole batch. * **Adjustable scale and JPG quality** for print-ready output. * **Fully offline-capable** once loaded — every library is vendored locally, no third-party CDN at runtime (web fonts are the only external request and are optional). * **No backend** — runs entirely as static assets served by nginx. No database, no API, no user data leaves the browser. --- ## Supported Barcode Formats | Group | Symbology | Notes | |---|---|---| | Retail · GS1 | EAN-13, EAN-8, UPC-A, UPC-E, ISBN-13, GS1 DataBar, GS1-128 | ISBN-10 auto-converts to ISBN-13 | | Linear · Industrial | Code-128, Code-39, Code-39 Full ASCII, Code-93, Code-11, Code 2of5 Interleaved, MSI Plessey, Flattermarken, Telepen Alpha | | | Pharma · Specialty | Pharmacode One-Track, Pharmacode Two-Track | Laetus binary codes | | Postal | KIX | Dutch PostNL routing | | 2D · Matrix | QR Code, Data Matrix, PDF417 | | --- ## Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Browser │ │ ┌───────────────────────────────────────────────────┐ │ │ │ index.html (UI + state) │ │ │ │ ├── JsBarcode (linear codes) │ │ │ │ └── bwip-js (specialty + 2D codes) │ │ │ │ ─→ HTMLCanvasElement → JPG / PNG Blob │ │ │ └───────────────────────────────────────────────────┘ │ └──────────────────────────────┬──────────────────────────────┘ │ HTTP (static assets only) ▼ ┌─────────────────────────────────────────────────────────────┐ │ Docker container · nginx 1.30.1 · alpine 3.23 │ │ • runs as non-root (UID 101) │ │ • read-only root filesystem │ │ • all caps dropped, no-new-privileges │ │ • CSP / HSTS / nosniff / X-Frame-Options / COOP / CORP │ └─────────────────────────────────────────────────────────────┘ ``` Encoding happens **entirely in the browser** — the container only ships static HTML/JS. The image cannot leak data because there is no application server to leak through. --- ## Requirements | Component | Minimum version | Why | |---|---|---| | Docker Engine | **24.0+** | Buildkit, compose-v2 features | | Docker Compose | **v2.20+** | Modern compose schema (`deploy.resources`, `tmpfs` options) | | Host OS | Linux / macOS / Windows (WSL2) | Anything Docker supports | | RAM | 64 MB free | Container reserves 32 MB, caps at 128 MB | | Disk | ~50 MB | Image is ~40 MB | | Open port | one host port (default 8080) | Reconfigurable via `.env` | Verify your toolchain: ```bash docker --version # → Docker version 24.x.x or newer docker compose version # → Docker Compose version v2.20+ or newer ``` If you're on an older Docker that only has the standalone `docker-compose` binary, the file still works — just replace `docker compose` with `docker-compose` in the commands below. --- ## Quick Start ```bash # 1. Unzip and enter the project unzip cipher-barcode-studio.zip cd cipher-barcode-studio # 2. (Optional) override the host port cp .env.example .env # then edit HOST_PORT if needed # 3. Build and start docker compose up -d --build # 4. Open in browser # http://localhost:8080 ``` That's it. To stop: ```bash docker compose down ``` To stop and remove the built image as well: ```bash docker compose down --rmi local ``` --- ## Configuration All runtime configuration lives in `.env` (copied from `.env.example`): | Variable | Default | Description | |---|---|---| | `HOST_PORT` | `8080` | Port published on the host machine | The container itself always listens on `8080` internally — only the host-side port is configurable. ### Putting it behind a reverse proxy If you're running this behind Traefik, Caddy, nginx, or similar: ```yaml # example with Traefik labels services: cipher: # ...existing config... labels: - "traefik.enable=true" - "traefik.http.routers.cipher.rule=Host(`barcode.example.com`)" - "traefik.http.routers.cipher.tls=true" - "traefik.http.routers.cipher.tls.certresolver=letsencrypt" - "traefik.http.services.cipher.loadbalancer.server.port=8080" ``` When fronted by HTTPS, the `Strict-Transport-Security` header in `nginx.conf` will kick in automatically. --- ## Project Structure ``` cipher-barcode-studio/ ├── docker-compose.yml # Orchestration + runtime hardening ├── Dockerfile # Image build steps ├── .dockerignore # Keep the build context lean ├── .env.example # Template for runtime config ├── LICENSE # MIT + third-party attributions ├── README.md # You are here ├── nginx/ │ └── nginx.conf # Hardened nginx config w/ CSP & security headers └── web/ ├── index.html # The full application (single HTML file) └── vendor/ ├── JsBarcode.all.min.js └── bwip-js-min.js ``` --- ## Security This image is built with a defense-in-depth posture even though it serves static content with no user data: ### Container hardening | Control | Mechanism | |---|---| | Non-root execution | Image's `USER` is `nginx` (UID 101). Compose pins `user: "101:101"`. | | Read-only rootfs | `read_only: true` in compose; writable paths exposed via `tmpfs`. | | Capability drop | `cap_drop: [ALL]` — no Linux capabilities granted. | | No privilege escalation | `security_opt: no-new-privileges:true`. | | Resource limits | CPU capped at 0.5 cores, memory at 128 MB. | | Minimal base | `nginxinc/nginx-unprivileged:alpine3.23-slim` — Alpine, no extra packages, security upgrades applied at build. | | Restricted log size | json-file driver capped at 5 MB × 3 rotations. | ### HTTP hardening (in `nginx/nginx.conf`) | Header | Purpose | |---|---| | `Content-Security-Policy` | Restricts scripts/styles/fonts/images to `self` + Google Fonts only | | `Strict-Transport-Security` | One-year HSTS with subdomain coverage (effective behind TLS) | | `X-Frame-Options: DENY` + CSP `frame-ancestors 'none'` | Clickjacking protection | | `X-Content-Type-Options: nosniff` | Disables MIME sniffing | | `Referrer-Policy: strict-origin-when-cross-origin` | Limits referrer leakage | | `Permissions-Policy` | Disables camera, mic, geolocation, USB, payment APIs | | `Cross-Origin-Opener-Policy: same-origin` | Window isolation | | `Cross-Origin-Resource-Policy: same-origin` | Resource isolation | | `server_tokens off` | Hides nginx version | Also: request size capped at 1 KB (this is a static GET-only service), hidden files (`/\.`) blocked, `/healthz` lightweight endpoint for orchestrators. ### Supply chain * JavaScript libraries are **vendored** under `web/vendor/`, not loaded from CDN at runtime. * Each `