Skip to content
RobinMJDPublic

About

Free Modern URL Shortener.

Resources

Stars

0 stars

Watchers

0 watching

Forks

 
 

Repository files navigation

Kutt logo

Kutt: RobinMJD fork

A self-hosted URL shortener with public redirects, authenticated management, shared workspaces, advanced routing and operational controls. Built on thedevs-network/kutt, using Node.js, Express, server-rendered templates and HTMX. This is an independent fork, not the upstream hosted service.

Fork CI Release License: MIT

Project status

The manually deployed .69 release consolidates link editing around a single End date/time, uses browser-local start/end pickers and aligns Paused. Existing deadlines are migrated without extending validity; the legacy API remains compatible. Validation and recovery tracker. This one-off deployment was explicitly authorized while GitHub Actions is blocked. Remote CI and .69 GHCR publication are not claimed; exact-image tests, pre/post NAS writable recovery and live acceptance passed and are recorded in the manual deployment receipt. It also includes .68, which fixes lowercase country-code names displayed as "Unknown" on the Statistics map. Counts are retained; no analytics reset or GeoIP collection change is needed. Validation tracker.

The preceding 9 October 2026 release, 3.2.6-sr94.67.2, is published and was deployed, adding owner-confirmed per-link statistics reset with API support and queue/cache fencing, plus compatible template/proxy security patches. Main/tag CI, full exact-image regression, local/NAS writable recovery, public API authorization, desktop/mobile UI acceptance and repeated monitoring passed. Existing users, links and secrets are preserved. Open a link's Statistics page and choose Reset statistics; confirmation clears test analytics without changing link settings or redirect quotas. WAF/SSO and public redirects are unchanged.

The preceding 2 October 2026 release, 3.2.6-sr94.66, was published to GHCR and deployed on the homelab. Main/tag CI, exact-image regression, public redirect and management-boundary checks, stable health and verified writable local/NAS recovery passed. The security audit records the fixes and two bounded residual risks. The preceding .65 open-issue review records eight applicable upstream issue fixes and the disposition of all 69 open issues. The earlier homelab Actions budget blocker was resolved using its dedicated, repository-scoped runner. Repository hygiene and deployment checks passed; no paid fallback or relaxed checks were used.

The source includes all 16 original roadmap features and all 21 community features, all complete. English (default), French and Spanish use separate catalogs with matching stable keys. See the delivery ledger for exact versioned coverage and historical evidence, and the regression guide for reproducible checks.

SQLite is the fully exercised database engine. PostgreSQL 17 and MySQL 8.4 have targeted security/concurrency tests; MariaDB remains configuration-only. These are not full feature-parity guarantees. Security reviews and image scans are bounded, dated evidence, not certification that the application has no vulnerabilities.

The preceding .64 update completed the 12-finding interface review: aligned search/sort controls, visible dark-mode icons, compact responsive layouts, consistent focus/controls and useful empty analytics. Live browser acceptance covered 72 pages across 18 locale/theme/viewport combinations. Public redirects and WAF/SSO/CSP/TLS controls remain unchanged.

The preceding .59 update replaces the homepage's relative expiry with calendar/time pickers for UTC start/end, displaying yyyy-MM-dd HH:mm:ss. See creation schedules and the accepted release evidence. Blank dates mean immediate availability and no scheduled expiration. The legacy relative-expiry API remains compatible. From .69, personal/admin editing uses local dates and no longer exposes a relative duration; the explicit UTC creation controls retain their behavior.

Contents

Features

The fork retains custom domains, custom aliases, password-protected links, visit statistics, local accounts and administrator tools. It adds:

Area Capabilities and guide
API access Named, scoped, expiring and revocable tokens, domain restrictions and retry-safe idempotent link creation
Link availability Pause, scheduled start/end and maximum visits, enforced when a redirect is requested
Recovery Change history, trash and restore, with protection against silently reusing retired aliases
Moderation Atomic administrative bans, explicit recovery and audit, with permanent credential revocation and final-administrator protection
Appearance System, Light and Dark, persistent browser preference, readable charts and unchanged white QR exports
Languages English (default), French and Spanish, separate extensible catalogs, request-local translation and localized UI, feedback, email, dates and numbers
Sign-in Stable OIDC identities, signed logout, explicit signing algorithms and opt-in administrator-claim mapping, with bounded grants and protected recovery
Management and domains Optional separate management hostname and explicit domain sharing, host-only cookies, creator-isolated analytics and permanent scoped-token revocation
Organization Tags, collections, saved filters and bulk actions
Data transfer CSV/JSON import and export, templates, dry-run previews and explicit conflict handling
QR codes PNG/SVG downloads, PNG clipboard copy and printing, with validated PNG branding, generated locally
Collaboration Shared workspaces with owner/editor/viewer roles and conflict-aware editing
Routing Ordered device, language, country and query rules, with a redirect preview
Forwarding Multi-segment aliases and allowlisted query/path forwarding
Campaigns UTM campaign builder in personal, admin and workspace editors, with API support
Analytics Date ranges, exports, tag summaries, bot filtering and interactive country maps, keyboard controls, a text alternative and owner-confirmed statistics reset
Destination policy Mandatory safe-scheme baseline and optional allowed-host policy, enforced across writes, imports, redirects, management links and monitoring
Browser protection Staged and enforced Content Security Policy, fresh nonces and compatible native/HTMX workflows
Transport and performance Verified database/Redis TLS, safe sorting and profile-led SQLite indexing
Privacy Per-link tracking opt-outs and administrator-controlled retention
Integrations Signed asynchronous webhooks, delivery history, retries and private live updates
Monitoring Opt-in destination checks, private aggregate monitoring and protections against requests to private network destinations
Performance monitoring Dedicated authenticated metrics listener, bounded request/latency labels and process gauges; off by default
Optional mobile client Scoped-token iOS Shortcut; Apple Shortcuts is not needed to run the server

Management screens include responsive tables, keyboard-accessible dialogs, draft preservation, conflict feedback, masked one-time credentials and explicit clipboard success/failure states. See the UI/UX ledger for measured coverage and limitations.

Quick start

Docker Compose with SQLite

Use Docker Engine with the Compose plugin. The default example builds the checked-out source, persists SQLite and custom assets in named volumes, and publishes only 127.0.0.1:3000 on the Docker host.

git clone --branch v3.2.6-sr94.67.2 --depth 1 https://github.com/RobinMJD/kutt.git
cd kutt
cp .example.env .env
chmod 600 .env

Before starting, edit .env:

  1. Set JWT_SECRET to a long random value. For a new installation, generate one with openssl rand -base64 48. Preserve the existing secret on upgrades.
  2. Set DEFAULT_DOMAIN to the canonical hostname, without a scheme or path, for example short.example.com. Keep localhost:3000 only for local testing.
  3. Leave anonymous creation and local registration disabled unless deliberately required. Keep TRUST_PROXY=false for direct local access; configure it explicitly for your trusted proxy topology before deployment.
docker compose config --quiet
docker compose up --build -d
docker compose ps

The container applies database migrations before starting. Complete first-admin setup privately before enabling public routing. On a remote Docker host, use a private tunnel or the documented trusted proxy path, not a public backend port. Follow deployment setup to configure TLS, WAF and OIDC management access before exposure. These controls are not automatically installed by the Compose example.

The default example is a starting point, not the complete hardened homelab deployment. Add an appropriate restart, backup, monitoring and resource policy for your environment. Do not use docker compose down --volumes on an instance whose data you intend to keep.

Published images and alternative examples

The accepted fork release image is ghcr.io/robinmjd/kutt:v3.2.6-sr94.67.2 (digest sha256:5381aae825699e437833eb419920cb79427586efcd52c38248274de4603bb514). For an image-based deployment, replace the Compose service's build section with an image reference, retaining its environment and persistent volumes. Pin the tested image digest in production. The upstream kutt/kutt Docker Hub image does not contain these fork changes.

The fork workflow tests main and release tags, but publishes an image only for v*-sr94.* tags. Publication is separate from deployment; do not auto-deploy untested branch builds or moving tags.

Example Purpose
docker-compose.yml SQLite, no external database service
docker-compose.sqlite-redis.yml SQLite with private Redis for visit queues and shared rate-limit state
docker-compose.postgres.yml PostgreSQL configuration example; explicit database image and credentials required
docker-compose.mariadb.yml MariaDB configuration example; separate application and root credentials required

Select an alternative with docker compose -f <file> ...; these are standalone examples, not overlays to combine. Read the database and Redis caveats before using them. In particular, the Redis examples do not persist queued work, and changing database image tags is not a supported major-version migration.

Configuration and access

Use .example.env as the configuration checklist and server/env.js for the accepted values/defaults. Avoid copying an old full environment table from another Kutt version. Important settings:

Setting Operational guidance
JWT_SECRET Required for production; preserve it with the database because it also protects sessions and encrypted webhook credentials
DEFAULT_DOMAIN, SITE_NAME Canonical hostname and display name; the domain contains no scheme/path
DISALLOW_ANONYMOUS_LINKS, DISALLOW_REGISTRATION Both default to true; delegated OIDC provisioning is a separate policy
OIDC_*, DISALLOW_LOGIN_FORM Configure native SSO and provider admission first; use DISALLOW_LOGIN_FORM=true for SSO-only login
OIDC_ALLOW_REGISTRATION Controls new OIDC identities, not local signup; defaults to true
OIDC_SESSION_MAX_SECONDS Absolute OIDC session lifetime; defaults to 3,600 seconds
TRUST_PROXY Defaults to false. Enable only for an exclusive trusted proxy path that replaces untrusted forwarding headers; prefer explicit peer addresses over trusting all proxies
CSP_MODE Optional off (default), report-only or enforce; see nonce policy and customization compatibility before enabling
MANAGEMENT_ORIGIN Empty by default; separate management hosting requires matching WAF/TLS/SSO routes and IdP callbacks first
OIDC_ADMIN_MAPPING_ENABLED, OIDC_ADMIN_* Off by default; use explicit signed claim/value mapping with a tested protected recovery administrator
DESTINATION_ALLOWED_HOSTS Empty disables the optional host allowlist, not the mandatory safe-scheme check; an explicit empty JSON array denies all destinations. Review policy and recovery before enabling
METRICS_* Off by default; enable only a private, bearer-authenticated listener. Do not publish the metrics port
DB_*, REDIS_* Must match the selected topology, persistent paths and existing credentials
ENABLE_RATE_LIMIT Defaults to true for management API protection. Disable only behind an equivalent tested rate limit; protected-link password and report throttles remain enabled independently
CUSTOM_DOMAIN_USE_HTTPS Controls custom-domain link URLs; it does not provision DNS, TLS certificates or proxy routes
MAIL_*, REPORT_EMAIL, CONTACT_EMAIL Configure SMTP for verification/recovery/report workflows; mail is disabled by default

Declared application settings support NAME_FILE secret-file input. File values take precedence over inline values and unreadable files fail startup. Mount secret files read-only at their container paths. The supplied Compose examples validate an inline JWT_SECRET; adapt that declaration deliberately when switching to file-only injection. Never commit real .env files, credentials, identity mappings, databases or backup archives.

Public links, protected management

  • Public short-link redirects do not require a Kutt or Authentik login. An explicitly password-protected link still requires its own link password, and pause/expiry/trash rules still apply. Do not put an interactive SSO challenge over the whole hostname.
  • Management uses Kutt's authorization and native OIDC. The homelab uses Authentik plus BunkerWeb/WAF; a new installation must configure its own edge protections and provider access policy. Never expose a direct backend to bypass those controls.
  • API clients authenticate with scoped tokens. OIDC callbacks and signed back-channel logout need their documented routes, not an extra interactive SSO challenge. WAF and application validation must remain in place.
  • Existing accounts are not automatically linked by email. Before migrating an existing OIDC installation, follow the verified identity-binding procedure. The callback is https://YOUR_DOMAIN/login/oidc; changing issuer or subject mode later requires a planned identity migration.

API and integrations

The application serves both /api and /api/v2. Prefer named tokens created in Settings, give each integration only the scopes/domain it needs, and send the secret in the X-API-Key header. Public redirects need no API credential. Legacy API keys remain compatible but broad; migrate integrations to scoped tokens rather than treating old keys as least-privilege credentials.

Start with the API token and idempotency guide. Feature guides above document their endpoints, permissions, limits and recovery behavior. Some account, invitation and administrator operations deliberately require a browser session; a token never grants site-administrator privileges.

The base API specification can be rendered with npm run docs:build in a development checkout. It is not a complete reference for every fork endpoint; use the feature guides for additions. Older upstream API clients may work with compatible routes but are not automatically validated against the fork's scopes and security policies.

The optional iOS Shortcut guide covers the reviewed artifact and private token setup. It is an API client example, not a server dependency or deployment mechanism.

Development and testing

Use Node.js 24. SQLite is the default and needs no separate database server. There is no frontend bundler step; templates, CSS and browser scripts ship with the application. Install locked dependencies, configure a private .env and initialize the database:

npm ci
npm run migrate
npm run dev

npm start runs production mode. Standalone Node listens on network interfaces, so restrict access with your host firewall/trusted proxy; the Compose loopback binding does not apply to a standalone process. Native SQLite dependencies may need a compiler toolchain when no prebuilt binary is available for your platform.

For release validation, use a separate disposable checkout with no .env:

docker build -t kutt-smoke .
docker run --rm --network none --entrypoint node kutt-smoke tests/container-smoke.cjs
sh tests/redis-smoke.sh kutt-smoke
python3 tests/compose-config.py

The full smoke suite uses temporary SQLite data and tests migrations, API compatibility, authorization, public redirects and feature behavior. The Redis test uses its own isolated containers. Compose validation renders the four examples without claiming full PostgreSQL/MariaDB runtime coverage.

See test instructions for rendered desktop/mobile suites, fresh loopback fixtures, Playwright requirements and separate native-device acceptance. Never point disposable browser tests at a real deployment or an existing user's browser profile. CI is not a substitute for testing the exact deployed image, real SSO, monitoring and recovery.

Upgrades and recovery

Read deployment and recoverable upgrades before changing an existing instance. Release publication alone is not a successful deployment.

  1. Record the current source/image digest, configuration, database version and health. Take a consistent backup of the database, custom assets and private configuration/secrets, and keep an encrypted off-host copy.
  2. Restore into an isolated instance using the exact candidate image; verify integrity, migrations and a write/read cycle before deploying. For SQLite, use its backup API or stop writers; copying only a live main file is unsafe.
  3. Deploy the tested digest without replacing secrets or volumes. Validate existing links, public redirects, management authorization, real OIDC and enabled integrations, then observe monitoring through a post-restart interval.
  4. Take a clean post-deployment backup and verify restoration again. Record the deployment and rollback evidence separately from release CI.

Prefer fix-forward or a documented schema-compatible rollback. Old images can ignore newer lifecycle, privacy or revocation rules; do not drop populated policy tables to force a downgrade. Restores must reconcile later writes and revoked credentials before reopening access. The repository alone can recreate an empty application, not recover lost users, links, secrets or analytics.

Customization

Local branding can override files through custom/css, custom/images and custom/views. A custom/css/styles.css replaces the corresponding default stylesheet. Keep overrides under version control without private credentials, and include them in backups.

Containers load overrides from /kutt/custom. The example Compose files mount a persistent custom volume there; populate that volume or use a deliberate bind mount instead of relying on edits to an ephemeral container filesystem. Restart after template/configuration changes and verify loaded browser assets.

Custom templates and themes must be checked on every upgrade. Older upstream themes may omit new controls, validation, accessibility or security behavior; they are not automatically compatible with this fork. Preserve versioned asset URLs and their query strings through proxy/CDN caches.

Documentation

Task Start here
Install, upgrade or restore Deployment and recovery
Configure SSO or migrate identities OIDC security
Connect an API client Tokens and idempotency
Understand a feature Feature guides
Operate destination checks Destination health
Review hardening and scan limits Security maintenance
Reproduce automated checks Testing
Check implementation/deployment status Original roadmap, community ledger and UI/UX ledger
Add another language Catalog and language-registry guide
Review community ideas and attribution Community review

Contributing and credits

Propose fork-specific fixes and enhancements through pull requests. The fork's issue tracker is currently disabled. Include the version, database engine, reproduction steps and sanitized logs; never post credentials, private destinations or identity mappings. Changes should include focused tests, authorization/backward-compatibility checks and any migration/recovery notes.

This fork builds on the work of the upstream Kutt contributors. Original author: Pouria Ezzati. Special thanks to Thomas and Muthu; logo design by Muthu. Selected community proposals are credited in the review linked above. The upstream hosted service and third-party integrations are operated independently of this fork.

Licensed under the MIT License.

About

Free Modern URL Shortener.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages