Technical documentation for Kontorfix. Stack details may be named here (the outward-facing README stays deliberately technology-neutral).
- Backend: Laravel 12 (PHP 8.2+), served via FrankenPHP.
- Frontend: Inertia.js v2 + Vue 3 + TypeScript, Tailwind CSS 3, shadcn-vue.
- Data: PostgreSQL 17 (UUID v7 primary keys), Redis (cache + queue).
- Operations: Laravel Horizon (queue dashboard), Reverb (live updates over WebSockets), Scheduler (periodic re-sync + cleanup).
- Registry protocols: Composer v2 (
packages.json,p2/*.json, dist download) and npm (packument, tarball, publish). Plus a REST management API under/api/v1with auto-generated, interactive documentation at/docs/api(operator admins only).
Registry and webhook endpoints run deliberately stateless (outside the web middleware
group, without cookies/CSRF) and are secured solely by token or signature verification.
ddev start
ddev composer install
ddev exec npm install
ddev exec php artisan key:generate
ddev exec php artisan migrate --seed
ddev exec npm run devUseful commands (all via ddev exec …):
ddev exec vendor/bin/pest # Test suite
ddev exec vendor/bin/pint # Code style (Laravel Pint)
ddev exec vendor/bin/phpstan analyse # Static analysis (Larastan, level 6)
ddev exec npm run lint # ESLint
ddev exec npm run build # Frontend buildapp/Http/Controllers/{Registry,Api/V1,Admin,Portal,Auth,Settings}— endpoints per area.app/Services/{Upstream,Registry,Storage,Health,...}— business logic/services.app/Http/Middleware— incl.AuthenticateRegistry,AuthenticateApiKey,EnsureOperator,EnsureUserRole,SecurityHeaders,RejectRobotWebSession.resources/js/pages— Inertia/Vue pages (admin, portal, settings).routes/{web,api,registry,webhooks,auth,settings,console,channels}.php— routing.docker/— container entrypoint (roles app/worker/scheduler/reverb) + Compose.
- Operator invariant (security-critical): the privileged roles
admin/maintainerexist exclusively in the operator organization (is_operator = true). Customers aremember. Enforced byEnsureOperatoracross the entire/adminarea and in theStore/UpdateUserRequestrules. - Account types:
human(interactive login) androbot(API key only, no interactive login — blocked in the password/2FA/OIDC flows plus a globalRejectRobotWebSessionmiddleware).
Kontorfix runs behind a reverse proxy (e.g. Traefik). Set the following ENV values for a production deployment:
TRUSTED_PROXIES— pin to the concrete proxy IP(s), not the broad default private ranges. TheX-Forwarded-*headers are only accepted from these addresses; with too broad a configuration the client IP (and thus IP-based rate limits as well as the host in generated URLs) could be spoofed. Also make sure the app port is reachable only through the proxy (network segmentation).SECURITY_HSTS=true— once TLS is terminated at the proxy.SESSION_SECURE_COOKIE=true— session cookie over HTTPS only.SECURITY_CSP_REPORT_ONLY=true— roll out the Content-Security-Policy in report-only mode first, evaluate violations (Inertia/Vite compatibility), then switch to enforcement.APP_DEBUG=falsein production.
The artifact storage (local or S3/MinIO) is configured by the operator admin and is treated
as trusted infrastructure. Two deliberate properties:
- The S3 endpoint is not filtered against internal addresses — an internal MinIO on the container network is the normal case. Since only the highest-privileged operator admin sets this configuration, this is an accepted residual risk (no lower-privileged actor can influence the endpoint).
- A broken S3 configuration can interrupt downloads (the
artifactsdisk throws on errors). Use the built-in connection test before saving.
Deliberately classified as low and documented in the security audit (non-blocking):
- DNS rebinding (TOCTOU): the SSRF check (
UrlSafety::isSafeResolving) and the actual later connection resolve the hostname separately. Fully closed only with a resolver pinned to cURL (CURLOPT_RESOLVE). - Open self-registration:
/registerallows creating amemberaccount without an organization (which sees nothing in the portal). For a closed instance,/registercan be gated or disabled. - OIDC email linking: auto-linking across multiple enabled identity providers for member accounts — relevant only with multiple, partly untrusted IdPs.
- API existence oracle: route model binding runs before the key auth; non-existent
{id}routes return 404 instead of 401. Low value due to non-enumerable UUIDs. - API docs in
local:/docs/apiis ungated in thelocalenvironment (development). - JWKS cache: OIDC reloads the JWKS on each callback (performance/robustness, not a security issue).