File Storage Reference Public

Administration

Administrator and compliance guide to the ERPat File Storage security and governance model — permissions, governance policies, quarantine, legal holds, reference-aware purge, isolation guarantee, cron jobs, REST APIs, and the demo seeder.

Guide version: r1 Module version: 1.0.0 Updated: 2026-07-30 Estimated time: 9 min 3 views

How to think about File Storage security

File Storage is a logical layer over ERPat's shared general_files registry. It manages only its own logical files (file_nodes) and is deliberately blind to files owned by other modules — profile photos, payslips, ticket attachments. That boundary is not a filter you can misconfigure; it is enforced by construction (see the isolation guarantee below).

Everything an administrator controls falls into four layers:

LayerWhat it decidesWhere you manage it
PermissionsWho may use each capability at allRoles & Permissions settings
ACL (per file/folder/space)Who may see or act on a specific itemSet by users; computed server-side
PoliciesThe org-wide guard rails (sharing, expiry, retention)Governance → Policies
Legal holds & purge gatesWhat may never be permanently destroyedGovernance → Legal Holds; safe-purge job

The permission set

All permission keys live under the file_storage family and are gated by the module_file_storage setting. Grant the base permission plus the specific capabilities each role needs — capabilities are additive, and the high-risk governance keys should be reserved for administrators and compliance owners.

Base access

PermissionGrants
file_storageBase access to the module — see the workspace, the tabs, and files the user is authorized for. Required for every other key to take effect.
file_storage_createCreate folders and upload new files.
file_storage_updateRename, move, and upload new versions of existing items.
file_storage_deleteSend items to Trash and restore them.

Collaboration & sharing

PermissionGrants
file_storage_share_internalShare files and folders with other ERPat users, groups, or departments (Shared with Me grants).
file_storage_share_externalCreate external share links (token URLs) that reach recipients outside ERPat.
file_storage_manage_permissionsChange the ACL of an item — add or remove viewer/commenter/contributor/editor/manager/owner roles.
file_storage_create_spaceCreate a Team Space (tenant-owned shared area).
file_storage_manage_spaceManage a Team Space — membership, managers, and settings.
file_storage_create_file_requestCreate upload-only file requests that let outsiders send files in without seeing anything.

Governance (high-risk — restrict to admins & compliance)

PermissionGrants
file_storage_governanceAccess to the Governance admin console.
file_storage_governance_view_activityView the tenant-wide activity feed and audit trail.
file_storage_governance_manage_quarantineReview, release, or reject files held in upload quarantine.
file_storage_governance_manage_policiesChange the org-wide policies (sharing, expiry, OTP, retention, minimum managers).
file_storage_governance_legal_holdPlace and release legal holds that block permanent purge.
file_storage_governance_purgePermanently purge items — subject to the fail-closed safe-purge gate.
Separation of duties. Keep file_storage_governance_legal_hold and file_storage_governance_purge on different people where you can. A legal hold must be able to survive the person who wants to destroy the data.

The Governance console

The Governance console (FileStorage_admin) is a separate admin surface from the user workspace. It has four tabs, each gated by its own governance permission.

Policies

Org-wide guard rails, applied to every user of the tenant. Set these once and they constrain what sharing and lifecycle actions are even possible:

PolicyWhat it controls
External sharingWhether external share links may be created at all, and the widest scope allowed (restricted / tenant / external recipients / anyone / upload-only).
Link expiryThe default and/or maximum lifetime of a share link before it stops opening.
OTP defaultWhether new external links require an email one-time passcode by default.
Trash retentionHow long trashed items are kept before they become eligible for the safe-purge job.
Minimum managersThe minimum number of managers a Team Space must keep (default enforces at least two, so a space is never left ownerless).

Quarantine review

Uploads pass through a validated intake and can be held for review before they become live files. The Quarantine tab is where an authorized reviewer inspects held uploads and either releases or rejects them.

Upload validation today reuses ERPat's existing extension, MIME, and magic-byte checks through the shared file pipeline. The module also ships a pluggable scanner hook (File_scanner_service) for a real anti-virus / content-disarm engine, but no AV engine is enabled today — that is a planned future integration, not a current control.

Legal Holds

Place a legal hold on files or spaces that must be preserved for litigation, audit, or regulatory reasons. A held item cannot be permanently purged — the safe-purge gate treats an active hold as a hard block. Holds are recorded in file_legal_holds with their scoped items in file_legal_hold_items, and releasing a hold is itself an audited action.

Risk dashboard

A tenant-wide view of exposure — surfaced by the daily risk-scan job (see Cron jobs). It highlights the things a compliance owner needs to watch: broadly-scoped or expiring external links, quarantine backlog, and items under legal hold. Use it as the standing review surface for the security posture of the storage estate.

Reference-aware trash & permanent purge

Deletion is two-stage. First, items are soft-deleted to Trash and remain fully recoverable. Only after the trash-retention window — and only via the safe-purge job — can bytes actually be destroyed, and even then a fail-closed gate stands in the way.

Because physical bytes are shared through ERPat's general_files registry, the module keeps a reference ledger (general_file_references). Permanent purge is reference-aware: a physical object is destroyed only when nothing still points to it. The gate blocks a purge if any of these is true:

Blocking conditionWhy it blocks
An active reference existsAnother logical file (or module) still relies on the bytes.
It is the current versionA live file still resolves to this object.
Trash retention has not elapsedThe recovery window is still open.
A legal hold covers the itemPreservation overrides destruction.
Fail-closed means fail-closed. If the gate cannot positively confirm that destruction is safe, it does not destroy. A purge that is blocked is the system working correctly, not an error to override.

The purpose-policy isolation guarantee

File Storage never shows or touches files that belong to other modules. This is guaranteed by two design choices working together:

  • Owner-less registry rows. File Storage writes general_files rows with no owning-module identity of another feature. It only ever resolves logical files it created (file_nodes and their file_versions), so avatars, payslips, and ticket attachments are simply out of its reach — there is no query that could return them.
  • Purpose-policy registry. Every logical file carries a purpose, and the purpose-policy registry (File_purpose_policy) governs what may be done with it. Purposes are isolated from one another, so a policy configured for one purpose cannot leak behavior onto another.

The practical consequence: enabling, configuring, or even misconfiguring File Storage cannot expose another module's files. Isolation is a property of the data model, not a permission you must remember to set.

The six cron jobs

File Storage runs six Advanced Cron jobs. Each runs per tenant and self-gates on module_file_storage — a job does nothing on a tenant where the module is disabled. They are the moving parts that keep intake, access, membership, the registry, and retention correct without manual work.

Job slugScheduleWhat it does
file_storage_upload_pipelineevery 1 minProcesses queued upload sessions — validates, materializes bytes into the file pipeline, and promotes them to live versions (or into quarantine).
file_storage_access_expirerevery 5 minExpires share links and time-bound access grants that have passed their expiry or max-opens limit.
file_storage_membership_syncdailyReconciles Team Space membership against department membership so space access follows org changes.
file_storage_registry_reconcilehourlyReconciles the reference ledger against general_files so reference counts stay accurate.
file_storage_safe_purgedailyRuns reference-aware permanent purge on retention-eligible trashed items — respecting every fail-closed block above.
file_storage_risk_scandailyComputes the Governance Risk dashboard — exposure from external links, quarantine backlog, and held items.
These jobs are how policy becomes enforcement: a retention policy only matters because file_storage_safe_purge acts on it; an expiry policy only matters because file_storage_access_expirer revokes on it. Confirm the tenant's cron runner is active or none of it fires.

The two REST APIs

File Storage exposes two distinct API surfaces with different audiences and different authorization models.

Integration API — /api/v1/file-storage/*

The machine-to-machine surface for integrations, served by FileStorage_api. Every action authorizes a bearer scope (authorizeScope):

ScopeGrants
filestorage:readRead access — list and retrieve File Storage resources.
filestorage:writeWrite access — create and modify File Storage resources.

End-User API — /v1/api/file-storage/*

The self-service surface for the signed-in end user, served by FileStorage_euapi. It is gated by requireModule + requirePermit, and — critically — identity is always resolved from the session via selfUserId(). A caller can only ever see their own files: self-service list, view, download, recent, and starred. There is no way to ask this API for someone else's data.

The two surfaces are not interchangeable. The Integration API is scope-authorized for systems; the End-User API is self-scoped for people. Route integrations to /api/v1/file-storage/* only.

The demo seeder

The module ships a demo seeder, FileStorageDemo, that loads representative sample data (spaces, folders, files, versions, and shares) so an evaluator can see the workspace populated immediately. It is optional and operator-driven — run it only in demo or evaluation environments:

php erpat db:seed FileStorageDemo

The seeder is idempotent and reversible in line with ERPat's seeder contract; it does not install schema or functional configuration (those come from the module migrations).

Setup checklist

  1. Enable the module — turn on module_file_storage in Manage Modules.
  2. Run the module migrations: php erpat migrate:modules.
  3. Assign permissions per role — base access for everyone who needs files, collaboration keys for managers, governance keys for admins and compliance only.
  4. (Optional) Set your guard rails in Governance → Policies — external sharing scope, link expiry, OTP default, trash retention, and minimum managers.
  5. Confirm the tenant cron runner is active so the six jobs run.
  6. (Optional, non-production) Load sample data: php erpat db:seed FileStorageDemo.
Key takeaways: isolation is guaranteed by construction (owner-less rows + purpose policies); permanent purge is reference-aware and fail-closed; upload validation reuses ERPat's extension/MIME/magic-byte checks (no AV engine today); deletion is soft-delete first and recoverable; and policies only bite because the per-tenant cron jobs enforce them.
Was this guide helpful?

Report a content problem