Appearance
Security (project conventions)
- Mass assignment: define
$fillable(allow-list) on every model; persist$request->validated(), never$request->all(). $hiddeneveryencryptedattribute (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 aresnake_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
encryptedcast and the credentials UI — never.env. OnlyAPP_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, useconfig('...')elsewhere. - Scope + severity come from the threat model. This file is the tactical how-to checklist;
docs/security/threat-model.mdis 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 itslast_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").