Browse Help Desk documentation
Behind the scenes

Security and storage

The protections GVenta Help Desk applies to every request, how files are stored and served, audit logging, encryption, the OAuth broker and health checks.

Documented from GVenta Help Desk version 1.8.55.

GVenta Help Desk is built on the assumption that it will be exposed to the internet, handle other people’s data and be driven by automation. This page describes the protections that apply to every request, how attachments are stored and served, what the audit log records, how secrets are kept, why Microsoft 365 sign-in goes through a broker, and the small resilience details in the interface.

What happens on every API call

Every request to /api/* passes through the same layers, in this order:

  1. Security headers. A Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, X-XSS-Protection, Referrer-Policy: strict-origin-when-cross-origin, a Permissions-Policy that denies geolocation, microphone and camera, HSTS in production over HTTPS, and removal of the X-Powered-By header.
  2. Canonical domain redirect. GET and HEAD requests arriving on a hostname other than your instance’s primary domain are redirected (301) to it. This is never applied to state-changing methods and never to internal endpoints.
  3. Rate limiting. Keyed by user ID when authenticated and by IP address otherwise, with separate buckets for auth, public, api and default, each with a configurable request count and window. Rate limiting fails open if the cache store is unreachable, so an infrastructure blip never locks your customers out. The health and metrics endpoints are exempt. See API getting started.
  4. Authentication, according to the route’s declared type: staff JWT, API key, flexible (either) or client. See Roles and access.
  5. CSRF. Required on all state-changing methods in browser and session flows. Tokens are 32 random bytes, held with a time-to-live (default 1 hour) and scoped to a user and IP address. API-key requests are exempt by design, because an attacker cannot forge a custom header cross-site.
  6. Handler execution and the JSON response.

Around these sit input validation and sanitization, prepared statements for every database query, AES-256-GCM encryption for stored credentials, and the audit log described below.

Attachment and file storage

  • Every file lives in private object storage. Nothing is written to local disk. Attachments, avatars and knowledge base images all go to storage.
  • Tenant isolation. Your instance’s files are kept under their own storage path prefix.
  • Configurable limits. Maximum file size (default 10MB) and an allowed-extension list (default: jpg, jpeg, png, gif, pdf, doc, docx, xls, xlsx, ppt, pptx, txt, csv, zip), both editable per instance.
  • A hard-coded blocklist on top of the allowlist. Even if an administrator adds a dangerous extension to the allowed list, the following are always rejected: php, phtml, php3, php4, php5, php7, phps, cgi, pl, asp, aspx, shtml, shtm, fcgi, exe, bat, cmd, sh and bash.
  • No public URLs. Every download is proxied through an authenticated endpoint that checks permissions before streaming the file. Client portal downloads additionally verify that the file’s ticket belongs to the contact’s company.
  • Every upload, download and delete is audit-logged.
  • Storage statistics are aggregated per category (attachments, avatars, media library) for the Resources page.

Why it matters: A file a customer attached to their ticket can only be fetched by someone who is allowed to see that ticket, and a file that could execute on a server is refused no matter what the settings say.

Audit logging

For every significant action, the audit log records the acting user (or none, for system actions), the action type, the entity type and ID, the full old and new values as JSON, the IP address, the browser user agent and a timestamp.

Twenty action types are defined: create, update, delete, read, login, logout, login_failed, export, import, send, receive, assign, close, reopen, archive, restore, approve, reject, merge and split. They apply across eleven entity types: user, ticket, message, attachment, kb_article, kb_category, email_account, api_token, tag, sla_policy and system.

Key properties:

  • Sensitive values are sanitized before they are written.
  • Audit failures never break the application. They are written to the error log and swallowed, so a problem with the audit trail can never block a customer’s ticket.
  • API-key authentication logs both successes and failures.
  • Cross-company access attempts from the client portal are recorded as unauthorized_access.
  • Knowledge base articles have an additional, separate per-article log of create, update, delete, publish, unpublish, view and permission-change events.

The log is available in the interface under Settings → Audit Logs and through the API, with filtering, per-entity history, per-user activity, recent activity, statistics, CSV export and a retention purge. See Settings.

Encryption and secret handling

  • Stored credentials are encrypted with AES-256-GCM, the authenticated encryption mode. This covers IMAP passwords, OAuth tokens and provider API keys. Each operation uses a random 96-bit IV and an authentication tag for integrity, and the result is stored base64-encoded. The encryption key is held in the instance’s configuration and is never stored in the database.
  • Passwords are hashed, never stored or logged in plaintext.
  • API tokens are stored only as SHA-256 hashes. The plaintext exists exactly once, in the response that creates it.
  • Microsoft 365 refresh tokens are held by the OAuth broker, not by your instance.

The OAuth broker

Microsoft Entra ID requires an application to declare fixed redirect URIs, but every GVenta Help Desk instance runs on its own domain. Registering a new Microsoft application for every customer would be impractical. The broker resolves this by terminating all Microsoft 365 OAuth traffic at a single registered redirect URI.

The flow when you connect a mailbox from Settings → Email Integration is:

Your instance → broker → Microsoft Entra ID → broker → your instance.

Microsoft only ever sees the broker’s callback URL. Your instance identifies itself to the broker with an installation ID and only ever handles short-lived access tokens; when one expires, the broker refreshes it with Microsoft internally and hands back a fresh one. The long-lived refresh token stays with the broker. Disconnecting removes the connection at the broker.

The broker is an independently run service with its own rate limiting, security middleware, signed state parameters, token encryption and health probe. If the broker rejects your instance’s credentials, the email pipeline backs off and retries rather than disabling your mailboxes.

Why it matters: Every customer gets Microsoft 365 integration on their own branded domain, without anyone needing to register a new Microsoft application per deployment.

Front-end resilience

Small things that are invisible when they work:

  • Automatic asset cache-busting. Every stylesheet and script URL is rewritten at render time to carry the application version, so browsers always load current assets after an upgrade, even when the previous file was cached as immutable.
  • Cache-Control: no-store on all dynamic HTML, because pages can contain user-specific content.
  • Dirty-state guarding. A shared guard warns before a page unload would discard an unsaved reply or form.
  • Open-redirect protection. Post-login redirect targets are validated against the application’s own origin before use.
  • Consistent HTML escaping at every render point, with documented, reviewed exceptions.
  • Output buffering around file uploads and API responses, so a stray server-side warning can never corrupt a JSON payload.
  • Graceful error states. Every list, chart and panel has explicit loading, empty, error and retry presentations rather than blank space.
  • Panel width persistence. Every resizable drawer remembers its width in the browser.
  • A notification heartbeat. A shared client-side poller checks for ticket changes every 30 seconds, keeps a de-duplicated alert list (capped at 10, newest first) and shows an unread badge on the bell.

The development-only system status page returns a 404 in every non-development environment, so it can never leak internals in production.

Health and metrics

  • GET /api/health returns status, timestamp and version. It requires no authentication, is exempt from rate limiting, and is intended for uptime monitors and liveness/readiness probes.
  • GET /api/metrics exposes Prometheus-compatible metrics and can be protected by a bearer token. It measures HTTP request counts and durations by route and status, database query counts and durations, cache hits and misses, authentication failures by reason, knowledge base article views, active sessions and email sync duration. Route paths are normalized before being used as labels, so ID-bearing URLs cannot create unbounded label cardinality.

Both endpoints are listed in the endpoint reference.