Skip to content

Security (project conventions)

  • Mass assignment: define $fillable (allow-list) on every model; persist $request->validated(), never $request->all().
  • $hidden every encrypted attribute (and any secret) so a decrypted token can never ride into an Inertia prop or API response by accident — e.g. Credential.value, OAuthToken.access_token/refresh_token.
  • Validate all input through Form Requests (App\Http\Requests), never inline. Custom rule names are snake_case.
  • Authorize every action that touches a resource a user could be denied — don't rely on "they couldn't reach the URL." Policies for model permissions, Gates for simple non-model checks; the server check is the real boundary, UI affordances are cosmetic.
  • Never render unsanitised user content as HTML — no v-html (Vue) or {!! !!} (Blade) on anything the user supplied. Escape by default; sanitise only if HTML is genuinely needed.
  • User-managed third-party credentials (API tokens for connected services) live encrypted in the DB via the encrypted cast and the credentials UI — never .env. Only APP_KEY + the DB connection live in env. Never expose a stored credential back to the client: surface a masked preview / "connected" state only.
  • Never call env() outside config files. Read env into config, use config('...') elsewhere.
  • Scope + severity come from the threat model. This file is the tactical how-to checklist; docs/security/threat-model.md is the on-demand reference for what Tempo defends against (trust boundaries, attacker-controlled inputs, consciously-accepted risks). Consult it when judging a finding's severity, and review it when a change adds or alters a trust boundary (a new integration, input source, stored credential, or outbound write) — then bump its last_reviewed.
  • External side-effects (mail, webhooks, mutating third-party systems): default to a draft / dry-run a human approves before the irreversible action fires ("always draft, never auto-send").