Cybersecurity
1.6 Security, Compliance, and Data Protection
The platform applies a zero-trust security model integrating the following controls:
- mTLS for all internal communications (Istio Service Mesh);
- SPIFFE/SPIRE identities for workload authentication;
- Keycloak SSO for centralized access control, MFA enforcement, and short-lived OIDC tokens;
- Vault/HSM for key management and secret retrieval;
- Immutable audit logging via Loki and WORM object storage;
- Cloudflare WAF for perimeter protection;
- Row-Level Security (RLS) and Kafka partitioning by jurisdiction to enforce data segregation;
- GDPR compliance through pseudonymisation and data minimisation;
- DORA alignment via defined RTO/RPO targets and regular resilience testing. Regular independent audits confirm provider compliance with ESMA cloud outsourcing guidelines and the DLT Pilot Regulation.
1.6.1 Data Retention Policy
The DEUSS platform defines data retention policies based on data classification, legal obligations, security requirements, and operational needs, in alignment with GDPR principles and applicable financial regulations. The following retention categories are applied: Regulatory & Compliance Data (KYC/AML records, investor identification, transaction records, audit trails)
- retained for 5–10 years after the end of the business relationship. Business & Contractual Data (investor portfolios, offers, agreements, onboarding/case data)
- retained for the duration of the relationship and applicable legal retention period. Security & Audit Logs (user actions, access logs, audit events)
- retained for 1–5 years, with compliance-critical audit records retained longer where required. Technical & Operational Data (application logs, monitoring, telemetry)
- retained for 30–180 days unless required for security investigations or incident analysis. Key retention principles:
- data is classified by category and assigned a defined retention period
- retention must be justified by legal, regulatory, security, or operational requirements
- sensitive and compliance-critical records are protected using secure and immutable (WORM) storage where required
- expired data is automatically deleted or anonymized unless legal or regulatory obligations require extended retention This approach ensures compliance with GDPR, financial regulations, auditability requirements, and secure handling of sensitive platform data.
1.6.2 Privileged Access and System-wide Roles
The DEUSS platform applies strict Role-Based Access Control (RBAC) and least-privilege principles. System-wide access is limited to a minimal set of privileged operational and compliance roles. The following privileged roles may access selected cross-system information:
- System Administrator — operational configuration, infrastructure, user and platform management
- Compliance / Audit Role — read-only access to audit logs, transactions, and regulatory records
- Support / Operations Roles (L1/L2) — limited and controlled troubleshooting access for operational support activities Access to sensitive data (e.g. KYC, financial or compliance-related data) is:
- strictly role-restricted
- logged and auditable
- granted only on a need-to-know basis Partner (tenant/originator) data remains logically isolated and mutual visibility between partners is prohibited by default. All privileged access is protected using strong authentication (Passkeys/MFA), fully audited, and subject to periodic review. Additional controls such as approval workflows or just-in-time (JIT) access may be applied for elevated operations.
1.6.3 Partner Data Isolation and Tenant Access Control
The DEUSS platform enforces strict tenant isolation between partners (originators) to prevent unauthorized visibility of cases, financing offers, and related business data.
All business data is associated with a specific partner_id, which serves as the primary tenant isolation boundary across the platform.
Key protection mechanisms include:
- backend-enforced authorization
- strict RBAC enforcement
- backend-enforced authorization checks
- tenant-based filtering in all queries and APIs
- secure propagation of partner context (
partner_id,partner_role) via OIDC/JWT tokens - logical data segregation between partners
- separation of Partner APIs and Admin APIs
- centralized identity management via Keycloak
- encryption in transit and at rest Cross-partner access is denied by default and cannot be bypassed at the frontend level. All APIs validate ownership and partner context before granting access to requested resources. Partner status and lifecycle are centrally governed by the Partner Service. Suspended partners are immediately isolated across the platform by revoking API access and preventing continuation of active workflows. Additional safeguards include:
- immutable audit logging of access attempts & audit logging and monitoring of all privileged operations
- security and penetration testing focused on tenant isolation
- optional logical or physical data partitioning for sensitive datasets
- rate limiting and abuse protection This approach ensures confidentiality of partner data and prevents unauthorized visibility across tenant boundaries. This approach ensures that:
- partners operate in fully isolated data domains
- cross-partner visibility is technically prevented
- privileged administrative access remains controlled and auditable
- security and compliance requirements are consistently enforced across the platform.
File-lifecycle
745 — Backend-Owned File Lifecycle for Private Files
Companion to #744 (Permission Model). This issue is the file-domain Policy Enforcement Point (PEP) that consumes the Permission Model defined in #744 — do not implement in isolation.
Audited 2026-05-29 against current code. Core findings (IDOR onDraftDocumentFacade, missingexpires_at/single_use/allowed_content_type, per-feature validation, theuseScoringDocumentsbypass) all still hold. Two drifts applied: presigned-URL TTL now differs per service (company-servicePT5Mvs bond-issuancePT1H), and@AutoLogis now present on company-service document facades but still absent onDraftDocumentFacade. Line-number citations removed (they rot; anchor to class/method).
1. Context
The platform handles private files attached to sensitive business resources — company documents, scoring documents, issuance draft documents, and draft marketing assets. The current upload / download path is split between one shared frontend upload hook, several feature-specific frontend hooks, several backend facades, and a common S3 file manager.
Today the frontend computes helper metadata (SHA-256 checksum, content length, file type) and calls backend init / register endpoints. That logic is useful for UX and for early failure detection — it is not a security boundary. Frontend checks are trivially bypassable.
2. Terminology (use these terms consistently)
| Term | Meaning |
|---|---|
| Subject | Authenticated principal performing the operation (user or service account). |
| Resource | Business entity the file is attached to: Company, Scoring, IssuanceDraft, etc. |
| Parent Resource | The owning entity at the root of the authorization chain (a draft document's parent is the IssuanceDraft; an IssuanceDraft's ultimate parent for permission purposes is its Company / Originator). |
| Action | CRUD verb against the file: create, read, update, delete. Aligned with #744. |
| Permission Model | The tuple (Subject, Parent Resource, Action) → allow / deny decided centrally. Defined in #744 and reused here verbatim. |
| Policy Enforcement Point (PEP) | The single backend layer that consults the Permission Model on every file event. There must be exactly one PEP per file action — no per-feature shortcuts. |
| Upload ID | Short-lived, single-use capability token. Scoped to (Subject, Parent Resource, Action, expected_checksum, expected_content_length, allowed_content_type, expires_at). |
| Object Key | S3 storage key. Opaque to the client; never used as an authorization input. |
| Usable | State in which the file has passed verification and is visible to read operations. |
| Technical audit | Security / forensic audit of file events. Distinct from the business UseCase audit already wired on the frontend via X-Use-Case-Id headers. |
3. Current State (with code references)
3.1 Backend
- Shared S3 abstraction is in place but exposes both streamed download and presigned URLs:
offchain/common/file-manager/.../FileManager.kt— interface withupload,finishFileUpload,download,delete,generateUploadUrl,generateDownloadUrl.offchain/common/file-manager/.../S3FileManager.kt— implementation (upload, presign PUT, presign GET).- Per-service wiring via
FileManagerFactoryincompany-service,bond-issuance-service,registry-service,media-service.
- Upload IDs are not consistently scoped. Persisted entities carry
requested_by,checksum_sha256,content_length,state ∈ {REGISTERED, STARTED},created_at. They do not carry:expires_at— no expiry tracking.single_useenforcement — implied by record deletion, not by an invariant.allowed_content_typeallowlist — content type is stored but not validated as a capability constraint at finalize time.- Entities:
CompanyDocumentUploadEntity,ScoringDocumentUploadEntity,DraftDocumentUploadEntity,DraftMarketingAssetUploadEntity. ❌ None of the three missing scopes has been added since the original analysis. - ⚠️ Type drift:
DraftMarketingAssetUploadEntity.checksumSha256is aByteArray(byteacolumn) while the other three upload entities storechecksum_sha256asString. Normalize during the lifecycle work, or the central finalize-verify step has to special-case one entity.
- Authorization is inconsistent across resource types at the PEP layer:
- ✅
RegisterCompanyDocumentUploadFacade.registerUpload— callscompanyValidator.hasEditAccess(companyId, userId)and throwsForbiddenExceptionon deny. Parent-resource check. - ✅
RegisterScoringDocumentUploadFacade.registerUpload— checks parent company edit access + scoring stateCREATED. Parent-resource check. - ❌
DraftDocumentFacade.registerUpload— only checksissuanceDraftValidator.existsByIdOrNull(...). No Subject-vs-Parent-Resource check. - ❌
DraftDocumentFacade.upload— validatesuploadContext.issuanceDraftId == request.issuanceDraftId. Confirms the Upload ID's draft binding, but does not re-check the Subject's right to edit that draft now. - ❌
DraftDocumentFacade.downloadStream— validatesdocument.issuanceDraftId == request.issuanceDraftId. Same gap on the read path. - ❌
IssuanceDraftAuthorizationFacadewrapsDraftDocumentFacadebut does not perform a permission check when delegating toregisterUpload— the wrapping layer exists but the authorization step is not in it. The calling controller is@Secured(IS_AUTHENTICATED)only (any authenticated user, any draft).
- ✅
- Presigned URL TTL differs per service (drift). ⚠️
bond-issuance-service/.../application.yaml: GET/PUT defaultPT1H(env-overridable viaPUBLIC_API_PRESIGNED_URL_DURATION_GET/PUT).company-service/.../application.yaml: GET/PUT hard-codedPT5M.- Company documents thus expire in 5 minutes; bond/draft documents in 60. There is no single platform default, and the bond-issuance window is long enough to outlive a parent-resource permission change (a presigned URL keeps working after the Subject loses access). The original analysis pinned a uniform
PT1H; the real state is an undocumented per-service split.
- Validation is per-feature, not centralized. Checksum, content length, and content type are passed to the S3 SDK at PUT time (
S3FileManager.upload) but there is no post-finalize re-verification and no central allowlist enforcement. Each service defines its own MIME enums (DraftDocumentMimeType,CompanyDocumentMimeType,MarketingAssetFileType); the rules drift independently. - Technical audit (
@AutoLog) is now inconsistent across services (drift). ⚠️- ✅ company-service document facades are annotated:
RegisterCompanyDocumentUploadFacade,UploadCompanyDocumentFacade,DeleteCompanyDocumentFacade,GetAllCompanyDocumentsFacade. - ❌
bond-issuance-service'sDraftDocumentFacade(and its methods) remain unannotated — draft documentcreate/read/update/deleteproduce no audit signal. - There is still no file-event-specific audit table (
@AutoLogwrites to the structured log stream, not a queryable technical-audit store — see §5.4). The gap closed on one service and widened the cross-service inconsistency.
- ✅ company-service document facades are annotated:
3.2 Frontend
- Shared upload hook is in place and correctly two-step (register → upload):
frontend/libs/shared/src/hooks/useFileUploadFactory.ts—useFileUpload<TFileType>withregisterMutationthenuploadMutation.- Configs:
createDraftDocumentUploadConfig,createDraftMarketingAssetUploadConfig,createCompanyDocumentUploadConfig. - SHA-256 + content length computed in
frontend/libs/shared/src/utils/fileUtils.ts(FileUtils.calculateSHA256Checksum,file.size). - UseCase audit headers (
X-Session-Id,X-Use-Case-Id,X-Use-Case-Instance-Id) are already injected viacreateAuditedRequestInit— preserve this; the technical audit (this issue) is separate from the UseCase audit.
- One feature bypasses the shared hook and calls the OpenAPI SDK directly:
apps/deuss-frontend/src/pages/company/hooks/useScoringDocuments.ts— instantiatesregisterScoringDocumentUploadMutation/uploadScoringDocumentFileMutationfrom the generated SDK and orchestrates the two-step flow by hand. ❌ Still bypassing.- Result: a parallel upload path that any lifecycle hardening could miss. Bring it back onto the shared hook.
- No expiration handling. The shared hook has generic HTTP error handling but no detection of
409 / 410"Upload ID expired", no automatic re-init, no retry policy. With company-service now atPT5M, a slow scoring/document flow can expire mid-upload; today the user retries manually.
3.3 Concrete risk surface
- Stale Upload ID reused for a different resource → cross-tenant file replacement.
- Wrong-owner registration → file attaches under a foreign
company_id/originator_id/draft_id. - Checksum / content-length mismatch silently tolerated → integrity loss.
- Presigned download URL keeps working after the Subject loses access to the parent resource → data leak (acute at the
PT1Hbond-issuance setting). - Reused object key → silent overwrite of an existing usable file.
- Draft document events leave no technical-audit trail (
DraftDocumentFacadeunaudited) → no forensic record of a foreign-document access.
Realistic impact: data leak (foreign document), file-replacement attack, unauthorized persistence under a foreign resource, unaudited access to private draft files.
4. Goal
Define and enforce a backend-owned file lifecycle in which every Upload ID is a short-lived authorization capability scoped to a specific (Subject, Parent Resource, Action, expected metadata, expires_at). Every subsequent operation (read, update, delete) is re-authorized against the current Permission Model decision for the Parent Resource — independently of who uploaded the file originally. Validation, expiry, and the technical audit are centralized in the shared file manager and the common facades, so no per-feature path can drift.
5. Scope
5.1 File lifecycle (backend-owned, single PEP per action)
- Init upload — Caller requests an upload for an explicit
Parent ResourceandAction(createorupdate). PEP consults the Permission Model from #744 before issuing any Upload ID or signed URL. - Issue Upload ID — Scoped to
(Subject, Parent Resource, Action, expected_checksum, expected_content_length, allowed_content_type, expires_at). Single-use — invalidated on successful finalize, never reused. - Object upload to storage — Optionally via short-lived presigned URL (see 5.3) or via service-streamed PUT.
- Verify & register (finalize) — Before marking the file
usable, the backend verifies actual checksum, actual size, content length, content type against the allowlist, object presence in storage, and Upload ID validity + single-use state. Registration rejects any attempt to attach the object to a different Resource / Company / Originator / Draft than the one for which the upload was initialized. - Mark usable — File becomes visible to read operations only after finalize succeeds.
- Read / Update / Delete — All three actions re-enter the same PEP and re-consult the current Permission Model from #744. Original uploader identity is not a basis for any decision.
5.2 Validation & limits
The backend explicitly rejects: wrong-subject registration; wrong-resource registration (any drift in company_id / originator_id / draft_id / scoring_id); checksum mismatch; content-length mismatch; oversized files (per-resource-type limit, configurable); disallowed content types (allowlist, never a blocklist); expired Upload ID; already-finalized Upload ID. Normalize checksum_sha256 to one representation across all four upload entities so the central verify step has a single code path.
5.3 Presigned URLs
- For sensitive files, prefer service-streamed download (already used by
DownloadCompanyDocumentFacadeand the streamedDraftDocumentFacade.downloadStream). - Where a presigned URL is unavoidable, TTL must be minutes, not hours, and uniform across services. Today company-service is already
PT5Mwhile bond-issuance isPT1H— converge on one documented per-resource value (company-service'sPT5Mis the right order of magnitude; shrink bond-issuance to match). - A presigned URL must not outlive the Subject's access to the Parent Resource. If revocation happens during the TTL window, the backend must have a mechanism (key rotation, signed-URL scoping, or a service-streamed fallback) to honor it.
5.4 Technical audit (distinct from business UseCase audit)
Not the business UseCase audit. The frontend already attaches
X-Use-Case-Id/X-Use-Case-Instance-Idheaders viacreateAuditedRequestInit. This section is the security / forensic audit of file events.
Every sensitive file event (create, read, update, delete) is recorded with: subject_id; parent_resource_type, parent_resource_id; action; file_id (where applicable); decision (allow / deny) and reason; timestamp. The technical audit is queryable independently of the structured log stream and is retained per the platform's incident-response policy. (@AutoLog today emits to the log stream only; it covers company-service document facades but not DraftDocumentFacade — close that gap and back it with a queryable store. See the platform-wide audit-trail control in the GOV analysis, 1.3.)
6. Functional & technical requirements
- All behavior changes land in the shared S3 file manager and the common facades, not per-feature. The PEP is single — duplicating it per service is the regression we are fixing.
- The existing valid upload flow must keep working through the shared frontend hook (
useFileUploadFactory.useFileUpload) without UX regression. TheuseScoringDocumentshook that currently bypasses the shared hook must be migrated onto it. - API contract changes (new fields in upload-init response — e.g.
expires_at, allowed content types, max size) are reflected in OpenAPI and the generated clients. - Permission Model integration follows #744 exactly. Do not invent a parallel permission API for files.
@AutoLog(or the technical-audit equivalent) is applied uniformly across all four document facades, includingDraftDocumentFacade.
7. Acceptance criteria
- Upload ID is single-use, has a server-issued
expires_at, and is scoped to(Subject, Parent Resource, Action). It cannot be reused across any of those dimensions. - Backend rejects, with explicit error codes: wrong-subject registration, wrong-resource registration, checksum mismatch, content-length mismatch, oversized files, disallowed content types (allowlist), expired Upload ID, already-finalized Upload ID.
- Private files cannot be read, updated, or deleted without a current Permission Model decision against the Parent Resource — the original uploader identity grants no implicit access. This includes the
DraftDocumentFacadepaths (registerUpload,upload,downloadStream) and theIssuanceDraftAuthorizationFacadedelegation, which today perform no Subject check. - File registration cannot attach the object to a
company/originator/draft/scoringother than the one for which the upload was initialized. - Presigned URL TTLs for private files are uniform across services and measured in minutes (converge bond-issuance
PT1Hdown to the company-servicePT5Morder of magnitude); configured inapplication.yamland documented per resource. -
checksum_sha256is stored in one representation across all four upload entities (resolve theDraftMarketingAssetUploadEntityByteArrayvsStringdrift). - Sensitive file events (
create/read/update/delete) are recorded in the technical audit (separate from UseCase audit) withsubject / parent_resource / action / file_id / decision / reason, uniformly across all document facades includingDraftDocumentFacade. - The existing valid upload flow works without UX regression via the shared frontend hook;
useScoringDocumentsis migrated onto the shared hook; the hook detects Upload ID expiry (409/410) and transparently re-inits.
8. Risks & notes
- This is not solvable by a frontend MIME check or a single patch on a single upload endpoint. Frontend checks are bypassable; per-feature backend checks drift apart over time — already visible between
RegisterCompanyDocumentUploadFacade(checks access) andDraftDocumentFacade(does not), and now also in the per-service presigned TTL and the per-service@AutoLogcoverage. - Tightly coupled to #744 — implement together. The file PEP is a consumer of the Permission Model from #744; the user → company → bond → draft chain is the same authorization graph. The
IssuanceDraftAuthorizationFacadeis the natural home for the missing draft-document permission check. - When introducing Upload ID expiration, plan the frontend retry path in
useFileUploadFactory(detect expiry → re-init upload transparently). The shorter company-servicePT5Mwindow already makes this user-visible. - Shrinking presigned URL TTLs may surface flakiness in long-running frontend flows; pair the TTL change with an FE re-fetch policy.
- The technical-audit store overlaps the platform-wide Immutable Audit Trail control (GOV analysis 1.3). Build the file-event audit on that store rather than a file-domain-only table.