Skip to content

Last updated:

Security overview

What the application does to protect your accounts and your clients' data. Every statement on this page is generated from a list in the application code that names the file proving it, and the build fails if that file goes away. What is not on this page is not claimed: there is no database-level encryption at rest, no penetration-test report and no SOC 2 or ISO certificate to point to.

Identity and access

Two-factor authentication

Every account can enable authenticator-app two-factor authentication with recovery codes. Enabling it requires confirming the code, and changing it requires the account password. Accounts that sign in through the iUtils hub carry the hub's two-factor state with them.

Evidence: config/fortify.php, app/Support/SocialHubAuthBridge.php

Agency-enforced two-factor

An agency owner or admin can require two-factor of every member from Settings → Team. Members get a grace period (seven days by default) to set it up; after that they are held at Security settings until they do, and the hold is audited. A member who signs in through the iUtils hub satisfies the requirement with the hub's two-factor.

Evidence: app/Http/Middleware/RequireAgencyMfa.php, app/Support/Agency/AgencyMfaPolicy.php, app/Livewire/Pages/Settings/Team.php, config/saas.php

Step-up authentication

Security settings and two-factor changes require a fresh password confirmation, or for hub accounts a fresh sign-in on the hub. Confirmation attempts are limited to five per minute.

Evidence: app/Http/Middleware/RequirePasswordConfirmation.php, routes/settings.php, app/Providers/FortifyServiceProvider.php

Password policy

In production, passwords must be at least 12 characters with mixed case, a number and a symbol, and are checked against known breach corpora before they are accepted.

Evidence: app/Providers/AppServiceProvider.php

Sign-in throttling

Sign-in, two-factor and password-confirmation attempts are limited to five per minute per account.

Evidence: app/Providers/FortifyServiceProvider.php

Sessions

Sessions are stored server-side in the database, expire after 120 minutes of inactivity, and use HTTP-only, SameSite cookies. A sign-out on the iUtils hub ends every session here through a signed back-channel request.

Evidence: config/session.php, app/Support/SocialHubAuthBridge.php

Roles and permissions

Agency roles (owner, admin, member), an optional content role (creator, reviewer, publisher) and per-brand roles with a permission map decide what each person can see and do. Owners and admins manage the roster, roles and invitations from Settings → Team; the last owner cannot be removed or demoted. The five brand-role presets and their permissions are published in the help centre.

Evidence: app/Enums/AgencyRole.php, app/Enums/AgencyContentRole.php, app/Policies/AgencyPolicy.php, app/Livewire/Pages/Settings/Team.php, app/Support/BrandAccess/DefaultBrandRoles.php, app/Support/BrandAccess/BrandAccess.php

Secrets and tokens

Platform tokens are encrypted at rest

OAuth access and refresh tokens for every connected network — and the Bluesky app password — are stored with application-level encryption, so a copy of the database yields no usable credential. Disconnecting a network deletes our copy and is recorded in the audit log.

Evidence: app/Models/LinkedInConnection.php, app/Models/MetaConnection.php, app/Models/BlueskyConnection.php, app/Observers/ConnectionAuditObserver.php

API tokens are scoped, expiring and hashed

API tokens are minted from Settings → API access with a name, an explicit set of scopes and an optional expiry, shown once and stored only as a SHA-256 hash. Every API endpoint and MCP tool declares the scope it needs and refuses a token without it. Issuing and revoking a token is audited, and each token records when it was last used.

Evidence: app/Models/ApiToken.php, app/Support/Api/ApiScopes.php, app/Http/Middleware/EnsureApiScope.php, app/Livewire/Pages/Settings/ApiAccess.php, app/Http/Middleware/AuthenticateMcpToken.php

Public link tokens are hashed

The tokens in client approval portal links and shared report links are stored only as a SHA-256 hash; the plaintext exists in the link and nowhere else. A read-only copy of the database yields no live approval or report link.

Evidence: app/Models/ClientApprovalPortal.php, app/Models/SharedAnalyticsReport.php

Webhook secrets and signatures

Outbound webhook secrets are encrypted at rest and shown once; every delivery is signed with HMAC-SHA256. Inbound webhooks from Meta and Stripe are signature-verified, and the Stripe endpoint refuses requests when no signing secret is configured rather than trusting them.

Evidence: app/Models/WebhookSubscription.php, app/Jobs/SendWebhookNotificationJob.php, app/Http/Controllers/Webhooks/MetaWebhookController.php, app/Http/Middleware/EnsureStripeWebhookIsVerifiable.php

Application security

Browser security headers

Every response carries X-Frame-Options, X-Content-Type-Options, Referrer-Policy and a Permissions-Policy that denies camera, microphone and location. HTTPS responses in production carry Strict-Transport-Security. An enforced Content-Security-Policy blocks plugin content, base-tag injection and framing; a stricter policy runs in report-only mode with violations reported to the application.

Evidence: app/Http/Middleware/AddSecurityHeaders.php, config/security.php, app/Http/Controllers/CspReportController.php

Rate limiting

Public write endpoints (contact form, client portal decisions, CSP reports), the public link pages (SmartLinks, short links, portals, shared reports), inbound webhooks, registration and password-reset requests, OAuth callbacks, analytics exports and the API are all throttled per route; API calls are also limited per token, and Livewire requests are capped per user.

Evidence: routes/web.php, routes/api.php, app/Providers/FortifyServiceProvider.php, tests/Feature/Security/PublicEndpointThrottleTest.php

Server-side request guards

Every URL the server fetches on a customer's behalf — webhook targets, RSS feeds, CSV imports, link previews, website snapshots — passes a guard that refuses private and internal address ranges and re-checks each redirect hop.

Evidence: app/Support/Http/PublicUrlGuard.php, app/Support/Http/SafeHttp.php

Upload validation

Uploads are validated by type and size on every upload path, including the client portal.

Evidence: app/Providers/AppServiceProvider.php, app/Http/Controllers/ClientApprovalPortalController.php

Production preflight

A deployment refuses to go live with debug output enabled, insecure session cookies, a wildcard trusted-proxy setting or a non-HTTPS application URL.

Evidence: app/Console/Commands/SocialPreflightCommand.php

Dependency auditing

Continuous integration blocks a merge on high or critical advisories against the PHP and JavaScript lockfiles.

Evidence: composer.lock, package.json

Data governance

Personal-data register

Every database table is classified in a register that records the data category, purpose, retention period, whether it is exportable, and how it is erased. A test fails the build if a table is added without an entry.

Evidence: app/Support/PersonalData/PersonalDataRegistry.php, tests/Feature/PersonalData/PersonalDataRegistryTest.php

Retention is enforced nightly

A nightly job deletes records past the periods the register states — the same periods printed on the privacy policy. Security and sign-in records are kept for 24 months.

Evidence: app/Services/PersonalData/RetentionPruner.php, app/Console/Commands/PruneOldSocialDataCommand.php, bootstrap/app.php

Access and erasure requests

A per-person data export is produced from the register with secrets redacted, and erasure runs a per-table strategy that leaves a tombstone proving it happened. Both are audited. Requests are fulfilled by iUtils on receipt at the privacy contact.

Evidence: app/Services/PersonalData/UserDataExporter.php, app/Services/PersonalData/UserErasureService.php, app/Console/Commands/ExportUserDataCommand.php, app/Console/Commands/EraseUserDataCommand.php

Security audit log

Sign-ins and failures, two-factor changes, network connections and disconnections, team and brand role changes, invitations, approvals, client portal actions, API token events and privacy operations are written to an audit log with the acting user and, where an administrator acts on someone's behalf, the impersonator. Agency owners and admins read it at Settings → Audit log, filter it by category, person and date, and export it as CSV or JSON Lines; the export is itself audited.

Evidence: app/Services/Security/AuditLogger.php, app/Models/SecurityAuditEvent.php, app/Support/HubImpersonation.php, app/Livewire/Pages/Settings/AuditLog.php, app/Http/Controllers/AuditLogExportController.php

Access review

An access-review report of who holds which role runs on the first of every month.

Evidence: app/Console/Commands/SecurityAccessReviewReportCommand.php, bootstrap/app.php

Operations

Monitoring

A runtime check every five minutes watches failed jobs, queue depth and age, the scheduler heartbeat and proxy trust, and alerts by email or Slack when configured. Application errors go to Sentry when configured, with default personal-data capture switched off.

Evidence: app/Console/Commands/OpsRuntimeCheckCommand.php, config/operations.php, config/sentry.php

Tenant isolation

Brands resolve only within the signed-in user's agencies and memberships; a brand outside them is not found rather than forbidden. A cross-agency isolation test holds this on every build.

Evidence: app/Providers/AppServiceProvider.php, app/Support/BrandAccess/BrandAccess.php, tests/Feature/Security/CrossAgencyIsolationTest.php

This deployment

The mechanisms above are in the code for every deployment. These lines are read from this deployment's configuration at the moment you load the page.

  • Error reports go to Sentry, with default personal-data capture off.

Reporting a vulnerability

If you believe you have found a security problem, tell us before you tell anyone else and give us a reasonable time to fix it. Do not access data that is not yours, and do not run automated scanners against customer workspaces. We will acknowledge a report, keep you informed, and credit you if you want that.

Security contact: [email protected]

Data-processing agreements and security questionnaires: [email protected]